Endpoints
default_subject, default_from_name, and so on). Once a template is approved, POST /v1/emails with template_id copies its HTML into a new email and inherits those defaults. Like emails, templates start as a draft and accumulate versions as you iterate.
AI generation is asynchronous. Creating with a
prompt or posting a message returns 202 Accepted with a generation_id, and you poll the generation endpoint to track progress. Only one generation can run per template at a time.Idempotency
POST /v1/email-templates, POST /v1/email-templates/{templateId}/messages, and POST /v1/email-templates/{templateId}/save-code accept an optional Idempotency-Key header. Retrying with the same key replays the stored result instead of creating a duplicate. If a request with the same key is still being processed, the API returns 409 Conflict with error_code: "idempotency_in_progress".
Create a Template
Request Body
Provide
code to upload your own HTML, or prompt to generate it with AI (202 Accepted). With neither, you get a blank scaffold branded from your workspace. Both code-based and blank creates return 201 Created with the template object.
Request Example
Response Example
202 Accepted
Poll a Generation
status moves through generating to completed or failed, with the same shape as email generations: completed generations include the version they produced plus content_path and plain_text_path under /v1/email-templates/...; failed ones return error_code: "generation_failed" and an error_message.
Retrieve a Template
200 OK
status is draft or approved. source_type is upload for API-created templates, or email for templates saved from an existing email (then source_email_id points to it).
Read Content
text/html content type — not JSON.
Plain Text
template_id instead of email_id: GET returns the auto-derived text, PUT overrides it (body: {"content": "..."}, up to 1 MB), and DELETE reverts to the auto-derived version. PUT and DELETE require a completed generation, otherwise they return 400 Bad Request. Iterating the HTML after an override flips stale_after_html_iteration to true.
Iterate With AI
prompt (1-10000 characters) with the edit instruction, plus optional asset_ids naming template assets to make available to the generation. Returns 202 Accepted with a generation_id to poll. Starting a second generation while one is running returns 409 Conflict with error_code: "generation_in_progress"; an unknown asset returns 409 with error_code: "asset_not_found".
Save Code Directly
{"code": "..."} with the full replacement HTML (up to 2 MB) to create a new version synchronously — 200 OK with the new generation_id and version. Saving code does not unapprove an approved template. Blocked while a generation is running.
Update Metadata
code and prompt: name, default_subject, default_preview_text, default_from_name, default_from_address, default_reply_to_email. Omit a field to leave it unchanged; pass null to clear nullable fields. Returns the updated template object.
Clone a Template
draft attributed to the API principal. The clone is never approved. Optionally send {"name": "..."}; the name defaults to "{original name} (copy)". Returns 201 Created with the new template object. Accepts an optional Idempotency-Key header.
Approve and Unapprove
default_* fields are optional — but the template must have at least one completed generation, otherwise the API returns 409 Conflict with error_code: "template_not_ready".
Optionally send {"version": <n>} as an optimistic-concurrency precondition — a mismatch with the newest completed version (normally equal to current_version) returns 409 Conflict with error_code: "approval_version_stale". While a generation is running, an approve without version is refused with 409 Conflict and error_code: "generation_in_progress"; passing version explicitly still approves that already-completed version mid-generation.
Unapproving is never blocked: emails already seeded from the template are unaffected, but new emails can no longer be created from it until it is approved again. Both endpoints return the updated template object.
List Templates
status (any, draft, approved), sort_by, sort_order, limit, and cursor. Responses contain templates and next_cursor. List items are the template object minus latest_generation_status and the content_path / plain_text_path fields — fetch HTML per template via GET /v1/email-templates/{templateId}/content.
Delete a Template
200 OK with {"template_id": "...", "deleted": true}. Unlike emails, deletion is never blocked — emails already seeded from the template keep their own copy of the content.
Common Errors
Next: Create Emails
Seed a new email from your approved template and send it with blasts and flows.