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. 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 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.
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: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).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:- All four
PX-headers are present (PX-Timestampis not required onGET /v1/time) - The timestamp is fresh (not checked on
GET /v1/time) - The key ID is well formed
- The key exists, and its trader and pod are loaded
- The signature verifies against the stored public key
- The body hash matches what was signed
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:
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.