Authentication
How to authenticate to the MyStocks Partner API — API keys via Authorization or x-api-key header, scoped read-only data keys for client-side use, Firebase-token endpoints, and the mandatory Idempotency-Key for money-movement calls.
Every request requires your API key — except four partner-portal endpoints (POST /register,
GET /me, POST /upgrade-request, and POST /api-keys/revoke), which authenticate with a
Firebase ID token from your portal session instead (see Firebase-token endpoints).
Send the API key using either method:
Option A — Authorization header (recommended)
Option B — Custom header
Never expose a full key (pk_live_) in client-side code or public repositories. Use a data key
(pk_data_) for any browser or mobile context — see below. Sandbox keys carry no financial risk;
production keys control real funds — guard them accordingly.
Data keys — scoped read-only credentials
A pk_data_ key is a scoped, read-only credential derived from your full key. It cannot trade, move
funds, or access sub-account PII, so exposing one carries no financial risk — suitable for low-stakes
surfaces like public widgets. For production mobile or web apps, prefer proxying market data through
your backend or minting short-lived tokens via POST /oauth/token: an embedded key can be extracted
by anyone, it shares your full key's rate-limit bucket (an abuser can exhaust your quota), and since
only one data key is active per partner, rotating it breaks every shipped install at once.
Generate a data key via POST /api-keys/data-key (see Key Management).
Data keys share the rate-limit bucket of their parent full key. Any call outside the allowed list
returns 403 FORBIDDEN.
Allowed endpoints (GET only): /stocks, /stocks/{symbol}, /stocks/{symbol}/chart,
/stocks/{symbol}/history, /stocks/{symbol}/pulse, /etfs, /etfs/{symbol}, /etfs/{symbol}/chart,
/etfs/{symbol}/history, /market/quotes, /market/status, /market/movers, /market/holidays,
/market/settlement, /bonds, /bonds/{id}, /funds, /funds/{id}, /companies,
/companies/{symbol}, /companies/{symbol}/chart, /companies/{symbol}/news, /companies/tickers,
/market-intel, /market-intel/{id}, /opportunities, /dividends/calendar.
Short-lived tokens — OAuth client credentials
For client-side surfaces where you don't want to embed a long-lived key, exchange your key for a
short-lived bearer token via POST /api/v1/partner/oauth/token — a standard OAuth 2.0
client-credentials grant. The token inherits your key's type (full or data) and scopes, expires in
15 minutes, and is prefixed ms_oauth_. This keeps a leaked credential useful for minutes rather
than indefinitely, and lets you rotate without breaking shipped installs.
This grant must be enabled for your partner account first (oauthClientCredentialsEnabled). Until it
is, the endpoint returns 403 — contact us to switch it on.
Send your client_secret (your API key) via HTTP Basic auth, or in a form / JSON body. An optional
scope narrows the token to a subset of your key's scopes:
Use the returned token exactly like a key: Authorization: Bearer ms_oauth_…. When it expires, request
a new one. The token endpoint is rate-limited per source IP to deter credential stuffing.
Firebase-token endpoints
Four endpoints authenticate with a Firebase ID token (Authorization: Bearer <firebase-id-token>)
obtained from the partner-portal login session rather than an API key: POST /register (issues and
rotates your key), GET /me (powers the dashboard UI), POST /upgrade-request (request production
access), and POST /api-keys/revoke (must remain reachable even after a key is compromised). All other
routes use your partner API key as normal.
Idempotency — safe retries for money-movement calls
On unstable mobile networks a POST can succeed on the server but time out on the client — causing a
double-charge if the app retries. Every money-movement and state-changing write — deposit,
withdraw, trade, topup, payout, KYC assertion, fund/bond subscribe &
redeem, and webhook creation — requires a unique Idempotency-Key header. Calls that
omit it are rejected with 400 MISSING_IDEMPOTENCY_KEY. We deduplicate by key for 24 hours and
return the cached response on retry.
Use any unique string — a UUID or your own transaction ID works well. If a concurrent duplicate is detected you receive HTTP 409 until the first request completes.
When is it safe to retry?
| Call type | Safe to retry? | How |
|---|---|---|
GET (all read endpoints) | Always | Retry freely with exponential backoff. Reads have no side effects. |
POST that moves money / places orders / changes account state | Only with Idempotency-Key | Required — returns 400 MISSING_IDEMPOTENCY_KEY without it. Resend the exact request with the same key to get the original response back, never a duplicate movement. |
POST /users (create sub-account) | Yes | The externalId is the natural idempotency key — an existing sub-account is returned unchanged. |
PATCH (settings, pricing, float) | Yes | Full-replace semantics — repeating the same request converges to the same state. |
DELETE (cancel order, remove webhook) | Yes | Repeats return 404/422 once the resource is gone or settled — treat that as success. |
On 429, wait for Retry-After before retrying (see Rate Limits). On
5xx for a money-movement call, always retry with the same Idempotency-Key — never generate a fresh
key for a retry of the same logical operation.