Skip to main content

Partner Onboarding

Use this guide when your platform (ClickFunnels, a marketplace, a page builder, etc.) creates PAI merchants on behalf of the businesses that use your product. It covers your merchant's own API key, POST /v1/public-api/merchants, key lifecycle for the sub-merchants you create, how owner assignment works, how to tell when a seller claims a merchant, and what you can (and cannot) do for KYC today.

The create, read, and assign operations live in the API Reference under Merchant (with API key lifecycle under API Keys). Catalog and checkout routes stay under Products and Checkout.

There is no separate "platform" credential. Your own Merchant API Key onboards sub-merchants — any merchant without a parent of its own may call this endpoint. A merchant that already has a parent cannot itself become a parent (one level of nesting only — see The one-level cap).

Full data-plane authority​

Your Merchant API Key has the same access to every sub-merchant it creates as that sub-merchant's own key. It is not read-only and it is not limited to onboarding — it can create products, run checkouts, issue refunds, and mint or revoke API keys for any of your sub-merchants, exactly as if it were that sub-merchant's own key. Treat it accordingly: encrypted at rest, redacted from logs, rotated on a schedule, revoked immediately if you suspect exposure. Revoking your key cascades to every key it minted over the Public API — see Key lifecycle below.

To also receive webhooks for activity on sub-merchants you created, configure endpoints on your parent merchant in Developer Tools → Webhooks and enable Receive sub-merchant events on each listener. Event types still follow each endpoint's subscription list. Details: Webhooks.

1. Prerequisites​

  • Your own Merchant API Key (pai_test_* in sandbox, pai_live_* in live) — the same key you use for /v1/public-api/merchants/{merchantId}/products, checkouts, etc. There is no separate platform-scoped credential.
  • Keep sandbox and live keys strictly separated — a pai_test_* key is rejected in live deployments and vice-versa.

2. Create a sub-merchant​

POST /v1/public-api/merchants
Authorization: Bearer pai_live_...
Content-Type: application/json

{
"name": "Acme Coaching",
"email": "founder@acmecoaching.com",
"phone": "+14155552671",
"merchantId": "01930000-0000-7000-8000-0000000000ab",
"websiteUrl": "https://acmecoaching.com"
}
  • name — business name; PAI appends 's Merchant internally.
  • email — the future owner's email. See Assign an owner.
  • phone — optional E.164 phone (^\+[1-9]\d{1,14}$). Dashboard KYC collects it later if you omit it.
  • merchantId — optional client-supplied UUID v7. Supply your own so a network retry cannot double-provision — re-posting the same id with the same details returns created: false idempotently.
  • websiteUrl — optional HTTPS URL (max 255 characters) for the sub-merchant's storefront or funnel. Checkout returnUrl hosts must exactly match the hostname in this URL (https://shop.example.com and https://www.shop.example.com are different hosts; subdomains and additional funnel domains are rejected). http:// is rejected. Sellers on platforms with multiple custom domains may need more than one allowed host — that is not supported today.

The new merchant is parented to you — the merchant bound to the API key making the call. There is no parentId field in the body: parentage is always derived from the calling key, never from anything the client sends.

Response (201 Created)​

{
"merchantId": "01930000-0000-7000-8000-0000000000ab",
"created": true,
"ownerAttached": false,
"joinUrl": "https://app.payments.ai/join#token=<opaque>&merchantId=01930000-0000-7000-8000-0000000000ab",
"joinUrlExpiresAt": "2026-09-25T15:04:05Z"
}
  • created — true on first success, false on an idempotent replay.
  • ownerAttached — whether create assigned an owner. See Assign an owner.
  • joinUrl — owner-claim deep link. Present only when ownerAttached is false. Deliver it to the seller through your own channel; PAI does not email the seller. 7-day TTL.
  • joinUrlExpiresAt — ISO-8601 timestamp when the link expires. Present with joinUrl.

Errors​

StatusCause
400 Bad RequestMissing or malformed body — the response errors array names the Zod issue.
400 Bad RequestEmail cannot receive mail (code: email_undeliverable); provide a different address. See Email cannot receive mail.
401 UnauthorizedMissing Authorization, unknown key, or key environment mismatched with the deployment.
404 Not FoundShould not occur on this route in practice — the caller is always its own parent.
409 ConflictReplaying merchantId with different name, email, phone, or websiteUrl — or your merchant already has a parent of its own (see The one-level cap).

Update the website URL​

After create, set or change the sub-merchant's website URL with a scoped PATCH. Use the same Merchant API Key you use for other sub-merchant routes; :merchantId must be your own merchant id or a sub-merchant you created.

PATCH /v1/public-api/merchants/{merchantId}
Authorization: Bearer pai_live_...
Content-Type: application/json

{
"websiteUrl": "https://acmecoaching.com"
}
{
"websiteUrl": "https://acmecoaching.com"
}

Only https:// URLs are accepted (max 255 characters). Clearing the URL is not supported on this route — use the seller dashboard if you need to remove it. On idempotent create replay, websiteUrl is applied only when the stored URL is still empty; a replay with a different non-empty URL returns 409 Conflict like a name or email mismatch.

