Skip to content

Prop-Firm Authentication

Every /v1/partner/* request authenticates with a tenant API key — a server-to-server secret scoped to your firm. It is distinct from the user-facing ps_live_… keys:

User-facing keyTenant key
HolderAn individual trader / botYour backend
HeaderX-API-Key: ps_live_…X-API-Key: <tenant-key>
ScopeOne trader’s own accounts/tradesEvery account your tenant provisions
Surface/v1/* trading + market data/v1/partner/*

The key is backend-only — traders never use it. They authenticate to PolySimulator through the one-time login link you send them (login-link flow). A tenant key can provision accounts and read trade history for every trader you register — its blast radius is your whole tenant.


Send your tenant key in the X-API-Key header:

Terminal window
curl -H "X-API-Key: $POLYSIM_TENANT_KEY" \
https://api.polysimulator.com/v1/partner/users/12345

Authorization: Bearer <tenant-key> is accepted as an alias — the raw key after Bearer , not a JWT. X-API-Key wins if both are sent. A missing key returns 401 MISSING_API_KEY.


We store only a hash of your key, never the raw value. On each request the key is matched, your tenant must be active, rate limits are enforced, and the route checks the key carries the permission it requires. Deactivating or rotating a key takes effect within a few seconds.


Each key carries permission scopes. The default issued set is:

["users:write", "accounts:write", "accounts:read"]
PermissionGrants
users:writePOST /v1/partner/users, POST /v1/partner/users/{id}/login-link
accounts:writePOST /v1/partner/accounts, PATCH /v1/partner/accounts/{id}, POST /v1/partner/accounts/{id}/close, POST /v1/partner/accounts/{id}/reset
accounts:readGET /v1/partner/users/{id}, GET /v1/partner/accounts/{id}, GET /v1/partner/accounts/{id}/trades

A key missing the required scope returns 403 INSUFFICIENT_PERMISSION. Ask your contact for an accounts:read-only key to split a reconciliation/analytics service from your provisioning service.


Your tenant can hold several active keys at once, all scoped to the same tenant. Use this to separate environments (a leaked staging key can’t touch live accounts — though staging and production share the same backing data, see the staging note), or to split services by least privilege (a read-only reporting key can’t provision accounts).

Rotation is gapless: because multiple keys validate at once, ask us to issue a new key, deploy it, confirm traffic flows, then ask us to deactivate the old one — no cutover gap. Key issuance, rotation, and deactivation are contact-managed in v1; there is no self-serve key-management endpoint yet.


/v1/partner/* errors return a two-field envelope — a stable machine error code plus a human message. Branch on error and the HTTP status; never parse message. The same code is also returned in the X-Polysim-Code response header on error responses (success responses carry no such header).

{ "error": "INVALID_KEY", "message": "Invalid API key" }

The auth/authz codes for keys:

StatuserrorMeaning
401MISSING_API_KEYNo X-API-Key (and no Authorization: Bearer) sent
401INVALID_KEYKey doesn’t exist or was revoked
401KEY_DEACTIVATEDKey was administratively disabled
401KEY_EXPIREDKey is past its expiry
403TENANT_DISABLEDYour tenant has been disabled
403INSUFFICIENT_PERMISSIONThe key lacks the scope this route requires
404NOT_FOUNDThe partner surface is not enabled for your tenant yet

A 404 on a partner path you expect to exist means your tenant isn’t enabled — until then the entire /v1/partner/* surface returns 404, indistinguishable from a nonexistent route (no enumeration).

See Rate Limits & Errors for the full envelope, the contrast with the single-field GA surface, request-validation and rate-limit codes, and per-endpoint codes.