# REST API reference

> Every VectorFast Render /v1 endpoint with its parameters, request body, response fields, and status codes, generated from the OpenAPI 3.1 document.

Canonical URL: https://vectorfast.ai/render/docs/rest-api
Markdown URL: https://vectorfast.ai/render/docs/rest-api.md
Last modified: 2026-10-30

{/* Generated by scripts/generate-render-api-reference.ts from shared/render/openapi.json. Do not edit by hand; run pnpm render:api-reference:generate. */}

VectorFast Render API 0.1.0. Base URL: `https://vectorfast.ai`. The machine-readable document is [`GET /v1/openapi.json`](https://vectorfast.ai/v1/openapi.json).

Authenticate every `/v1` call except this document with an API key created in your account: `Authorization: Bearer vf_live_…`.

Each key is limited to 60 requests per 60 seconds. Errors always have the shape `{"error": {"code", "message", "docs"}}`.

**Billing is not connected yet**: `credits_charged` is always 0 and `GET /v1/usage` reports `credits_remaining: null`.

Templates: launch-post, blog-post, social-square, achievement-card, invite-card, stat-card, changelog, quote-card. `GET /v1/templates/{id}` returns each one's params schema, examples and an `agent_example` (a prompt and the render call it should become). A template listed with `status: "planned"` cannot be rendered yet.

## Endpoints [#endpoints]

| Method | Path                                                      | Summary                      |
| ------ | --------------------------------------------------------- | ---------------------------- |
| GET    | [`/v1/templates`](#get-v1-templates)                      | List templates               |
| GET    | [`/v1/templates/{id}`](#get-v1-templates-id)              | Describe a template          |
| POST   | [`/v1/renders`](#post-v1-renders)                         | Render an image              |
| POST   | [`/v1/previews`](#post-v1-previews)                       | Render a watermarked preview |
| GET    | [`/v1/usage`](#get-v1-usage)                              | Usage for the current period |
| GET    | [`/v1/brand-kits`](#get-v1-brand-kits)                    | List brand kits              |
| POST   | [`/v1/brand-kits`](#post-v1-brand-kits)                   | Create a brand kit           |
| GET    | [`/v1/brand-kits/{id}`](#get-v1-brand-kits-id)            | Read a brand kit             |
| PUT    | [`/v1/brand-kits/{id}`](#put-v1-brand-kits-id)            | Update a brand kit           |
| DELETE | [`/v1/brand-kits/{id}`](#delete-v1-brand-kits-id)         | Delete a brand kit           |
| POST   | [`/v1/brand-kits/{id}/logo`](#post-v1-brand-kits-id-logo) | Upload a logo                |
| GET    | [`/v1/openapi.json`](#get-v1-openapi-json)                | This document                |

## Templates [#templates]

Official templates, their param schemas and preview images.

<a id="get-v1-templates" />

### List templates [#list-templates]

`GET /v1/templates`

All catalog templates. `preview_url` is a signed `/i/` URL of the English example at the default size.

**Auth:** `Authorization: Bearer <API key>`.

**Response 200**

| Field       | Type               | Required | Constraints | Description |
| ----------- | ------------------ | -------- | ----------- | ----------- |
| `templates` | TemplateListing\[] | yes      | —           | —           |

**Status codes**

| Status | Meaning                                                                                      |
| ------ | -------------------------------------------------------------------------------------------- |
| 200    | The catalog.                                                                                 |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                             |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`. |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                    |

<a id="get-v1-templates-id" />

### Describe a template [#describe-a-template]

`GET /v1/templates/{id}`

Params JSON Schema (draft 2020-12, generated from the template's zod schema), sizes, examples and an agent prompt.

**Auth:** `Authorization: Bearer <API key>`.

**Parameters**

| Name | In   | Type                                                                                                                                 | Required | Description |
| ---- | ---- | ------------------------------------------------------------------------------------------------------------------------------------ | -------- | ----------- |
| `id` | path | "launch-post" \| "blog-post" \| "social-square" \| "achievement-card" \| "invite-card" \| "stat-card" \| "changelog" \| "quote-card" | yes      | —           |

**Response 200** (`TemplateDescription`)

The template. Content types: `application/json`.

**Status codes**

| Status | Meaning                                                                                      |
| ------ | -------------------------------------------------------------------------------------------- |
| 200    | The template.                                                                                |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                             |
| 404    | The id is not in the catalog (`template_not_found`).                                         |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`. |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                    |

## Renders [#renders]

Synchronous renders and watermarked previews.

<a id="post-v1-renders" />

### Render an image [#render-an-image]

`POST /v1/renders`

Validates the template, params, format and size, then answers from the edge cache, the R2 cache or a fresh render (`cached` tells which).

Send `Idempotency-Key` to make retries safe: the same key with the same body returns the stored JSON for 24 hours (header `Idempotent-Replayed: true`); the same key with a different body answers 409 `invalid_params`.

Every call records one usage row (template, format, cached); params are never stored.

**Auth:** `Authorization: Bearer <API key>`.

**Parameters**

| Name              | In     | Type   | Required | Description                                              |
| ----------------- | ------ | ------ | -------- | -------------------------------------------------------- |
| `Idempotency-Key` | header | string | no       | Client-chosen retry key, printable ASCII without spaces. |

**Request body** (`application/json`)

| Field       | Type                      | Required | Constraints                                  | Description |
| ----------- | ------------------------- | -------- | -------------------------------------------- | ----------- |
| `template`  | string                    | yes      | pattern `^([a-z0-9-]{1,64})(?:@(\d{1,3}))?$` | —           |
| `format`    | "png" \| "jpeg" \| "webp" | no       | default `"png"`                              | —           |
| `width`     | integer                   | no       | 16–4096                                      | —           |
| `height`    | integer                   | no       | 16–4096                                      | —           |
| `scale`     | 1 or 2                    | no       | default `1`                                  | —           |
| `brand_kit` | string                    | no       | 1–64 chars                                   | —           |
| `params`    | object                    | yes      | —                                            | —           |
| `response`  | "url" \| "binary"         | no       | default `"url"`                              | —           |

Body of `POST /v1/renders` and `POST /v1/previews`. `params` must satisfy the template's own schema (see `GET /v1/templates/{id}` → `params_schema`, or the `TemplateParams_*` components).

**Response 200** (`RenderResponse`)

| Field              | Type                      | Required | Constraints                  | Description                                                                               |
| ------------------ | ------------------------- | -------- | ---------------------------- | ----------------------------------------------------------------------------------------- |
| `id`               | string                    | yes      | pattern `^rnd_[0-9a-f]{32}$` | Render id; stable across idempotent replays.                                              |
| `url`              | string (uri)              | yes      | —                            | Signed `/i/{template}.{format}` URL serving the same pixels. It never expires.            |
| `cache_key`        | string                    | yes      | pattern `^[0-9a-f]{64}$`     | —                                                                                         |
| `cached`           | boolean                   | yes      | —                            | True when a cache layer answered and nothing was rendered.                                |
| `credits_charged`  | 0                         | yes      | —                            | Always 0 in this phase: credits are connected in a later release. Cache hits will stay 0. |
| `width`            | integer                   | yes      | —                            | —                                                                                         |
| `height`           | integer                   | yes      | —                            | —                                                                                         |
| `scale`            | 1 \| 2                    | yes      | —                            | —                                                                                         |
| `format`           | "png" \| "jpeg" \| "webp" | yes      | —                            | —                                                                                         |
| `render_ms`        | number                    | yes      | —                            | Renderer time (layout + raster + encode) for this request; 0 on cache hits.               |
| `template_version` | string                    | yes      | —                            | `<id>@<semver>` of the template that produced the pixels.                                 |
| `watermark`        | true                      | no       | —                            | Present only on `/v1/previews` responses.                                                 |

**Status codes**

| Status | Meaning                                                                                                                  |
| ------ | ------------------------------------------------------------------------------------------------------------------------ |
| 200    | The render. With `response: "url"` a JSON body; with `response: "binary"` the image bytes and the same facts in headers. |
| 400    | Invalid request body, params, size or Idempotency-Key (`invalid_params`).                                                |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                                                         |
| 404    | Unknown or planned template, or unknown brand kit (`template_not_found`, `brand_kit_not_found`).                         |
| 409    | `Idempotency-Key` reused with a different body (`invalid_params`).                                                       |
| 413    | Body larger than 256 KiB (`invalid_params`).                                                                             |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`.                             |
| 500    | The renderer failed (`render_failed`).                                                                                   |
| 502    | Fonts could not be loaded (`render_failed`).                                                                             |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                                                |

<a id="post-v1-previews" />

### Render a watermarked preview [#render-a-watermarked-preview]

`POST /v1/previews`

Same body and validation as `/v1/renders`; the image carries a translucent PREVIEW label, is cached at the edge only (never in R2), charges nothing and records no usage. `Idempotency-Key` is ignored.

**Auth:** `Authorization: Bearer <API key>`.

**Request body** (`application/json`)

| Field       | Type                      | Required | Constraints                                  | Description |
| ----------- | ------------------------- | -------- | -------------------------------------------- | ----------- |
| `template`  | string                    | yes      | pattern `^([a-z0-9-]{1,64})(?:@(\d{1,3}))?$` | —           |
| `format`    | "png" \| "jpeg" \| "webp" | no       | default `"png"`                              | —           |
| `width`     | integer                   | no       | 16–4096                                      | —           |
| `height`    | integer                   | no       | 16–4096                                      | —           |
| `scale`     | 1 or 2                    | no       | default `1`                                  | —           |
| `brand_kit` | string                    | no       | 1–64 chars                                   | —           |
| `params`    | object                    | yes      | —                                            | —           |
| `response`  | "url" \| "binary"         | no       | default `"url"`                              | —           |

Body of `POST /v1/renders` and `POST /v1/previews`. `params` must satisfy the template's own schema (see `GET /v1/templates/{id}` → `params_schema`, or the `TemplateParams_*` components).

**Response 200** (`RenderResponse`)

| Field              | Type                      | Required | Constraints                  | Description                                                                               |
| ------------------ | ------------------------- | -------- | ---------------------------- | ----------------------------------------------------------------------------------------- |
| `id`               | string                    | yes      | pattern `^rnd_[0-9a-f]{32}$` | Render id; stable across idempotent replays.                                              |
| `url`              | string (uri)              | yes      | —                            | Signed `/i/{template}.{format}` URL serving the same pixels. It never expires.            |
| `cache_key`        | string                    | yes      | pattern `^[0-9a-f]{64}$`     | —                                                                                         |
| `cached`           | boolean                   | yes      | —                            | True when a cache layer answered and nothing was rendered.                                |
| `credits_charged`  | 0                         | yes      | —                            | Always 0 in this phase: credits are connected in a later release. Cache hits will stay 0. |
| `width`            | integer                   | yes      | —                            | —                                                                                         |
| `height`           | integer                   | yes      | —                            | —                                                                                         |
| `scale`            | 1 \| 2                    | yes      | —                            | —                                                                                         |
| `format`           | "png" \| "jpeg" \| "webp" | yes      | —                            | —                                                                                         |
| `render_ms`        | number                    | yes      | —                            | Renderer time (layout + raster + encode) for this request; 0 on cache hits.               |
| `template_version` | string                    | yes      | —                            | `<id>@<semver>` of the template that produced the pixels.                                 |
| `watermark`        | true                      | no       | —                            | Present only on `/v1/previews` responses.                                                 |

**Status codes**

| Status | Meaning                                                                                          |
| ------ | ------------------------------------------------------------------------------------------------ |
| 200    | The preview; `watermark: true` in JSON responses.                                                |
| 400    | Invalid request body, params, size or Idempotency-Key (`invalid_params`).                        |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                                 |
| 404    | Unknown or planned template, or unknown brand kit (`template_not_found`, `brand_kit_not_found`). |
| 413    | Body larger than 256 KiB (`invalid_params`).                                                     |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`.     |
| 500    | The renderer failed (`render_failed`).                                                           |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                        |

## Brand kits [#brand-kits]

Versioned brand kits: colors, fonts and a logo that templates render with.

<a id="get-v1-brand-kits" />

### List brand kits [#list-brand-kits]

`GET /v1/brand-kits`

Your kits at their current version, oldest first (the first is the default for renders). With no kits of your own the list holds the read-only built-in `default` kit. `limit` is how many kits your plan includes.

**Auth:** `Authorization: Bearer <API key>`.

**Response 200**

| Field        | Type        | Required | Constraints | Description |
| ------------ | ----------- | -------- | ----------- | ----------- |
| `brand_kits` | BrandKit\[] | yes      | —           | —           |
| `limit`      | integer     | yes      | —           | —           |

**Status codes**

| Status | Meaning                                                                                      |
| ------ | -------------------------------------------------------------------------------------------- |
| 200    | The kits.                                                                                    |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                             |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`. |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                    |

<a id="post-v1-brand-kits" />

### Create a brand kit [#create-a-brand-kit]

`POST /v1/brand-kits`

Creates version 1. Colors and fonts you leave out start from the built-in kit. Font families must be Google Fonts families that load at weights 400 and 700; custom font uploads are not available yet.

**Auth:** `Authorization: Bearer <API key>`.

**Request body** (`application/json`)

| Field                     | Type   | Required | Constraints                                            | Description |
| ------------------------- | ------ | -------- | ------------------------------------------------------ | ----------- |
| `name`                    | string | yes      | 1–64 chars                                             | —           |
| `colors`                  | object | no       | —                                                      | —           |
| `colors.background`       | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.foreground`       | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.muted`            | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.accent`           | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.accentForeground` | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.surface`          | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `fonts`                   | object | no       | —                                                      | —           |
| `fonts.heading`           | object | no       | —                                                      | —           |
| `fonts.body`              | object | no       | —                                                      | —           |

**Response 201** (`BrandKit`)

| Field                     | Type                       | Required | Constraints                                            | Description                                                                                                                         |
| ------------------------- | -------------------------- | -------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `id`                      | string                     | yes      | —                                                      | `bk_` + 16 base32 characters, or `default` for the built-in kit.                                                                    |
| `name`                    | string                     | yes      | —                                                      | —                                                                                                                                   |
| `version`                 | string                     | yes      | —                                                      | `<id>@<n>`. Every save creates a new version; renders fold it into the cache key, so old images are never served for a changed kit. |
| `version_number`          | integer                    | yes      | 1–∞                                                    | —                                                                                                                                   |
| `is_default`              | boolean                    | yes      | —                                                      | The kit renders use when `brand_kit` is omitted: your oldest kit, or the built-in kit when you have none.                           |
| `read_only`               | boolean                    | yes      | —                                                      | True only for the built-in `default` kit.                                                                                           |
| `colors`                  | object                     | yes      | —                                                      | —                                                                                                                                   |
| `colors.background`       | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.foreground`       | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.muted`            | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.accent`           | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.accentForeground` | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.surface`          | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `fonts`                   | object                     | yes      | —                                                      | —                                                                                                                                   |
| `fonts.heading`           | object                     | yes      | —                                                      | —                                                                                                                                   |
| `fonts.body`              | object                     | yes      | —                                                      | —                                                                                                                                   |
| `logo`                    | object or null             | yes      | —                                                      | Set by `POST /v1/brand-kits/{id}/logo`. URLs are signed, immutable and never expire.                                                |
| `logo.svg_url`            | string or null (uri)       | yes      | —                                                      | —                                                                                                                                   |
| `logo.png_url`            | string or null (uri)       | yes      | —                                                      | —                                                                                                                                   |
| `logo.source_format`      | "png" \| "jpeg" \| "svg"   | yes      | —                                                      | —                                                                                                                                   |
| `logo.width`              | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `logo.height`             | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `logo.png_width`          | integer                    | yes      | —                                                      | Twice `width` (capped at 4096).                                                                                                     |
| `logo.png_height`         | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `logo.path_count`         | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `created_at`              | string or null (date-time) | yes      | —                                                      | —                                                                                                                                   |
| `updated_at`              | string or null (date-time) | yes      | —                                                      | —                                                                                                                                   |

**Status codes**

| Status | Meaning                                                                                      |
| ------ | -------------------------------------------------------------------------------------------- |
| 201    | The new kit.                                                                                 |
| 400    | Invalid request body, params, size or Idempotency-Key (`invalid_params`).                    |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                             |
| 403    | Your plan's brand-kit limit is reached (`brand_kit_limit_reached`).                          |
| 413    | Body larger than 256 KiB (`invalid_params`).                                                 |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`. |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                    |

<a id="get-v1-brand-kits-id" />

### Read a brand kit [#read-a-brand-kit]

`GET /v1/brand-kits/{id}`

**Auth:** `Authorization: Bearer <API key>`.

**Parameters**

| Name | In   | Type   | Required | Description                                                                                    |
| ---- | ---- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
| `id` | path | string | yes      | Brand kit id. `GET` also accepts a pinned version such as `bk_…@2` and the built-in `default`. |

**Response 200** (`BrandKit`)

| Field                     | Type                       | Required | Constraints                                            | Description                                                                                                                         |
| ------------------------- | -------------------------- | -------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `id`                      | string                     | yes      | —                                                      | `bk_` + 16 base32 characters, or `default` for the built-in kit.                                                                    |
| `name`                    | string                     | yes      | —                                                      | —                                                                                                                                   |
| `version`                 | string                     | yes      | —                                                      | `<id>@<n>`. Every save creates a new version; renders fold it into the cache key, so old images are never served for a changed kit. |
| `version_number`          | integer                    | yes      | 1–∞                                                    | —                                                                                                                                   |
| `is_default`              | boolean                    | yes      | —                                                      | The kit renders use when `brand_kit` is omitted: your oldest kit, or the built-in kit when you have none.                           |
| `read_only`               | boolean                    | yes      | —                                                      | True only for the built-in `default` kit.                                                                                           |
| `colors`                  | object                     | yes      | —                                                      | —                                                                                                                                   |
| `colors.background`       | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.foreground`       | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.muted`            | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.accent`           | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.accentForeground` | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.surface`          | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `fonts`                   | object                     | yes      | —                                                      | —                                                                                                                                   |
| `fonts.heading`           | object                     | yes      | —                                                      | —                                                                                                                                   |
| `fonts.body`              | object                     | yes      | —                                                      | —                                                                                                                                   |
| `logo`                    | object or null             | yes      | —                                                      | Set by `POST /v1/brand-kits/{id}/logo`. URLs are signed, immutable and never expire.                                                |
| `logo.svg_url`            | string or null (uri)       | yes      | —                                                      | —                                                                                                                                   |
| `logo.png_url`            | string or null (uri)       | yes      | —                                                      | —                                                                                                                                   |
| `logo.source_format`      | "png" \| "jpeg" \| "svg"   | yes      | —                                                      | —                                                                                                                                   |
| `logo.width`              | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `logo.height`             | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `logo.png_width`          | integer                    | yes      | —                                                      | Twice `width` (capped at 4096).                                                                                                     |
| `logo.png_height`         | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `logo.path_count`         | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `created_at`              | string or null (date-time) | yes      | —                                                      | —                                                                                                                                   |
| `updated_at`              | string or null (date-time) | yes      | —                                                      | —                                                                                                                                   |

**Status codes**

| Status | Meaning                                                                                      |
| ------ | -------------------------------------------------------------------------------------------- |
| 200    | The kit at its current (or pinned) version.                                                  |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                             |
| 404    | No such kit or version on your account (`brand_kit_not_found`).                              |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`. |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                    |

<a id="put-v1-brand-kits-id" />

### Update a brand kit [#update-a-brand-kit]

`PUT /v1/brand-kits/{id}`

Saves a new version. Fields you leave out keep their current values; the logo only changes through `POST /v1/brand-kits/{id}/logo`. Earlier versions stay readable and renderable when pinned.

**Auth:** `Authorization: Bearer <API key>`.

**Parameters**

| Name | In   | Type   | Required | Description                                                                                    |
| ---- | ---- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
| `id` | path | string | yes      | Brand kit id. `GET` also accepts a pinned version such as `bk_…@2` and the built-in `default`. |

**Request body** (`application/json`)

| Field                     | Type   | Required | Constraints                                            | Description |
| ------------------------- | ------ | -------- | ------------------------------------------------------ | ----------- |
| `name`                    | string | no       | 1–64 chars                                             | —           |
| `colors`                  | object | no       | —                                                      | —           |
| `colors.background`       | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.foreground`       | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.muted`            | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.accent`           | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.accentForeground` | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `colors.surface`          | string | no       | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —           |
| `fonts`                   | object | no       | —                                                      | —           |
| `fonts.heading`           | object | no       | —                                                      | —           |
| `fonts.body`              | object | no       | —                                                      | —           |

**Response 200** (`BrandKit`)

| Field                     | Type                       | Required | Constraints                                            | Description                                                                                                                         |
| ------------------------- | -------------------------- | -------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `id`                      | string                     | yes      | —                                                      | `bk_` + 16 base32 characters, or `default` for the built-in kit.                                                                    |
| `name`                    | string                     | yes      | —                                                      | —                                                                                                                                   |
| `version`                 | string                     | yes      | —                                                      | `<id>@<n>`. Every save creates a new version; renders fold it into the cache key, so old images are never served for a changed kit. |
| `version_number`          | integer                    | yes      | 1–∞                                                    | —                                                                                                                                   |
| `is_default`              | boolean                    | yes      | —                                                      | The kit renders use when `brand_kit` is omitted: your oldest kit, or the built-in kit when you have none.                           |
| `read_only`               | boolean                    | yes      | —                                                      | True only for the built-in `default` kit.                                                                                           |
| `colors`                  | object                     | yes      | —                                                      | —                                                                                                                                   |
| `colors.background`       | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.foreground`       | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.muted`            | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.accent`           | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.accentForeground` | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `colors.surface`          | string                     | yes      | pattern `^#(?:[0-9a-f]{3}\|[0-9a-f]{6}\|[0-9a-f]{8})$` | —                                                                                                                                   |
| `fonts`                   | object                     | yes      | —                                                      | —                                                                                                                                   |
| `fonts.heading`           | object                     | yes      | —                                                      | —                                                                                                                                   |
| `fonts.body`              | object                     | yes      | —                                                      | —                                                                                                                                   |
| `logo`                    | object or null             | yes      | —                                                      | Set by `POST /v1/brand-kits/{id}/logo`. URLs are signed, immutable and never expire.                                                |
| `logo.svg_url`            | string or null (uri)       | yes      | —                                                      | —                                                                                                                                   |
| `logo.png_url`            | string or null (uri)       | yes      | —                                                      | —                                                                                                                                   |
| `logo.source_format`      | "png" \| "jpeg" \| "svg"   | yes      | —                                                      | —                                                                                                                                   |
| `logo.width`              | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `logo.height`             | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `logo.png_width`          | integer                    | yes      | —                                                      | Twice `width` (capped at 4096).                                                                                                     |
| `logo.png_height`         | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `logo.path_count`         | integer                    | yes      | —                                                      | —                                                                                                                                   |
| `created_at`              | string or null (date-time) | yes      | —                                                      | —                                                                                                                                   |
| `updated_at`              | string or null (date-time) | yes      | —                                                      | —                                                                                                                                   |

**Status codes**

| Status | Meaning                                                                                      |
| ------ | -------------------------------------------------------------------------------------------- |
| 200    | The kit at its new version.                                                                  |
| 400    | Invalid body, or the read-only built-in kit (`invalid_params`).                              |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                             |
| 404    | No such kit on your account (`brand_kit_not_found`).                                         |
| 409    | The kit changed while the request ran (`brand_kit_conflict`); read it and retry.             |
| 413    | Body larger than 256 KiB (`invalid_params`).                                                 |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`. |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                    |

<a id="delete-v1-brand-kits-id" />

### Delete a brand kit [#delete-a-brand-kit]

`DELETE /v1/brand-kits/{id}`

Soft delete: the kit stops counting toward your limit, renders that name it answer 404, and its signed image URLs stop rendering on a cache miss.

**Auth:** `Authorization: Bearer <API key>`.

**Parameters**

| Name | In   | Type   | Required | Description                                                                                    |
| ---- | ---- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
| `id` | path | string | yes      | Brand kit id. `GET` also accepts a pinned version such as `bk_…@2` and the built-in `default`. |

**Response 200**

| Field     | Type   | Required | Constraints | Description |
| --------- | ------ | -------- | ----------- | ----------- |
| `id`      | string | yes      | —           | —           |
| `deleted` | true   | yes      | —           | —           |

**Status codes**

| Status | Meaning                                                                                      |
| ------ | -------------------------------------------------------------------------------------------- |
| 200    | Deleted.                                                                                     |
| 400    | The read-only built-in kit (`invalid_params`).                                               |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                             |
| 404    | No such kit on your account (`brand_kit_not_found`).                                         |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`. |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                    |

<a id="post-v1-brand-kits-id-logo" />

### Upload a logo [#upload-a-logo]

`POST /v1/brand-kits/{id}/logo`

Send the file as the raw body with `Content-Type: image/png`, `image/jpeg` or `image/svg+xml`, or as `multipart/form-data` with a part named `file`. At most 5 MB and 4096×4096 pixels; the file name is ignored and never stored.

PNG and JPEG logos are traced to SVG with the same engine as the free VectorFast vectorizer, then cleaned (tiny fragments, near-duplicate colors, coordinate precision); the untouched trace is kept too. SVG logos skip tracing and are sanitized (scripts, event handlers, external links and embedded HTML are removed). Both get a 2x PNG. Tracing is a best-effort candidate, not an exact reconstruction of the original artwork.

Saves a new kit version; renders and signed URLs that name the kit use it from then on. Limited to 3 uploads a minute and 10 an hour per API key.

**Auth:** `Authorization: Bearer <API key>`.

**Parameters**

| Name | In   | Type   | Required | Description                                                                                    |
| ---- | ---- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
| `id` | path | string | yes      | Brand kit id. `GET` also accepts a pinned version such as `bk_…@2` and the built-in `default`. |

**Request body** (`image/png`, `image/jpeg`, `image/svg+xml`, `multipart/form-data`)

| Field  | Type            | Required | Constraints | Description |
| ------ | --------------- | -------- | ----------- | ----------- |
| `file` | string (binary) | yes      | —           | —           |

**Response 200**

| Field       | Type                     | Required | Constraints | Description |
| ----------- | ------------------------ | -------- | ----------- | ----------- |
| `brand_kit` | BrandKit                 | yes      | —           | —           |
| `version`   | string                   | yes      | —           | —           |
| `logo`      | BrandKit/properties/logo | yes      | —           | —           |

**Status codes**

| Status | Meaning                                                                                                                                     |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| 200    | The kit at the version that carries the logo.                                                                                               |
| 400    | Over 5 MB or 4096×4096, a body that does not match its Content-Type, an unreadable image, or the read-only built-in kit (`invalid_params`). |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                                                                            |
| 404    | No such kit on your account (`brand_kit_not_found`).                                                                                        |
| 409    | The kit changed while the upload ran (`brand_kit_conflict`); upload again.                                                                  |
| 415    | Content-Type is not PNG, JPEG, SVG or multipart (`invalid_params`).                                                                         |
| 429    | More than 3 uploads a minute or 10 an hour for this API key (`rate_limited`); honour `Retry-After`.                                         |
| 500    | The pipeline or storage failed (`render_failed`).                                                                                           |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                                                                   |

## Account [#account]

Usage for the current API key's account.

<a id="get-v1-usage" />

### Usage for the current period [#usage-for-the-current-period]

`GET /v1/usage`

Renders counted in the current UTC calendar month for the key's account. `credits_charged` is 0 and `credits_remaining` is `null` until billing is connected, after which the period follows the subscription.

**Auth:** `Authorization: Bearer <API key>`.

**Response 200** (`Usage`)

| Field               | Type               | Required | Constraints | Description                                                  |
| ------------------- | ------------------ | -------- | ----------- | ------------------------------------------------------------ |
| `period`            | object             | yes      | —           | —                                                            |
| `period.start`      | string (date-time) | yes      | —           | —                                                            |
| `period.end`        | string (date-time) | yes      | —           | —                                                            |
| `renders`           | integer            | yes      | —           | `POST /v1/renders` calls in the period, cache hits included. |
| `cached_renders`    | integer            | yes      | —           | —                                                            |
| `credits_charged`   | 0                  | yes      | —           | Always 0 until billing is connected.                         |
| `credits_remaining` | integer or null    | yes      | —           | `null` until billing is connected.                           |

**Status codes**

| Status | Meaning                                                                                      |
| ------ | -------------------------------------------------------------------------------------------- |
| 200    | Usage.                                                                                       |
| 401    | Missing, malformed, unknown or revoked API key (`unauthorized`).                             |
| 429    | More than 60 requests in 60 seconds for this API key (`rate_limited`); honour `Retry-After`. |
| 503    | The API is not configured or a dependency is down (`render_unavailable`).                    |

## Meta [#meta]

This document.

<a id="get-v1-openapi-json" />

### This document [#this-document]

`GET /v1/openapi.json`

**Auth:** none.

**Response 200**

The OpenAPI 3.1 document. Content types: `application/json`.

**Status codes**

| Status | Meaning                   |
| ------ | ------------------------- |
| 200    | The OpenAPI 3.1 document. |

## Errors [#errors]

Every error body has the same shape; see [Errors](/render/docs/errors) for what each code means and how to fix it.

| Field           | Type                                                                                                                                                                                                                                                                                | Required | Constraints | Description                                               |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- | --------------------------------------------------------- |
| `error`         | object                                                                                                                                                                                                                                                                              | yes      | —           | —                                                         |
| `error.code`    | "invalid\_params" \| "unauthorized" \| "insufficient\_credits" \| "template\_not\_found" \| "brand\_kit\_not\_found" \| "brand\_kit\_limit\_reached" \| "brand\_kit\_conflict" \| "not\_found" \| "rate\_limited" \| "render\_failed" \| "render\_timeout" \| "render\_unavailable" | yes      | —           | —                                                         |
| `error.message` | string                                                                                                                                                                                                                                                                              | yes      | —           | Public-safe; names a field or rule, never echoes a value. |
| `error.docs`    | string (uri)                                                                                                                                                                                                                                                                        | yes      | —           | —                                                         |
