Image renders
Create PNG, JPEG, or WebP images from HTML or saved templates, then check each render and download the result.
Give this prompt to your AI agent. The agent does the task for you.
Read https://thirds.ai/docs/api-reference/image and help me choose the right thirds.ai endpoint and write a request. Check the fields and credentials in this reference.
See all API resources or read the PDF generation API guide.
Endpoints
createImage
POST /v1/image
Create a static image
Render one PNG, JPEG, or WebP from raw HTML, an inline stateless template, or one owned saved-template version. Returns 200 for a terminal job or 202 while the accepted job remains queued or running. A terminal job can be succeeded, failed, or cancelled; check status before downloading, then use file downloads to read the finished file. Send wait=false to skip the bounded wait. A successful render costs exactly one credit; a failed or cancelled render costs zero.
Authentication: bearer API key. See authentication.
| Parameter | Location | Required | Rule |
|---|---|---|---|
Idempotency-Key | header | No | {"type":"string","minLength":1,"maxLength":255,"pattern":"^[ -~]+$"} |
Request body: application/json, ImageRequest.
| Status | Meaning | Body |
|---|---|---|
| 200 | The job is terminal. Check status for succeeded, failed, or cancelled; HTTP 200 does not mean the render succeeded. | application/json: ImageJob |
| 202 | The image job is durable and remains queued or running. | application/json: ImageJob |
| 400 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 401 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 402 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 403 | The authenticated account is suspended. The code is account_suspended. | application/json: ErrorEnvelope |
| 405 | The method is not allowed on this route. | application/json: ErrorEnvelope |
| 409 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 413 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 415 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 422 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 429 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 431 | The request has more than 64 headers or more than 32 KiB of header names and values. | application/json: ErrorEnvelope |
| 500 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 503 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
listImageHistory
GET /v1/image
List image history
List the authenticated account's image jobs, newest first. Failed or cancelled jobs removed through DELETE do not appear. Bounded by keyset: pass the previous page's next_cursor to continue, rather than an offset, so a page already read stays stable while new jobs are created.
Authentication: bearer API key. See authentication.
| Parameter | Location | Required | Rule |
|---|---|---|---|
q | query | No | Case-insensitive search of references, template names, and file types. {"type":"string","maxLength":200,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]*$"} |
limit | query | No | 1 to 100. Defaults to 20. A value outside this range is clamped rather than refused. {"type":"integer","default":20} |
cursor | query | No | An opaque value from an earlier page's next_cursor. A cursor that cannot be read is refused with 400. {"type":"string","maxLength":128} |
| Status | Meaning | Body |
|---|---|---|
| 200 | One page of the account's image history. | application/json: ImageHistoryPage |
| 400 | The limit parameter was not a whole number, the cursor could not be read, or the query held an unknown parameter. | application/json: ErrorEnvelope |
| 401 | The request did not carry a valid, active API key. | application/json: ErrorEnvelope |
| 405 | The method is not allowed on this route. | application/json: ErrorEnvelope |
| 431 | The request has more than 64 headers or more than 32 KiB of header names and values. | application/json: ErrorEnvelope |
| 500 | An internal error occurred. | application/json: ErrorEnvelope |
getImage
GET /v1/image/{id}
Get a static image's status
Check an image request's status and get its download link when the image is ready.
Authentication: bearer API key. See authentication.
| Parameter | Location | Required | Rule |
|---|---|---|---|
id | path | Yes | {"type":"string","pattern":"^image_[0-9a-f]{32}$"} |
| Status | Meaning | Body |
|---|---|---|
| 200 | The image job's current status. | application/json: ImageJob |
| 401 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 403 | The authenticated account is suspended. The code is account_suspended. | application/json: ErrorEnvelope |
| 404 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 405 | The method is not allowed on this route. | application/json: ErrorEnvelope |
| 431 | The request has more than 64 headers or more than 32 KiB of header names and values. | application/json: ErrorEnvelope |
| 500 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
deleteImage
DELETE /v1/image/{id}
Delete an image file or an unsuccessful render
Remove a successful job's file, or remove a failed or cancelled job from history. Repeated requests are safe. The job stays readable by ID, and billing, abuse limits, and idempotency keys stay unchanged. Queued and running jobs return 409.
Authentication: bearer API key. See authentication.
| Parameter | Location | Required | Rule |
|---|---|---|---|
id | path | Yes | {"type":"string","pattern":"^image_[0-9a-f]{32}$"} |
| Status | Meaning | Body |
|---|---|---|
| 200 | The image job after file or history removal. | application/json: ImageJob |
| 401 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 403 | The authenticated account is suspended. The code is account_suspended. | application/json: ErrorEnvelope |
| 404 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 405 | The method is not allowed on this route. | application/json: ErrorEnvelope |
| 409 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 431 | The request has more than 64 headers or more than 32 KiB of header names and values. | application/json: ErrorEnvelope |
| 500 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
changeImageExpiry
PATCH /v1/image/{id}
Change how long an image file is kept
Set a new end for a successful job's file: the job's created_at plus retention_days. You can make the time shorter or longer, up to the plan maximum: 365 days on a paid plan, 30 on a free, pack-only, or trial account. The new end must be in the future. A later plan change keeps this choice, except that a smaller plan maximum still shortens it. A job without a live file, such as a queued, failed, deleted, or expired one, returns 404.
Authentication: bearer API key. See authentication.
| Parameter | Location | Required | Rule |
|---|---|---|---|
id | path | Yes | {"type":"string","pattern":"^image_[0-9a-f]{32}$"} |
Request body: application/json, FileExpiryChange.
| Status | Meaning | Body |
|---|---|---|
| 200 | The job with its file's new end in artifact.expires_at. | application/json: ImageJob |
| 400 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 401 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 403 | The authenticated account is suspended. The code is account_suspended. | application/json: ErrorEnvelope |
| 404 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 405 | The method is not allowed on this route. | application/json: ErrorEnvelope |
| 413 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 415 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 422 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
| 431 | The request has more than 64 headers or more than 32 KiB of header names and values. | application/json: ErrorEnvelope |
| 500 | The one error envelope every backend response uses. | application/json: ErrorEnvelope |
Schemas
ImageRequestCommon
To receive finished image events, follow the webhook delivery guide.
| Field | Required | Description and type |
|---|---|---|
filename | No | Download name. Surrounding spaces are refused. The server uses the real output extension. Omit it for a safe template-name and UTC creation-date name, or html-render and date for HTML. {"type":"string","minLength":1,"maxLength":120,"pattern":"^(?!.* $)(?!.*\\.\\.)[A-Za-z0-9][A-Za-z0-9 ._-]*$"} |
reference | No | Your reference, preserved in the job, history, and webhook. Omitted when not supplied. {"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]+$"} |
retention_days | No | Days to keep the finished file after it succeeds. Omit it to use the account default, which starts at the plan maximum. A paid plan allows up to 365 days. A free, pack-only, or trial account allows up to 30 days, and a larger value is refused with retention_limit. artifact.expires_at shows the exact end. {"type":"integer","minimum":1,"maximum":365} |
brand_kit_id | No | Select an owned active brand kit. This uses template evaluation with a 1 MiB source limit and a 3 MiB data limit, even when data is omitted. Overrides the source thirds-brand-kit meta default. Supplies reserved brand.name, brand.palette, brand.colours.primary/secondary/accent (the kit colour that fits each role of this design, so the design keeps its own pattern of dark, pale, and bright colours), brand.colours.dark/light (a dark and a light surface in the kit's hue, each readable on the other), brand.ink.primary/secondary/accent/dark/light (#ffffff, #111111, or #000000, the text colour on each role colour, at least 4.5:1), brand.logo, brand.logos, brand.logo_ground (the plate colour on which the first logo shows well), brand.logo_tile.primary/secondary/accent/dark/light (transparent when the logo shows well on that role colour, or else the plate colour), brand.logo_aspect (the logo width divided by its height), brand.fonts entries with family and src, and brand.heading_font and brand.body_font (the family of the first and second font). data.brand is refused when a kit is selected. A kit with no colours supplies no colour roles. Only present logos and fonts are supplied. With no kit, brand is an empty value that is false in a test, so brand.colours.primary | default('#000000') gives the default. Assets are captured before enqueue. Schemas validate customer data before brand is added. {"type":["string","null"],"pattern":"^kit_[a-f0-9]{32}$"} |
image | Yes | {"type":"object","properties":{"format":{"type":"string","enum":["png","jpeg","webp"]},"width":{"type":"integer","minimum":320,"maximum":7680,"description":"Defaults to 1280. A saved template with no width or height uses its own canvas size."},"height":{"type":"integer","minimum":200,"maximum":4320,"description":"Defaults to 720. A saved template with no width or height uses its own canvas size."},"quality":{"type":"integer","minimum":1,"maximum":100},"transparent":{"type":"boolean","default":false}},"required":["format"],"allOf":[{"if":{"properties":{"format":{"const":"png"}}},"then":{"not":{"required":["quality"]}},"else":{"properties":{"quality":{"default":80}}}},{"if":{"properties":{"format":{"const":"jpeg"}}},"then":{"properties":{"transparent":{"const":false}}}}],"additionalProperties":false} |
javascript | No | {"type":"object","properties":{"mode":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false} |
wait | No | {"type":"boolean","default":true} |
Schema rules: {"type":"object"}.
RawImageRequest
Schema rules: {"allOf":[{"$ref":"#/components/schemas/ImageRequestCommon"},{"type":"object","properties":{"html":{"type":"string","minLength":1,"maxLength":5242880}},"required":["html"]}],"unevaluatedProperties":false}. See ImageRequestCommon.
StatelessTemplateImageRequest
Schema rules: {"allOf":[{"$ref":"#/components/schemas/ImageRequestCommon"},{"type":"object","properties":{"html":{"type":"string","minLength":1,"maxLength":1048576},"data":{"type":"object"}},"required":["html","data"]}],"unevaluatedProperties":false}. See ImageRequestCommon.
SavedTemplateImageRequest
Schema rules: {"allOf":[{"$ref":"#/components/schemas/ImageRequestCommon"},{"type":"object","properties":{"template_id":{"$ref":"#/components/schemas/TemplateId"},"version":{"type":"integer","minimum":1},"data":{"type":"object"},"size_id":{"type":["string","null"],"pattern":"^[a-z0-9][a-z0-9-]{0,39}$","description":"Selects one saved size of the template instead of the original. The image width and height must match the size exactly."}},"required":["template_id","data"]}],"unevaluatedProperties":false}. See ImageRequestCommon, TemplateId.
ImageRequest
Schema rules: {"oneOf":[{"$ref":"#/components/schemas/RawImageRequest"},{"$ref":"#/components/schemas/StatelessTemplateImageRequest"},{"$ref":"#/components/schemas/SavedTemplateImageRequest"}]}. See RawImageRequest, StatelessTemplateImageRequest, SavedTemplateImageRequest.
ImageJob
| Field | Required | Description and type |
|---|---|---|
reference | No | Your reference, preserved in the job, history, and webhook. Omitted when not supplied. {"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]+$"} |
id | Yes | {"type":"string","pattern":"^image_[0-9a-f]{32}$"} |
status | Yes | {"type":"string","enum":["queued","running","succeeded","failed","cancelled"]} |
created_at | Yes | {"type":"string","format":"date-time"} |
finished_at | Yes | {"type":["string","null"],"format":"date-time"} |
template | Yes | The exact saved template version this job rendered. Null for raw HTML or an inline stateless template. {"type":["object","null"],"properties":{"id":{"$ref":"#/components/schemas/TemplateId"},"version":{"type":"integer","minimum":1}},"required":["id","version"],"additionalProperties":false}. See TemplateId |
credit | Yes | What happened to the credit this job holds: "reserved" while it is queued or running, "settled" once it succeeded, "released" once it failed or was cancelled, "free" when it is the account's free first file and costs no credit, and "none" when the job was never billed. {"type":"string","enum":["reserved","settled","released","free","none"]} |
artifact | Yes | {"type":["object","null"],"properties":{"media_type":{"type":"string","enum":["image/png","image/jpeg","image/webp"]},"byte_size":{"type":"integer","minimum":0},"sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"},"expires_at":{"type":"string","format":"date-time","description":"When retention ends and the file stops being served."},"removed_reason":{"type":["string","null"],"enum":["expired","deleted",null]}},"required":["media_type","byte_size","sha256","expires_at","removed_reason"],"additionalProperties":false} |
error | Yes | {"type":["object","null"],"properties":{"category":{"type":"string","enum":["invalid_input","unsafe_asset","resource_limit","timeout","renderer_failure","internal_failure"]},"code":{"type":"string"}},"required":["category","code"],"additionalProperties":false} |
download | Yes | {"type":["object","null"],"properties":{"url":{"type":"string"},"expires_at":{"type":"string","format":"date-time"}},"required":["url","expires_at"],"additionalProperties":false} |
Schema rules: {"type":"object","additionalProperties":false}.
ImageHistoryItem
One job in a bounded account history: the subset of ImageJob a history page lists. Never carries a download link, since a history page can name many jobs at once.
| Field | Required | Description and type |
|---|---|---|
reference | No | Your reference, preserved in the job, history, and webhook. Omitted when not supplied. {"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]+$"} |
id | Yes | {"type":"string","pattern":"^image_[0-9a-f]{32}$"} |
status | Yes | {"type":"string","enum":["queued","running","succeeded","failed","cancelled"]} |
created_at | Yes | {"type":"string","format":"date-time"} |
finished_at | Yes | {"type":["string","null"],"format":"date-time"} |
template | Yes | The exact saved template version this job rendered. Null for raw HTML or an inline stateless template. {"type":["object","null"],"properties":{"id":{"$ref":"#/components/schemas/TemplateId"},"version":{"type":"integer","minimum":1}},"required":["id","version"],"additionalProperties":false}. See TemplateId |
credit | Yes | What happened to the credit this job holds: "reserved" while it is queued or running, "settled" once it succeeded, "released" once it failed or was cancelled, "free" when it is the account's free first file and costs no credit, and "none" when the job was never billed. {"type":"string","enum":["reserved","settled","released","free","none"]} |
artifact | Yes | {"type":["object","null"],"properties":{"media_type":{"type":"string","enum":["image/png","image/jpeg","image/webp"]},"byte_size":{"type":"integer","minimum":0},"sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"},"expires_at":{"type":"string","format":"date-time","description":"When retention ends and the file stops being served."},"removed_reason":{"type":["string","null"],"enum":["expired","deleted",null]}},"required":["media_type","byte_size","sha256","expires_at","removed_reason"],"additionalProperties":false} |
error | Yes | {"type":["object","null"],"properties":{"category":{"type":"string","enum":["invalid_input","unsafe_asset","resource_limit","timeout","renderer_failure","internal_failure"]},"code":{"type":"string"}},"required":["category","code"],"additionalProperties":false} |
Schema rules: {"type":"object","additionalProperties":false}.
ImageHistoryPage
One bounded page of an account's image history, newest first.
| Field | Required | Description and type |
|---|---|---|
items | Yes | {"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/ImageHistoryItem"}}. See ImageHistoryItem |
next_cursor | Yes | Pass as the cursor query parameter to read the next page. Null on the last page. {"type":["string","null"],"maxLength":128} |
Schema rules: {"type":"object","additionalProperties":false}.
Shared errors, headers, and authentication schemes are in shared types and security.