PATCH errors​

StatusCause
400 Bad RequestMissing or malformed body — the response errors array names the Zod issue (for example non-HTTPS URL).
401 UnauthorizedMissing Authorization, unknown key, or key environment mismatched with the deployment.
403 Forbidden:merchantId is outside your key's allowed set (another partner's merchant, your parent, a sibling sub-merchant, or a missing / soft-deleted merchant).

3. Key lifecycle for your sub-merchants​

Mint, list, and revoke API keys for any sub-merchant you created using the ordinary merchant-scoped key routes — there is no separate hierarchy-shaped path. :merchantId below can be your own merchant id or any active sub-merchant's id; both work identically.

POST /v1/public-api/merchants/{merchantId}/api-keys
Authorization: Bearer pai_live_...
Content-Type: application/json

{
"name": "Acme Coaching storefront key",
"environment": "live"
}
{
"key": "pai_live_...",
"maskedKey": "pai_live_••••ab12",
"id": "01930000-0000-7000-8000-0000000000cd",
"createdAt": "2026-09-15T12:00:00.000Z"
}

The plaintext key is returned exactly once — store it immediately; PAI only ever persists its hash. GET /v1/public-api/merchants/{merchantId}/api-keys lists a sub-merchant's keys (masked); DELETE /v1/public-api/merchants/{merchantId}/api-keys/{id} revokes one.

