X3DStudios

Manage API keys

Generate, rotate and revoke the x3d_live_ key that authenticates the print API — from the browser, because these endpoints use your session rather than a key.

One account holds one API key. This page covers the three calls behind the Developer API key panel on /profile: read the key's status, generate or rotate it, and revoke it. All three are authenticated by your signed-in browser session — an API key cannot manage API keys.

Session-authenticated, not key-authenticated
These endpoints read the NextAuth session cookie. Sending Authorization: Bearer x3d_live_… to them returns 401. Key management happens in a browser, on purpose: a compromised key must not be able to mint its own replacement.

Read the key status#

GET/api/account/api-keySigned in

Whether the account has a key, and what it looks like.

200
{
  "hasKey": true,
  "prefix": "x3d_live_ab12cd…",
  "createdAt": "2026-09-09T12:00:00.000Z"
}
Response fields
hasKeybooleanoptional
true when a key exists on the account.
prefixstring | nulloptional
The non-secret display prefix: x3d_live_ plus the first six characters of the secret and an ellipsis. Enough to recognise which key is deployed where, not enough to use. null when there is no key.
createdAtstring | nulloptional
ISO 8601 timestamp of when the current key was issued. null when there is no key.
The secret is never in this response
There is no endpoint that returns an existing key. GET tells you a key exists and shows its prefix; that is all we are able to tell you, because the key itself is not stored.

Generate or rotate#

POST/api/account/api-keySigned in

Issue a new key, replacing any existing one. No request body.

From the profile page
const res = await fetch("/api/account/api-key", {
  method: "POST",
  credentials: "include",
});

const data = await res.json();
if (res.status === 402) throw new Error(data.error); // code: "CARD_REQUIRED"

// The only time you will ever see this value.
console.log(data.apiKey);
200
{
  "apiKey": "x3d_live_ab12cd34ef56...",
  "prefix": "x3d_live_ab12cd…",
  "createdAt": "2026-09-09T12:00:00.000Z"
}
Response fields
apiKeystringrequired
The full secret: x3d_live_ followed by 48 lowercase hex characters, 57 characters in total. Returned exactly once and never recoverable. Copy it into a secret store or environment variable before you close the tab.
prefixstringrequired
The display prefix for the key just issued. Safe to log and to show in a UI.
createdAtstringrequired
ISO 8601 timestamp of issue.
Rotation is immediate and silent
There is no grace period and no second active key. The moment this call returns, the previous key stops authenticating and every deployment still using it starts getting 401. Roll it out everywhere before you rotate, not after.

The card-on-file prerequisite#

A key can only be issued to an account with a saved payment method, because API orders bill automatically. Add one from /profile, then generate. If payments are not configured in a deployment at all, the check is skipped and a key is issued without a card — self-hosted setups will see that behaviour, x3dstudios.com will not.

402 — no card on file
{
  "error": "Add a card on file first — API print orders are billed automatically to your saved card.",
  "code": "CARD_REQUIRED"
}
The card is checked when the key is made, not when it is used
Remove the card later and the key keeps working. Orders still succeed with 201, but come back paid: false at PENDING_PAYMENT with a payment link and a message that says "No card on file". Nothing prints until they are paid.

Revoke#

DELETE/api/account/api-keySigned in

Delete the key. No request body.

This clears the stored hash, the prefix and the created date. Every request using that key gets 401 from then on. Orders already submitted are unaffected — they are paid work at the farm, and revoking a key does not cancel them, but you also lose the ability to poll them until you generate a new key.

200
{ "ok": true }
MethodStatusBody
GET200{ hasKey, prefix, createdAt }
GET / POST / DELETE401{ "error": "Unauthorized" }
POST200{ apiKey, prefix, createdAt }
POST402{ error, code: "CARD_REQUIRED" }
DELETE200{ "ok": true }
Every response these three endpoints return.

Handling the key#

  • Store it as an environment variable or in a secret manager. It authorises real charges to your card.
  • Keep it server-side. Anything in browser JavaScript, a mobile binary or a public repository is public.
  • Log the prefix, never the key. That is what the prefix is for.
  • Revoke immediately if it leaks — a revoked key cannot be used, and generating a new one revokes the old one anyway.
  • The prefix is not a password hint: it is nine fixed characters plus six of the secret, and it is safe to paste into a support ticket.