Skip to main content
You get two firms, not one, and conflating them is the largest single source of doc-caused tickets we have. Read this section before you write your first account-scoped call.
API reference: Accounts. Opens on the public documentation site in a new tab. Where it disagrees with this page, this page is authoritative for the partner surface.

Your two firms

If you are an IB, this differs. You get three identities, not two: the participant firm, the clearing-member firm, and an IB anchor DCO account, which is not the clearing-member firm. Record and label all three. Using a DCO clearing-member ID where a DCM firm name belongs stalls onboarding with no error returned to you. The physical-person legal-name rule also bites at the moment you record these strings — see Naming and record requirements.
The two strings differ only in the suffix: -api-participant versus -api-clearing-member. We give you both strings at your credential gate — record both, and do not construct either. What the leading digits mean is not published; do not read a date, an ordering or anything else out of them.
Legacy examples show three different prefix shapes — firms/ISV-Participant-YourFirmID/..., firms/ISV-YourFirmID/... and firms/ISV-Example-ClearingMember/... — with no explanation. None of them is the real shape. Use the two strings we gave you, verbatim.

whoami returns the wrong firm for this purpose

GET /v1/whoami resolves your clearing-member firm (ep3_account_firm_name), not your participant firm (ep3_participant_firm_name). It is the right call for “which firm is this token”, and the wrong basis for addressing a user. Concretely, what happens when you build a participant ID from the whoami firm and send it:
  1. The request fails validateParticipantFirmOwnership.
  2. It raises CROSS_ISV_PARTICIPANT_IMPERSONATION_ATTEMPT — a CRITICAL security log entry.
  3. It increments a cross-ISV access metric, against your firm, as a legitimate partner.
Three legacy pages instruct exactly this. If you followed one of them, tell us in your shared Slack channel so the alerts on your account can be attributed correctly.

Where a participant ID actually comes from

The authoritative sources are two, and both come from KYC:
  • participantId on the kyc.approved webhook, or
  • participantId from GET /v1/kyc/status after a decision of ACCEPT.
Record it against your own user row at onboarding. It is the value you will send as x-participant-id for the life of that user.
participantId can come back empty on an ACCEPT while provisioning finishes. Observed gaps run from ~500 ms up to 26 minutes. Wait for the kyc.approved webhook before you enable trading for that user; do not poll kyc/status once and treat an empty participantId as a failure.
GET /v1/users does not solve this. It is a roster read: it is account-scoped and requires x-participant-id itself, so it cannot be used to discover your first participant ID. There is no bootstrap listing.

One SSN is one account, platform-wide

A trading account ID is derived from the end user’s SSN, and one SSN maps to exactly one exchange account across the entire platform — the same rule the Polymarket mobile app enforces.
Never share an SSN across test users. Reusing a test SSN points every one of those users at a single account. Transfers then fail with NOT_FOUND, and unpicking it requires manual database cleanup by us. This caused a 66-reply outage thread.There is also no re-KYC, reset or PII-update path: a user whose SSN is already in use cannot be re-KYC’d by you.
Give every preprod test user a distinct SSN from your own fixture set, and record which user holds which.

provisionedAccount is opaque

provisionedAccount is the fully-qualified DCM account name. Its account identifier is opaque and cannot be derived from participant or user data. Store the string we return; never assemble it. Legacy examples showing firms/.../accounts/user-123-trading teach the wrong model — they imply the account segment is your user ID, and it is not.
GET /v1/accounts?user=<participant> returns invalid user for the same participant string that works in x-participant-id.There is no published alternative. Capture provisionedAccount at onboarding, because you may not be able to look it up later.

Where x-participant-id goes

Whether /v1/report/trades/stats needs the header is unresolved.The legacy troubleshooting page lists it under endpoints that do not require x-participant-id, and a partner hit PERMISSION_DENIED on exactly that call. Do not build a retry that flips the header blindly; if you hit it, report the verbatim error and we will settle it.

An account-scoped read, end to end

Each snippet imports the canonical token client from Authentication.
The response shape of GET /v1/users is not yet published here.Log the raw body on your first call and branch only on fields you have seen.
For gRPC, the same value goes in metadata alongside authorization. Note the field-naming split you will hit across surfaces: KYC requests are snake_case, REST responses are camelCase, and webhooks are snake_case.

What can go wrong

Next

Limits and errors