Authentication
How X3D API keys look, the two headers that carry them, why a lost key cannot be recovered, and how to rotate or revoke one from your profile.
Every call to the print API carries one credential: your key. It identifies the account, decides which orders you can read, and names the card that gets charged. There are no scopes, no OAuth flow and no refresh step — one long-lived secret, held on your server.
What a key looks like#
x3d_live_8f2b41c07d9e5a3610bc7f4d82e91a05c36bd7f2ae04915c- The prefix is always x3d_live_. A value that does not start with it is rejected before we look anything up.
- After the prefix are 48 lowercase hex characters — 24 random bytes — so a key is 57 characters long in total.
- Your profile shows a non-secret display prefix instead of the key, in the form x3d_live_ab12cd… That string identifies which key is active; it cannot be used to authenticate.
- There is no separate test prefix. A key that works works against the live farm.
Two headers are accepted#
Send the key either as a bearer token or in x-api-key. Authorization is read first: if it matches Bearer followed by a value, that value is used and x-api-key is ignored. If Authorization is absent or malformed, x-api-key is read instead. Whichever arrives is trimmed of surrounding whitespace before the lookup.
| Header | Example | Notes |
|---|---|---|
| Authorization | Authorization: Bearer x3d_live_8f2b… | The word Bearer is matched case-insensitively, and any amount of whitespace after it is fine. Preferred. |
| x-api-key | x-api-key: x3d_live_8f2b… | A fallback for clients that reserve Authorization for something else. Read only when the Authorization header did not match the Bearer pattern. |
curl https://x3dstudios.com/api/print/orders/X3D-K7M2QP \
-H "Authorization: Bearer $X3D_API_KEY"
curl https://x3dstudios.com/api/print/orders/X3D-K7M2QP \
-H "x-api-key: $X3D_API_KEY"Keys are stored hashed#
When a key is issued we keep three things: a SHA-256 hash of it, the display prefix, and the date. The key itself is returned in that one response and then discarded on our side. An incoming request is authenticated by hashing what you sent and looking for a matching account.
A card on file comes first#
/api/account/api-keySigned inIssue or rotate the key for the signed-in account. Returns the secret exactly once.
Key issuance checks for a saved card and refuses without one. This is not bureaucracy: an API order has no checkout step, so the card has to exist before the first order does. Add one from the same panel at /profile — Stripe collects it, we store a reference.
{
"error": "Add a card on file first — API print orders are billed automatically to your saved card.",
"code": "CARD_REQUIRED"
}Removing the card later does not disable the key. The key keeps authenticating, but submitted orders cannot be charged: they come back as 201 with paid: false, status PENDING_PAYMENT, and the message Auto-charge failed (No card on file).
Rotation and revocation#
| Action | Endpoint | Effect |
|---|---|---|
| Check | GET /api/account/api-key | Returns hasKey, the display prefix and createdAt. Never the secret. |
| Rotate | POST /api/account/api-key | Issues a new key and invalidates the previous one in the same write. No overlap window. |
| Revoke | DELETE /api/account/api-key | Clears the key. The account has no working key until you issue another. |
- Rotation is instant and has no grace period, so deploy the new key before you press the button, or expect a gap.
- Revoking a key does not cancel orders already submitted. Paid work stays paid and still prints.
- Revoking does not refund anything. To stop an order in flight, mail [email protected] with the order code.
The 401#
A missing, malformed, revoked or unknown key all produce the same 401 — we do not say which. The two print endpoints word it slightly differently, so match on the status code rather than the string.
{
"error": "Invalid or missing API key. Generate one on your X3D profile."
}{
"error": "Invalid or missing API key."
}A 404 Order not found from the status endpoint is a different thing entirely: the key was accepted, but that code either does not exist or belongs to another account. The two cases are deliberately indistinguishable so a valid key cannot be used to probe for other people's order codes.
Keep the key on a server#
- Server-side only. Never put the key in browser JavaScript, a mobile app bundle, a browser extension or anything else a user can open — a key in a client is a public key.
- Never commit it. Read it from an environment variable or a secret manager, and add the file that holds it to .gitignore before the first commit, not after.
- Do not paste it into a support ticket, a screenshot or an issue. Nobody at X3D will ever ask you for it.
- One key per account means one blast radius: if it leaks, rotate at /profile straight away — that is the fastest way to stop it being used.
- Every successful API order emails a confirmation to the order's email address, which is your account email unless the request overrode it. A confirmation you did not expect is your first warning.
- Call over HTTPS, and do not disable certificate verification to get a client working — that is exactly the shortcut that leaks the key.
Extension tokens are a different credential#
The X3D Chrome extension authenticates with its own token, which starts with x3d_ext_ and is minted by the pairing flow rather than by your profile. It is accepted by POST /api/print/orders — so an extension can place an order — but not by the order-status endpoint, which takes x3d_live_ keys only. Revoking one has no effect on the other. See /docs/integrations/chrome-extension.