Skip to main content
Every request to the ParlayX API is signed. There are no bearer tokens, no shared secrets in transit, and no way to assert an identity in a request body. Identity comes from the key that signed the request, and from nothing else. A request cannot claim to be a trader it is not, or reach a pod its key was not created for.

What a key is

An API key belongs to one trader in one pod. That pairing is fixed at the moment the key is created and cannot be changed afterward. This has a consequence worth internalizing before you write any code: the key determines scope, so scope never appears in the request. You do not pass a pod ID. You do not pass a trader ID. The API already knows both, because they are properties of the key that signed the call. A trader trading two pods holds two keys and chooses which one to sign with.

Key material

Keys are Ed25519 keypairs, generated in the trader’s browser when they create a key on the trading platform. The public key is registered with ParlayX; the private key is shown once, never leaves your systems, and we never see it.
The private key is the credential. Anyone holding it can trade the pod it is bound to, up to that pod’s balance. Store it the way you would store a venue API secret: in a secrets manager, never in source control, never in a config file that ships with your application.
If a key is exposed, revoke it. Revocation takes effect at the front door and is reversible. See Traders and permissions.

Signing a request

Every request carries four headers, and the calls that take an idempotency key carry a fifth (GET /v1/time is the one exception and carries no PX-Timestamp): The sequence:
1

Hash the body

Compute the SHA-256 of the request body and put it in PX-Content-Sha256. For a request with no body, hash the empty string.
2

Build the canonical string

Join these five lines with a newline, with no trailing newline:
If the request carries an Idempotency-Key header, append its value, trimmed, as a sixth line. A request without that header stops at five.An order submission with Idempotency-Key: order-2f9c1a and no query string signs six lines, the third of them empty:
The same call without that header would stop after the body hash.Build it byte for byte, since any deviation produces a signature that will not verify.
3

Sign it

Sign the canonical string with your private key and put the result in PX-Signature.
4

Send

Include the four PX- headers with the request (three for GET /v1/time, which has no PX-Timestamp).
The SDK does this for you. Sign manually only if you are working in a language the SDK does not cover.
Signatures built from the five lines alone are still accepted on requests that carry an Idempotency-Key, so an existing signer keeps working. If you sign manually, add the sixth line now. The SDKs still sign five lines today; the release that includes the key will be announced in the changelog, as will the date the five-line form stops being accepted.

Signing the time endpoint

GET /v1/time is the one exception to the timestamp. It exists so a client with a skewed clock can measure the offset, so it neither requires nor checks PX-Timestamp. Sign it with the timestamp line left empty; the other lines are built as usual, and the other three PX- headers are still required. A signature that puts a timestamp on that line does not verify and is rejected with 401.

What gets checked

Two stages, in order. Everything fails closed, so an ambiguous result is a rejection, never an approval. At the front door, before your request body is read:
  1. All four PX- headers are present (PX-Timestamp is not required on GET /v1/time)
  2. The timestamp is fresh (not checked on GET /v1/time)
  3. The key ID is well formed
  4. The key exists, and its trader and pod are loaded
  5. The signature verifies against the stored public key
  6. The body hash matches what was signed
Signature verification happens before the body is parsed. A request that does not verify is rejected without its contents ever being interpreted. Then the guard, which asks four questions in order: The three revocation checks return the same code deliberately: distinguishing them would tell an unauthenticated caller which key ids and traders exist. Which check failed is recorded on our side, so support can tell you. Three separate revocation levers, each independent. You can revoke a single key while leaving the trader on the pod, remove the trader from one pod while leaving their other pods working, or remove the trader from the organization. Pass all four and the request proceeds with a verified identity: organization, pod, trader, and key.

Timestamps

Requests carry a timestamp and are only accepted inside a narrow window, which prevents a captured request from being replayed later. GET /v1/time is the exception: it signs no timestamp, so it has no replay window, and it only reads the server clock. PX-Timestamp is in Unix seconds, not milliseconds. A request is accepted when its timestamp is within 30 seconds of the server clock in either direction, and a difference of exactly 30 seconds is still accepted. A timestamp further in the past or the future, or one that is not a number, is rejected with the same 401 UNAUTHORIZED as a signature that does not verify, so the response alone does not tell you which of the two happened. Keep your system clock synchronized. Clock drift on your side presents as authentication failures that look intermittent and are not. To correct for skew, call GET /v1/time. It works however far off your clock is, because it signs no timestamp (see Signing the time endpoint). It returns the server time as time, in Unix seconds. Subtract your own clock’s reading from it to get an offset, and add that offset to the timestamp of each request you sign. If requests from a signer that has worked before start failing with 401, measure the offset again before you debug the signature.

Scope is carried by the key

Because the key pins one trader and one pod, paths on the signed API never name a pod or an organization. There is no /pods/{podId}/orders on the public surface. There is /orders, and it means your orders, in your pod. A signed key can only ever address its own scope. This is structural, not a permission check that could be misconfigured. There is no way to express a request for someone else’s pod, so there is no way to accidentally grant one. Organization-wide and pod-wide oversight lives in the Platform, which authenticates differently because an administrator legitimately spans many pods.

Confirming what a key is

whoami returns the identity behind the key that called it:
Useful in three situations: verifying your signing implementation before you build anything on top of it, confirming which pod a key is bound to when you hold several, and checking that a key is still live. credentialId is the opaque identifier of the credential this key belongs to. capabilities echoes the key’s permission: ["trade"] for a trading key, ["data"] for a data key. scope and capabilities exist so that pod-scoped and organization-scoped keys can be introduced without changing the response shape, so read them rather than assuming their values.

What a key can do

The rule for a data key is the path: it calls discovery, the market lookup and the stream, and nothing under /v1/{venue}/. Every venue endpoint answers it with 403 FORBIDDEN, whether the endpoint places an order or only reads one. Discovery already resolves Polymarket token ids and Kalshi tickers, so this is the surface a data integration needs to find what to stream. Placing, tracking, and cancelling an order are one responsibility under one grant. There is no configuration in which a trader can cancel an order it cannot see, or see an order it did not place. Capital movement is not an API capability at all. Deposits, withdrawals, and transfers between pods are administrative actions. See Deposit and Transfer.

Rotation

Rotating issues a new key and retires the old one, with the same trader, the same pod, the same access, and a new credential. Rotate on a schedule, when someone with access to the key leaves, and immediately if exposure is suspected. Nothing about the trader, their pod, or their open positions is affected.

Authentication failures

Every 401 cause above returns the same UNAUTHORIZED code on purpose. A code that distinguished “no such key” from “wrong signature” would let an unauthenticated caller enumerate valid key ids, so the ladder collapses to one answer. That is why the fix column lists several things to check rather than one.
Signature failures are almost always the canonical string or the body hash. Call whoami first, since it is the smallest signed request you can make, and if it succeeds your signing is correct and the problem is in how you built that specific request.