Schemas
Uuid
A UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
Locale
^[a-z]{2,3}(-[A-Z]{2…A BCP 47 language tag. Content is generated and stored in one of two locales today, written as a bare language code with no region subtag: en or de. The pattern also admits a region subtag (en-GB) so that regional content can be added without a breaking change, but no such locale exists yet: a request naming one matches nothing, and no response carries one. Which locale your content is generated in comes from your tenant's settings; you do not choose it per request.
AssetRef
^image:[0-9a-fA-F]{8…A reference to one image embedded in a suggestion, written as the literal prefix image: followed by that image's id — for example image:019a9634-bcf0-7a92-b6e0-7b1d48f35026. Only the image: prefix exists today; more may be added, so reject a reference whose prefix you do not recognise rather than assuming it names an image.
Treat the whole string as opaque: store it and pass it back unchanged, and never split it, parse the id out of it, or build one from an id you hold. It is safe to keep for as long as you keep the suggestion — the reference itself does not expire and does not change when the image's URL is minted again.
A reference is scoped to the tenant that owns the suggestion it came from. Exchange it for a URL you can load with POST /v1/assets/urls, which rejects the whole batch if any reference is malformed, unknown, or owned by another tenant.
CollectionMeta
next_cursorPass this back as cursor to fetch the next page. Null when this is the last page — that is the only reliable end-of-listing signal, so keep paging until you see it rather than stopping on a page shorter than limit.
Every endpoint returning a collection carries this field, but the three that cannot paginate — GET /v1/inspiration, GET /v1/segments and GET /v1/use-cases — always return null and take no cursor: they return everything in one response.
The cursor records a position, not a snapshot. Items written while you are paging can be missed or repeated, so match on id if you are accumulating results.
ErrorObject
codeMachine-readable error code. Branch on this, never on message. New codes are added over time, so treat one you do not recognise as a generic failure of its HTTP status rather than rejecting the response.
messageA fixed, human-readable summary written for a developer reading a log. It is not localised and is not written for an end user, so do not put it in front of one, and never parse it — the same code keeps its meaning while the wording may be reworded at any time.
Extra machine-readable detail about the failure. Absent on most errors; its shape depends on code, so read it only once you have matched a code you know. validation_error puts the underlying schema failures under a validation array, and image_not_applicable names the offending request field under field.
ErrorEnvelope
The problems that caused this response. Every error body has this shape, whatever the status.
Exactly one entry is sent today, so read errors[0] — but the array is the contract, and more entries may be sent later, so do not assume a length of one when parsing. Several schema failures in one request do not become several entries: they arrive together inside the single entry's details.validation.
TokenRequest
grant_typeclient_idclient_secretTokenResponse
access_tokenAn opaque bearer token, sent on later requests as Authorization: Bearer <token>. It is a random value behind a fixed wer_token_ prefix, which exists so that a leaked token is recognisable to secret scanners — it carries no claims, so there is nothing to parse out of it and neither its length nor its alphabet is part of this contract. A token is valid for exactly one tenant, the one its credential belongs to.
token_typeexpires_inHow many seconds this token stays valid, counted from the moment it was issued. 900 under normal conditions, but read it rather than hard-coding it: a token is also cut short by its credential's own expiry, so the last token before a credential lapses comes back with a smaller number.
Exchanging again returns an additional token rather than replacing the one you hold, so a token already in flight keeps working. Revoking the credential, by contrast, takes effect immediately on every token issued from it.
TenantSummary
idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
nameSetActiveTenantRequest
tenant_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
Me
Null until a tenant has been chosen for this session.
Every tenant you belong to.
SegmentSummary
idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
nameSegment
idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
namedescriptionA short description of the segment.
SegmentCollection
Pagination metadata. There is no total count and no page number — a listing is walked forwards with the cursor until next_cursor is null, and cannot be jumped into part-way.
UseCaseSummary
idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
nameUseCaseCollection
Pagination metadata. There is no total count and no page number — a listing is walked forwards with the cursor until next_cursor is null, and cannot be jumped into part-way.
EmbeddedImageAsset
typeOnly images are supported today.
ref^image:[0-9a-fA-F]{8… · requiredA reference to one image embedded in a suggestion, written as the literal prefix image: followed by that image's id — for example image:019a9634-bcf0-7a92-b6e0-7b1d48f35026. Only the image: prefix exists today; more may be added, so reject a reference whose prefix you do not recognise rather than assuming it names an image.
Treat the whole string as opaque: store it and pass it back unchanged, and never split it, parse the id out of it, or build one from an id you hold. It is safe to keep for as long as you keep the suggestion — the reference itself does not expire and does not change when the image's URL is minted again.
A reference is scoped to the tenant that owns the suggestion it came from. Exchange it for a URL you can load with POST /v1/assets/urls, which rejects the whole batch if any reference is malformed, unknown, or owned by another tenant.
urlA signed, short-lived URL for the image, or null if you did not ask for one. It is null by default, and on every operation that has no expand parameter — including GET /v1/suggestion-sets, GET /v1/suggestion-sets/{suggestionSetId} and GET /v1/suggestions/{suggestionId} — so null here means "not requested", never "this image is missing". Mint one from ref with POST /v1/assets/urls when you need to render.
When present it carries the same warnings as a minted URL: it is a credential in its query string, load it unauthenticated, pass it around intact, and never store or log it.
url_expires_atWhen the URL stops working, 15 minutes after it was minted; null whenever url is null. Past this the image 403s and you mint again from ref, so do not hold an expanded response open on a screen and expect its images to keep loading.
alt_textGenerated accessibility text describing the image, in the content's locale. Always present and never empty. Use it as the alt attribute; it describes the picture and is not a caption to display alongside it.
SocialSingleImagePayload
bodyAn image inside a suggestion's content. url and url_expires_at are filled in only when you ask for expand=asset_urls; otherwise exchange ref for a URL with POST /v1/assets/urls.
SuggestionKind
The shape of a suggestion's content. New kinds may be added, so ignore any kind you do not recognise.
LineageSuggestionSummary
idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
The set this earlier suggestion belongs to; null if it belongs to a thread but no set (a refinement).
kindThe content shape this earlier suggestion had.
variant_nocreated_atinstructionThe instruction that produced this suggestion from the one before it; null for the suggestion the history starts from.
Suggestion
idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
The set this suggestion belongs to; null if it belongs to a thread but no set (a refinement).
kindTells you which payload shape to expect.
variant_noThis variant's position within its set; use it for a stable display order.
The suggestion this one was refined from, or null if it was not produced by a refinement.
rationaleWhy this content was suggested, or null when there is no explanation.
The content itself. Its shape is decided entirely by kind: email carries subject and body, social_single_image carries body and an asset. Switch on kind rather than probing for a property, and skip a suggestion whose kind you do not recognise — a future kind will bring a payload shape this list does not contain.
Text is plain text with real line breaks, not HTML or Markdown. Render it with the whitespace preserved; nothing in it is escaped for you.
The suggestions this one was refined from, closest first and the original last. Present only when you pass expand=lineage.
SuggestionSet
idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
Always set today; reserved to be null for future content that is not tied to a use-case.
locale^[a-z]{2,3}(-[A-Z]{2… · requiredA BCP 47 language tag. Content is generated and stored in one of two locales today, written as a bare language code with no region subtag: en or de. The pattern also admits a region subtag (en-GB) so that regional content can be added without a breaking change, but no such locale exists yet: a request naming one matches nothing, and no response carries one. Which locale your content is generated in comes from your tenant's settings; you do not choose it per request.
versionThe catalogue position of your tenant's ready-made (precompute) content for this segment, use case and locale, increasing each time that content is generated again. Null for content generated from your own instructions (steered) or from an edit (refinement), which has no place in that sequence.
created_atWhen the set was written, which is when its generation finished rather than when it was asked for. It is also what the listing is ordered by, newest first.
originHow the set was produced. precompute is your tenant's ready-made content, generated on a schedule. steered is a generation from your own instructions.
refinement is legacy and is no longer produced. An edit of an existing suggestion used to be wrapped in a set of its own holding a single variant; it now appends a turn to a thread instead, so a refinement writes no set and reports a suggestion_id rather than a suggestion_set_id. The value remains because sets written before that change still carry it and are still readable — handle it when reading, and expect no new set to arrive with it.
The user who asked for the generation. Null only for ready-made (precompute) content, which no individual user requested.
The generation run that produced this set; null for ready-made content, which has no run.
steering_textThe instructions the set was generated from; null unless origin is steered.
The set's variants, every one of them — a set is written complete, so this is never partially filled and never paginated. How many there are depends on how the set was generated: a brief produces the variant_count you asked for, while ready-made content produces a per-use-case count you do not control. Order is not guaranteed; sort by variant_no for a stable display order.
SuggestionSetCollection
Pagination metadata. There is no total count and no page number — a listing is walked forwards with the cursor until next_cursor is null, and cannot be jumped into part-way.
ThreadTurn
versionThis turn's position in the thread, counting from one, with the newest turn equal to the thread's turn_count. It counts steps through a refinement history, one turn at a time.
Do not confuse it with a suggestion's variant_no, which numbers alternatives generated side by side from one brief, or with a suggestion-set's version, which counts regenerations of ready-made content. The three are unrelated.
suggestion_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
generated_atinstructionThe instruction that produced this turn from the one before it; null for the thread's first turn.
Thread
idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
nameA generated name for the thread; null until one has been generated for it.
Always set today; reserved to be null for future content that is not tied to a use-case.
locale^[a-z]{2,3}(-[A-Z]{2… · requiredA BCP 47 language tag. Content is generated and stored in one of two locales today, written as a bare language code with no region subtag: en or de. The pattern also admits a region subtag (en-GB) so that regional content can be added without a breaking change, but no such locale exists yet: a request naming one matches nothing, and no response carries one. Which locale your content is generated in comes from your tenant's settings; you do not choose it per request.
The user who started the thread. Null only for ready-made (precompute) content, which no individual user requested.
The thread's current newest turn.
root_originWhere the thread started: precompute for ready-made content, steered for a generation from a brief, or user_draft for content you wrote yourself. A thread's root is never a refinement.
turn_countHow many turns the thread has, always at least one — a thread and its first turn are written together. It is also the version of the newest turn, so a thread whose turn_count has grown since you last read it has been refined.
created_atWhen the thread's first turn was generated or saved.
updated_atWhen the thread last changed. Two unrelated things move it: a refinement appending a turn, and a rename. It is therefore not a reliable "new content" signal on its own — compare turn_count or head_suggestion_id for that.
The thread's turns, oldest first. Present only on GET /v1/threads/{threadId}.
ThreadCollection
Pagination metadata. There is no total count and no page number — a listing is walked forwards with the cursor until next_cursor is null, and cannot be jumped into part-way.
GenerationTriggerRequest
segment_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
use_case_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
SteeredGenerationRequest
typesegment_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
use_case_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
steering_textWhat you want the generated content to say or do — the product calls this the brief. It is instructions for the generator, not content to publish: the generated copy is written for the segment you named and will not quote this text back.
Counted in Unicode characters, not bytes, and the same 4000-character ceiling is enforced again after the request passes schema validation, as 422 input_too_long. Text that is empty once trimmed is refused as 422 input_rejected.
variant_countHow many alternative pieces of content to generate from the one brief. They arrive together in the run's suggestion-set, numbered variant_no 1 upwards. Omit it and one variant is generated.
This applies only to a brief. Ready-made (precompute) content is generated with a per-use-case count you do not control, so a ready-made set can hold more variants than the maximum here.
RefinementRequest
typeparent_suggestion_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
instructionHow to change the suggestion you are refining.
imageWhether to keep the image of the suggestion you are refining or generate a new one. Only social_single_image content carries an image; email content does not.
Sending this field at all when refining content that has no image is rejected with 422 image_not_applicable, even if you send reuse. Omit it entirely for text-only content.
Reading it back is not symmetrical: a refinement run echoes image in its request whatever you sent, so a refinement of text-only content comes back showing image: reuse even though sending that value would have been rejected. Read it as "no new image was generated", not as confirmation of something you sent.
GenerationRunRequest
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · type="steered_generation" · requires: segment_id, use_case_id, steering_text | |
| type = object · type="refinement" · requires: parent_suggestion_id, instruction |
typesegment_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
use_case_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
steering_textWhat you want the generated content to say or do — the product calls this the brief. It is instructions for the generator, not content to publish: the generated copy is written for the segment you named and will not quote this text back.
Counted in Unicode characters, not bytes, and the same 4000-character ceiling is enforced again after the request passes schema validation, as 422 input_too_long. Text that is empty once trimmed is refused as 422 input_rejected.
variant_countHow many alternative pieces of content to generate from the one brief. They arrive together in the run's suggestion-set, numbered variant_no 1 upwards. Omit it and one variant is generated.
This applies only to a brief. Ready-made (precompute) content is generated with a per-use-case count you do not control, so a ready-made set can hold more variants than the maximum here.
DraftEmailSuggestionRequest
kindsegment_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
use_case_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
DraftSuggestionRequest
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · kind="email" · requires: segment_id, use_case_id, payload |
kindsegment_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
use_case_idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
ThreadUpdateRequest
nameThe thread's new name. Leading and trailing whitespace is trimmed and any run of whitespace is collapsed to a single space before the name is stored, so the name you read back may differ from the one you sent.
Two further limits are checked after schema validation and are reported as 422 rather than 400: the trimmed name must be at most 60 characters and at most 8 words, both as thread_name_too_long. A short name can therefore still be rejected for having too many words. Line breaks and other control characters are refused as thread_name_not_plain_text, and a name that is empty once trimmed as thread_name_empty.
GenerationRun
idA UUID, lower-case and hyphenated. Identifiers minted by this API are UUIDv7, so sorting them as strings happens to order them by creation time — the listings rely on that, which is why they need no sort parameter. Do not depend on it yourself, and do not read a timestamp out of an id: ids from other systems may be a different version, and the guarantee is not part of this contract.
typeWhich request produced this run. steered_generation is the product's brief; refinement is an edit of an existing suggestion.
statuscreated_atWhen the run was accepted, not when it started generating. A run can sit at queued for a while, so do not measure generation time from here.
created_byThe user who submitted the run. Never null: a run can only be created by a signed-in user, so there is no unattributed run.
attempt_countHow many attempts have started. 0 on the run you get back from POST /v1/generation-runs, because nothing has picked it up yet.
A run that stalls returns to queued and is tried once more, so watching this go up while status goes back to queued is normal progress, not a failure. Only status: failed is terminal — do not give up on a retry.
A generation request. type says which of the two shapes you are sending. The locale comes from your tenant's settings.
steered_generation is the request behind what the product calls a brief: you supply the instructions. refinement edits one suggestion that already exists. Ready-made content — the Inspiration catalogue — is deliberately not a value here: it is produced on a schedule rather than requested, and is read through GET /v1/inspiration.
The suggestion-set a succeeded steered_generation run produced. Always null for a refinement run — see suggestion_id.
The turn a succeeded refinement run appended to its thread. Always null for a steered_generation run — see suggestion_set_id.
Why the run failed, if it fails.
GenerationRunCollection
Pagination metadata. There is no total count and no page number — a listing is walked forwards with the cursor until next_cursor is null, and cannot be jumped into part-way.
AssetUrlMintRequest
refsMintedAssetUrl
ref^image:[0-9a-fA-F]{8… · requiredThe reference you sent, echoed back so you can match entries up.
urlA signed, short-lived HTTPS URL for the image, served from the content delivery network in front of the private asset store. Load it with a plain unauthenticated GET — do not send your session cookie or access token to it.
The whole URL is a single credential: the query string carries the signature (Expires, Signature and Key-Pair-Id) that grants access, so it must be passed around intact. Stripping, reordering or re-encoding any query parameter invalidates it and the delivery network answers 403. The path and host are an implementation detail and can change without notice, so never parse either, and never build a URL for an image yourself.
Because the URL is a credential, do not persist it, log it, or put it anywhere a different user could read it. Store the ref instead and mint a fresh URL when you next need to render the image.
url_expires_atWhen the URL stops working, 15 minutes after it was minted. After this the delivery network answers 403 and you mint a new URL from the same ref. Mint at the moment you render, not ahead of time, and do not cache the URL past this instant.
MintedAssetUrlCollection
Pagination metadata. There is no total count and no page number — a listing is walked forwards with the cursor until next_cursor is null, and cannot be jumped into part-way.