Checking your session…

For Partners

Everything in this chapter is partner territory: reselling the platform to your own customers under your own brand, with your own pricing, your own payment rails and a customer tree you manage. If you run one business with its own assistants, you can skip it entirely.

As a partner you get: customer accounts (sub-accounts you create and manage), wholesale rates (your per-customer pricing), payment routing (your customers pay you directly via Stripe Connect), and branding (your logo and colours on customer-facing surfaces, via the manage_branding permission in the dashboard).

Naming transition — merchantIdaccountId. We are renaming "merchant" to "account" across the product. The API is fully backward-compatible during the transition:

  • Requests: send either accountId (preferred) or merchantId (legacy) — they are interchangeable. If you send both, merchantId wins.
  • Responses: requests may send either field (input aliasing is applied everywhere), and some billing responses already echo an accountId alongside merchantId. Full response-side aliasing is still rolling out, so for now read merchantId and treat accountId as an additive alias where present. merchantId is not being removed yet, so existing integrations keep working unchanged.
  • Routes and the hierarchy semantics below are unchanged (/api/v1/merchants/...). accountId is the same colon-separated value, just a new name for the field.

Action: new integrations should read/write accountId. merchantId is deprecated and will be retired in a future major version (we'll give notice first).

Every app belongs to an account; pool balances, account-level credentials and Stripe Connect linkage all live on the account; access tokens carry account scope to gate cross-customer visibility.

Account IDs are colon-separated strings forming a hierarchy:

The parent is everything before the last colon. An account with id acme:harbour-hotel has parent acme.

Scope inheritance: a token scoped to acme can see acme and everything beneath (acme:*). It cannot see siblings or ancestors. This is enforced server-side on every endpoint; you don't have to filter — the platform does.

text
acme                          ← your partner account
acme:harbour-hotel            ← a customer account you created
acme:harbour-hotel:bath       ← optional deeper nesting

Requires show_partner_cta (an opt-in permission — the same one that surfaces the "Become a partner" button in the dashboard). Idempotent. Grants the caller the partner bundle (+manage_branding, +manage_users, +set_wholesale_rate, +partner_principal) and ensures a partner account exists for them. After this fires, the caller can begin creating customer accounts.

No body fields required beyond displayName for branding purposes.

POST/api/v1/dashboard/become-partnershow_partner_cta
curl -X POST https://api.everycallhandled.com/api/v1/dashboard/become-partner \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "displayName": "Acme Answering Co"
  }'
Sign in to run this against your account.

Requires manage_users on a token scoped at or above the new account's parent.

This call creates the account shell only. The first owner (via POST /api/v1/users), the plan/tier, and any per-customer wholesale rate are set with their own calls afterwards — no financial fields are seeded from this request.

Body fields:

Field Required Notes
merchantId yes Colon-separated hierarchy (lowercase alphanumeric segments, hyphens allowed inside a segment). accountId accepted as an alias. Any missing ancestor is created automatically.
displayName yes Human-readable name shown in branding contexts.

Response (200):

Errors (messages, not symbolic codes — see Error Handling):

  • 400merchantId required, or Invalid merchantId format... (spaces, leading/trailing colons, etc.).
  • 403Forbidden: target merchantId is outside your scope.
  • 409Merchant {id} already exists.
  • 409 ALREADY_EXISTS — the id is taken (this is global — even across partners).
POST/api/v1/merchantsmanage_users
curl -X POST https://api.everycallhandled.com/api/v1/merchants \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "merchantId": "acme:harbour-hotel",
    "displayName": "Harbour Hotel"
  }'
Sign in to run this against your account.
JSON
{
  "success": true,
  "id": "...",
  "merchantId": "acme:harbour-hotel",
  "displayName": "Harbour Hotel",
  "parentMerchantId": "acme"
}

Requires manage_users. Returns accounts visible to your scope: your own account and all descendants.

Response (200):

creditBalance is included per row, so rendering a partner's customer tree needs no extra lookups.

GET/api/v1/merchantsmanage_users
curl https://api.everycallhandled.com/api/v1/merchants \
  -H 'Authorization: Bearer YOUR_TOKEN'
