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 —
merchantId→accountId. We are renaming "merchant" to "account" across the product. The API is fully backward-compatible during the transition:
- Requests: send either
accountId(preferred) ormerchantId(legacy) — they are interchangeable. If you send both,merchantIdwins.- Responses: requests may send either field (input aliasing is applied everywhere), and some billing responses already echo an
accountIdalongsidemerchantId. Full response-side aliasing is still rolling out, so for now readmerchantIdand treataccountIdas an additive alias where present.merchantIdis not being removed yet, so existing integrations keep working unchanged.- Routes and the hierarchy semantics below are unchanged (
/api/v1/merchants/...).accountIdis the same colon-separated value, just a new name for the field.Action: new integrations should read/write
accountId.merchantIdis 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.
acme ← your partner account
acme:harbour-hotel ← a customer account you created
acme:harbour-hotel:bath ← optional deeper nestingRequires 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.
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"
}'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):
400—merchantId required, orInvalid merchantId format...(spaces, leading/trailing colons, etc.).403—Forbidden: target merchantId is outside your scope.409—Merchant {id} already exists.409 ALREADY_EXISTS— the id is taken (this is global — even across partners).
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"
}'{
"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.
curl https://api.everycallhandled.com/api/v1/merchants \
-H 'Authorization: Bearer YOUR_TOKEN'{
"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). - Usage —
GET /api/v1/merchants/{merchantId}/ledgerand/ledger/summaryper 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.
{
"stripeConnectAccountId": "acct_1Abc...",
"stripeConnectAccountType": "express",
"stripeConnectStatus": "onboarding"
}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)