Revoking a key you minted cascades: every key that key itself minted over the Public API is revoked in the same transaction. Revoking your own top-level key therefore revokes every key you minted for every sub-merchant — a fast, complete way to contain a leaked credential. Keys minted some other way (e.g. from the sub-merchant's own dashboard) are unaffected.

4. The one-level cap​

Nesting is exactly one level deep: a merchant with a parent can never itself become a parent. Calling POST /v1/public-api/merchants with a sub-merchant's own key returns 409 Conflict — onboard sub-merchants only with a top-level merchant's key.

5. Assign team members (including owners)​

You can put someone on a sub-merchant's dashboard team in three ways:

  1. At create — POST /v1/public-api/merchants with an email that already matches a Payments AI user sets ownerAttached: true and assigns them owner on that new merchant.
  2. Join link — when ownerAttached is false, deliver the joinUrl from the create response so the seller can sign up (or in) and claim the merchant.
  3. After create — POST /v1/public-api/merchants/{merchantId}/members copies an active member of your merchant team onto a sub-merchant you manage (any role: owner, admin, editor, or viewer).

There is no invite email and no accept step on the assign route — the user must already be on your team.

POST /v1/public-api/merchants/{merchantId}/members
Authorization: Bearer pai_live_...
Content-Type: application/json

{
"email": "operator@yourplatform.com",
"role": "owner"
}

{merchantId} must be a sub-merchant your key can access (an active child of the merchant that owns the key). Use your parent merchant's API key — a child merchant's key cannot target the parent. The path id must not be the same merchant that owns the API key: the email must already be active on that parent team, so using the parent id as {merchantId} always returns 409 with code team.already_member. The email must belong to someone who is already an active member of the merchant that owns the API key — not merely registered in Payments AI, and not only on another merchant's team. Unknown and out-of-org emails return the same 404 with code user.not_found (see User not found).

Create-time ownerAttached​

ownerAttachedWhat it meansWhat you should do
trueThat user is now owner of this merchant and can sign into the dashboard.Continue to KYC and catalog setup.
falseNo matching user yet. The response includes joinUrl — deliver it to the seller.Do not treat create as finished until claimed, or add the person to your team and call POST .../members.

Owner-claim join URL​

  • You deliver it. PAI does not email the seller — the link goes out through whichever channel your platform uses to notify sellers (email, dashboard, SMS, etc.).
  • 7-day TTL. After that the link is dead; the seller sees a "join link expired" screen.
  • Remint the link with POST /v1/public-api/merchants/{merchantId}/join-link (see below). Reminting invalidates the previous token.
  • One shot. Once a seller has claimed the merchant, both create (idempotent replay) and the remint endpoint return 409 merchant.owner_already_attached.

Use the same Merchant API Key that can access the sub-merchant (:merchantId in the path):

POST /v1/public-api/merchants/{merchantId}/join-link
Authorization: Bearer pai_live_...

Body: none. Response 201:

{
"joinUrl": "https://app.payments.ai/join#token=<opaque>&merchantId=01930000-0000-7000-8000-0000000000ab",
"joinUrlExpiresAt": "2026-09-25T15:04:05Z"
}

409 once the merchant has an owner; 403 if the merchant does not exist or your key cannot access it — the two cases return the same 403 by design, so a key cannot probe for merchant ids outside its scope.

Know when the seller claims the merchant​

Do not remint the link to check whether the seller claimed the merchant. On an unclaimed merchant, remint issues a new link and invalidates the one you already delivered. Use one or both of these signals instead.

Read it. Call GET /v1/public-api/merchants/{merchantId} with the same key you used for create:

GET /v1/public-api/merchants/{merchantId}
Authorization: Bearer pai_live_...

Response 200:

{
"merchantId": "01930000-0000-7000-8000-0000000000ab",
"ownerAttached": true,
"activation": {
"status": "pending",
"identityVerification": "not_started"
}
}
  • ownerAttached — true once the merchant has an active owner, however it got one: attached at create, claimed through joinUrl, or assigned with POST .../members.
  • activation.status — go-live progress: not_provisioned, pending, verification_pending, or active.
  • activation.identityVerification — the owner's identity verification (KYC) state: not_started, pending, in_review, verified, or rejected. See Identity verification (KYC).

403 if the merchant does not exist or your key cannot access it, the same as remint.

Get told. Subscribe an endpoint on your parent merchant to merchant.owner_attached and enable Receive sub-merchant events on it. We send the event when the seller claims the merchant through joinUrl. The body carries merchantId, ownerUserId, and email. Create-time attach and POST .../members do not send it — their responses already tell you. Each delivery is attempted once, so keep reading ownerAttached if your endpoint may miss one. See Webhooks.

Make attachment succeed at create​

  1. Have the future owner sign up in Payments AI first (or confirm they already have an account).
  2. Call POST /v1/public-api/merchants with that same email.
  3. Assert ownerAttached === true before you continue when you rely on create-time attach.

What does not attach someone​

  • Signing up after create does not claim an existing merchant by email (unless the seller uses joinUrl).
  • Assign member cannot add someone who was never on your merchant team — invite them to your platform merchant in the dashboard first, then assign them to the sub-merchant.
  • Dashboard team invite on an ownerless sub-merchant still requires an existing Owner or Admin on that merchant — use assign member, joinUrl, or Support instead.

Dashboard merchant context​

After assign, the user can sign in, but the dashboard still opens the first merchant they belong to (there is no merchant switcher yet). If they belong to multiple merchants, they may need to use the sub-merchant's URL context or sign out and back in depending on your flow — plan for multiple merchants per user limitations until switcher support ships.

6. Identity verification (KYC)​

Sellers must complete identity verification before live payouts. The verification flow itself is dashboard-only today. You cannot start it over the Merchant Public API, and its routes are not in this API Reference. You can follow its progress: read activation.identityVerification from GET /v1/public-api/merchants/{merchantId}.

After ownerAttached is true, the owner:

  1. Signs into the Payments AI dashboard.
  2. Opens onboarding / identity verification (/onboarding/kyc) for that merchant.
  3. Completes identity verification. Status questions after submit go to Support.

KYC also collects phone if you omitted it on create.

Not available yet (do not call these)​

Partners cannot yet:

  • Mint a verification link to send to the seller
  • Subscribe to a merchant.kyc.* webhook

Those partner APIs are in progress. Until they ship, do not call POST /v1/public-api/merchants/{merchantId}/kyc — that route does not exist (404). Dashboard KYC is GET /v1/merchants/{merchantId}/kyc and POST /v1/merchants/{merchantId}/kyc/session and requires the owner's Cognito session, not a Merchant API Key.

7. Next steps​

Once the owner is attached and KYC can run in the dashboard:

  1. Have the owner mint a merchant-scoped API key (Developer Tools → API Keys) for catalog and checkout.
  2. Use that key with the same merchantId on /v1/public-api/merchants/{merchantId}/products and the rest of the Merchant Public API.
  3. Your own key also reaches those routes for any sub-merchant you created — see Full data-plane authority.

FAQ​

Does PAI email the seller the join link? No. Deliver joinUrl through your own channel — email, dashboard, SMS, wherever you already talk to the seller. This keeps the notification voice yours and avoids double-messaging.

What if the seller loses the link? Call POST /v1/public-api/merchants/{merchantId}/join-link to remint. The previous token stops working; the new one is valid for another 7 days.

How do I know the seller claimed the merchant? Read ownerAttached from GET /v1/public-api/merchants/{merchantId}, or subscribe to merchant.owner_attached. Do not remint the link to find out — that invalidates the link you delivered. See Know when the seller claims the merchant.

Do I need to record consent flags? No. Consent for ToS and privacy policy is set to true on your behalf when you call this endpoint — you have accepted PAI's terms on the sub-merchant's behalf. The audit log captures the implied source so it is auditable.

Can I assign an owner later via API? Yes, with limits. Deliver the joinUrl from create or remint it with POST /v1/public-api/merchants/{merchantId}/join-link, or use POST /v1/public-api/merchants/{merchantId}/members to copy someone who is already an active member of your merchant team onto a sub-merchant (role can be owner). You cannot attach a brand-new seller who was never on your team without joinUrl — invite them to your merchant first, then assign, or use the join link. See Assign team members.

Can I start or poll KYC with my API key? You can poll it: read activation.identityVerification from GET /v1/public-api/merchants/{merchantId}. You cannot start it — the owner completes identity verification in the dashboard. See Identity verification (KYC).

Can I read or update a sub-merchant's data with my own key? Yes — see Full data-plane authority. Your key has the same access as the sub-merchant's own key across every Public API resource, not just onboarding.

How do I get a key that can onboard sub-merchants? Any merchant without a parent of its own already can — there is no separate eligibility flag or approval step. Use your existing Merchant API Key, or mint a fresh one from your dashboard.