Sign in to run this against your account.
JSON
{
  "merchants": [
    {
      "merchantId": "acme",
      "displayName": "Acme Answering Co",
      "creditBalance": 125000,
      "tier": "enterprise",
      "stripeConnectStatus": "active",
      "subMerchantCount": 14
    },
    {
      "merchantId": "acme:harbour-hotel",
      "displayName": "Harbour Hotel",
      "parentMerchantId": "acme",
      "creditBalance": 3200,
      "tier": "starter",
      "stripeConnectStatus": "none",
      "subMerchantCount": 0
    }
  ]
}

Everything else a partner needs is the same API, scoped per customer:

  • Team access — invite your customer's staff with POST /api/v1/users, scoped to their account (Your Team).
  • UsageGET /api/v1/merchants/{merchantId}/ledger and /ledger/summary per customer (Billing & Credits).
  • Credentials — account-level credentials per customer (Credentials).
  • Spend control — per-app caps to bound each customer's spend (Billing & Credits).

If your customers should pay you directly (not us), Stripe Connect is the topology you want: your customers see a checkout branded as your business; Stripe pays you directly; the platform takes an application fee per transaction. If you prepay us and bill your customers through your own rails, you don't need this section.

Two account types

Express (recommended for most partners) — we create a Stripe-managed account in your name. Stripe handles identity checks, payouts, dispute flow and customer support. You provide branding (logo, primary colour, business details) once at onboarding and Stripe uses it on every customer-facing surface (checkout, receipt emails, billing portal). You can't customise the Stripe-side checkout further — it's a managed product.

Standard — you connect an existing Stripe account you already own. You retain full control of branding, checkout customisation, dispute handling and payout schedule. Suitable for partners with significant existing Stripe usage who want continuity with their other Stripe-billed products.

Express is the default we recommend; Standard is the escape hatch for sophisticated existing Stripe users.

Onboarding

Onboarding starts from the Billing page of your dashboard. For Express, the platform creates your Stripe account and hands your browser to Stripe's hosted identity-and-payout setup; for Standard, you're sent through Stripe's connect-account authorisation for the account you already own. Either way you land back on your dashboard with a success or failure status, and your account then carries:

Read these via GET /api/v1/billing/merchant to know your onboarding state.

Onboarding state machine

active is the only state in which Connect-routed checkouts can be created. restricted blocks new charges (existing charges still settle). rejected is terminal for that account. Poll GET /api/v1/billing/merchant to read the current state, or watch for state transitions on your own Stripe webhook.

Checkout routing

Once your status is active, your customers' payments route through your Stripe account automatically. Credit checkouts (Buy Credits, and cap top-ups initiated for an end customer via POST /api/v1/apps/{appId}/cap-topup) are created on your Connect account with an application fee: Stripe charges the customer, settles to you, and transfers the fee to the platform. We never touch your customer's card details or settle their funds.

The routing decision is made server-side from the customer's account chain: the platform walks up to the nearest ancestor account with stripeConnectStatus === 'active'; if one is found, the checkout routes through that ancestor's Connect account; otherwise it routes to the platform. The application fee is configured per-account at onboarding (default partner rate; bespoke agreements override) and is visible in your dashboard's Billing → Connect page.

Subscriptions

When a customer of yours pays a monthly subscription through Connect, the platform detects each renewal and allocates the monthly credit bundle to that customer's pool automatically — you don't wire any invoice handling. You DO need to listen for failed-renewal events on your own side (so you can chase your customer for payment): Stripe sends invoice.payment_failed to your registered Connect webhook endpoint when that happens.

Disputes

When a Stripe dispute opens against a Connect-routed charge, the platform records it and surfaces it read-only in your dashboard's Billing page. There is no dispute-management API today — disputes are handled in your Stripe dashboard (Express or Standard). The platform's role is awareness and audit, not adjudication.

JSON
{
  "stripeConnectAccountId": "acct_1Abc...",
  "stripeConnectAccountType": "express",
  "stripeConnectStatus": "onboarding"
}
text
none → onboarding        (you click Connect)
onboarding → active      (Stripe completes checks + payout setup)
onboarding → restricted  (Stripe needs more info; tasks waiting in your Stripe dashboard)
onboarding → rejected    (checks failed; Connect unavailable on this account)
active → restricted      (compliance event; Stripe pauses payouts)