API overview
The REST API is versioned under /v1 and speaks JSON. Server-to-server calls authenticate with an API key from Studio.
Authentication
Send the key in the Authorization header as a bearer token, on every request.
curl -H "Authorization: Bearer $PUZZLE_API_KEY" \
"https://dev-api.pluzzento.com/v1/puzzles?limit=25"const response = await fetch("https://dev-api.pluzzento.com/v1/puzzles?limit=25", {
headers: { Authorization: `Bearer ${process.env.PUZZLE_API_KEY}` },
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} (${error.requestId})`);
}
const { data, nextCursor } = await response.json();- Keys look like
pp_<12 hex prefix>_<64 hex secret>. The prefix identifies the key; only its SHA-256 hash is stored. - The whole key is shown once, when it is created or rotated. Store it in your secret manager; it can't be shown again.
- Calls with a key carry no cookies and need no CSRF token. A request with both a key and session cookies is refused.
- Use keys only from your servers. Never put a key in a web page, an app bundle or a URL.
- Keys may expire (at most 730 days ahead), can be revoked at once, and can be rotated with an overlap of up to 168 hours so deployments can switch over. A workspace has at most 25 active keys.
Scopes
Each key gets one or more scopes. A call needs the scope that matches its permission.
puzzle:readRead puzzles- Puzzles, series and publications of the workspace, without solutions.
analytics:readRead analytics- The workspace's play analytics.
embed-tokens:createCreate embed tokens- Short-lived tokens that let your pages embed the workspace's token-protected puzzles.
A key acts as the person who created it, in the key's own workspace: it never grants more than that person holds there at the time of the call. If their role changes, the key's reach changes with it.
Requests and responses
- JSON bodies with camelCase properties; times are ISO 8601 in UTC; IDs are strings. Send
Content-Type: application/json. - Lists are cursor-based: pass
limit(up to 100) and the previous page'snextCursorascursor;nextCursoris null on the last page. - Calls that trigger deliveries or create billable work take an
Idempotency-Keyheader (a UUID): repeating a call with the same key replays the first answer for 24 hours. - Settings and drafts return an
ETag; send it back inIf-Matchwhen updating. A stale value gets 412. - Breaking changes go to a new version (
/v2);/v1keeps working for at least 12 months after that, announced withDeprecationandSunsetheaders.
GET /v1/puzzles?limit=25
GET /v1/puzzles?limit=25&cursor=<nextCursor of the previous page>Errors
Errors share one shape: a stable code, a human message, the requestId to quote to support, and sometimes details. Branch on the code, never on the message.
{
"error": {
"code": "INSUFFICIENT_SCOPE",
"message": "This API key lacks the scope this call needs.",
"requestId": "01JB3Q9V7Z4K2M8N6P5R3S1T0W"
}
}| Status | Code | Meaning |
|---|---|---|
| 401 | API_KEY_INVALID | The key is malformed, unknown, revoked or expired (the answer never says which). |
| 403 | INSUFFICIENT_SCOPE | The key lacks the scope this call needs. |
| 403 | ENTITLEMENT_REQUIRED | The workspace's plan no longer includes API access. |
| 403 | ACCOUNT_DISABLED | The account the key acts as is disabled. |
| 402 | QUOTA_EXCEEDED | The workspace used its monthly API calls. |
| 429 | RATE_LIMITED | Too many requests in a short time: wait for Retry-After, then try again. |
Rate limits and quotas
- Requests are rate-limited per client address, by default 120 a minute. Past it you get 429
RATE_LIMITEDwith aRetry-Afterheader in seconds. - Each plan includes a number of API calls a month per workspace. Every authenticated call counts, refused ones too. Past the quota you get 402
QUOTA_EXCEEDEDwithdetails.entitlement. - Studio shows each key's calls this month and when it was last used.
- Retry 429 and 5xx answers with exponential backoff; don't retry other 4xx answers unchanged.
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly API call quota reached.",
"requestId": "01JB3Q9V7Z4K2M8N6P5R3S1T0X",
"details": {
"entitlement": "api_calls_monthly_included"
}
}
}OpenAPI document
The API describes itself in an OpenAPI document: generate a client from it, or read it in the reference.