API reference
Generated from the API's own OpenAPI document (GET /v1/openapi.json) — every route and schema here is proven against real responses by
apps/api/src/openapi.contract.test.ts.
Background images (bg)
Pass bg as a top-level POST /v1/images field
(or query param on the signed/public GET routes) to render one of the 40 curated free-pack
background photos full-bleed behind the card, e.g. bg=bg-01. The
template's title/description text gets its own contained backdrop panel so it stays legible
over the photo — the photo itself is never dimmed or scrimmed.
Only templates whose design isn't a deliberate physical/mechanical artifact (a receipt, a
punchcard, a ballot, ...) accept it: basic, blog,
docs, changelog,
product, event,
podcast, job,
release, case-study,
testimonial, lesson,
newsletter, spotlight,
aurora, signal and
verbatim. An unrecognized bg id is
rejected with invalid_input; a valid id passed to a template
that doesn't support backgrounds is silently ignored, same as any other param a template's
schema doesn't declare.
Array fields on GET routes
POST /v1/images takes an array field as a real JSON array in the
request body. The two GET routes — signed GET /i/{keyId}/{sig}
(Signing) and unsigned GET /p/{keyId}/{template}
(Public URLs) — have no JSON body, so an array field is
carried as bracket-indexed query keys instead:
field[0]=first&field[1]=second, up to that field's own max
length, indexes contiguous starting at 0. A plain repeated key
(field=a&field=b) is rejected as a duplicate, not merged into
an array.
10 templates require at least one array field, so a request for them cannot be built without this syntax:
| Template | Array field(s) |
|---|---|
| changelog | changes |
| receipt | items |
| patch | added |
| ledger | rows |
| snellen | lines |
| redacted | lines |
| ballot | options |
| metro | stops |
| facts | rows |
| bracket | contenders, finalists |
POST /v1/images
Requires Authorization: Bearer og_live_....
Request body — PostImagesBody
| Field | Type | Required |
|---|---|---|
| template | string | yes |
| params | object | no |
| format | "png" | "jpg" | "webp" | no |
| preset | "og" | "x" | "square" | no |
| scale | "1" | "2" | no |
| v | string | no |
| bg | string | no |
Responses
| Status | Type | Description | Retry-After |
|---|---|---|---|
| 201 | PostImagesResponse | Image resolved (rendered or served from cache) | no |
| 400 | object | Invalid request | no |
| 401 | object | Missing/unknown/revoked API key | no |
| 413 | object | Request body too large | no |
| 429 | object | QuotaExceeded | Rate limited or quota exhausted | yes |
| 500 | object | Unexpected server error (e.g. a database dependency failure) | no |
| 503 | object | Render engine unavailable | yes |
GET /v1/usage
Requires Authorization: Bearer og_live_....
Responses
| Status | Type | Description | Retry-After |
|---|---|---|---|
| 200 | UsageResponse | Current-period usage for the authenticated account | no |
| 401 | object | Missing/unknown/revoked API key | no |
| 429 | object | Rate limited | yes |
GET /v1/templates
Responses
| Status | Type | Description | Retry-After |
|---|---|---|---|
| 200 | TemplatesResponse | Every registered template's id, version, and params JSON Schema | no |
Error codes
Every error response is JSON { "error": <code> }.
| Code | Meaning |
|---|---|
| invalid_input | Request body or params failed validation. |
| unknown_template | `template` does not match a registered template id. |
| params_too_large | Encoded params exceeded the size limit. |
| invalid_json | Request body was not valid JSON. |
| unauthorized | Missing or unknown API key. |
| unauthenticated | No session — sign in first. |
| key_revoked | The API key exists but has been revoked. |
| forbidden | Authenticated, but not allowed to perform this action. |
| not_found | Nothing at this path/id. |
| rate_limited | Per-key burst limit hit; see `Retry-After`. |
| quota_exceeded | Past the monthly cap and its grace window. |
| render_unavailable | The render engine is temporarily unavailable. |
| key_limit | This account already has the maximum of 5 active API keys. |
| invalid_signature | The webhook signature did not verify (or no webhook secret is configured). |
| no_subscription | No billing subscription exists yet for this account. |
| billing_unavailable | The billing provider's API failed; retry after the given `Retry-After`. |