Image renders

Create PNG, JPEG, or WebP images from HTML or saved templates, then check each render and download the result.

Use with your agent

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.

ParameterLocationRequiredRule
Idempotency-KeyheaderNo{"type":"string","minLength":1,"maxLength":255,"pattern":"^[ -~]+$"}

Request body: application/json, ImageRequest.

StatusMeaningBody
200The job is terminal. Check status for succeeded, failed, or cancelled; HTTP 200 does not mean the render succeeded.application/json: ImageJob
202The image job is durable and remains queued or running.application/json: ImageJob
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
402The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
409The one error envelope every backend response uses.application/json: ErrorEnvelope
413The one error envelope every backend response uses.application/json: ErrorEnvelope
415The one error envelope every backend response uses.application/json: ErrorEnvelope
422The one error envelope every backend response uses.application/json: ErrorEnvelope
429The one error envelope every backend response uses.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope
500The one error envelope every backend response uses.application/json: ErrorEnvelope
503The 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.

ParameterLocationRequiredRule
qqueryNoCase-insensitive search of references, template names, and file types. {"type":"string","maxLength":200,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]*$"}
limitqueryNo1 to 100. Defaults to 20. A value outside this range is clamped rather than refused. {"type":"integer","default":20}
cursorqueryNoAn opaque value from an earlier page's next_cursor. A cursor that cannot be read is refused with 400. {"type":"string","maxLength":128}
StatusMeaningBody
200One page of the account's image history.application/json: ImageHistoryPage
400The limit parameter was not a whole number, the cursor could not be read, or the query held an unknown parameter.application/json: ErrorEnvelope
401The request did not carry a valid, active API key.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope
500An 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.

ParameterLocationRequiredRule
idpathYes{"type":"string","pattern":"^image_[0-9a-f]{32}$"}
StatusMeaningBody
200The image job's current status.application/json: ImageJob
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope
500The 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.

ParameterLocationRequiredRule
idpathYes{"type":"string","pattern":"^image_[0-9a-f]{32}$"}
StatusMeaningBody
200The image job after file or history removal.application/json: ImageJob
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
409The one error envelope every backend response uses.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope
500The 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.

ParameterLocationRequiredRule
idpathYes{"type":"string","pattern":"^image_[0-9a-f]{32}$"}

Request body: application/json, FileExpiryChange.

StatusMeaningBody
200The job with its file's new end in artifact.expires_at.application/json: ImageJob
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
413The one error envelope every backend response uses.application/json: ErrorEnvelope
415The one error envelope every backend response uses.application/json: ErrorEnvelope
422The one error envelope every backend response uses.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope
500The one error envelope every backend response uses.application/json: ErrorEnvelope

Schemas

ImageRequestCommon

To receive finished image events, follow the webhook delivery guide.

FieldRequiredDescription and type
filenameNoDownload 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 ._-]*$"}
referenceNoYour reference, preserved in the job, history, and webhook. Omitted when not supplied. {"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]+$"}
retention_daysNoDays 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_idNoSelect 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}$"}
imageYes{"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}
javascriptNo{"type":"object","properties":{"mode":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}
waitNo{"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

FieldRequiredDescription and type
referenceNoYour reference, preserved in the job, history, and webhook. Omitted when not supplied. {"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]+$"}
idYes{"type":"string","pattern":"^image_[0-9a-f]{32}$"}
statusYes{"type":"string","enum":["queued","running","succeeded","failed","cancelled"]}
created_atYes{"type":"string","format":"date-time"}
finished_atYes{"type":["string","null"],"format":"date-time"}
templateYesThe 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
creditYesWhat 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"]}
artifactYes{"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}
errorYes{"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}
downloadYes{"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.

FieldRequiredDescription and type
referenceNoYour reference, preserved in the job, history, and webhook. Omitted when not supplied. {"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]+$"}
idYes{"type":"string","pattern":"^image_[0-9a-f]{32}$"}
statusYes{"type":"string","enum":["queued","running","succeeded","failed","cancelled"]}
created_atYes{"type":"string","format":"date-time"}
finished_atYes{"type":["string","null"],"format":"date-time"}
templateYesThe 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
creditYesWhat 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"]}
artifactYes{"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}
errorYes{"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.

FieldRequiredDescription and type
itemsYes{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/ImageHistoryItem"}}. See ImageHistoryItem
next_cursorYesPass 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.

Ready to make your first file?

Start in the playground with a gallery template, or get an API key and send your first request from your own terminal.