Steering and refinement
Ready-made "inspiration" content is generated on a schedule, but usually end-users want to provide a brief to guide the generation or even send draft content to be refined with behavioural AI.
NOTE: this path needs a signed-in user token
POST /v1/generation-runs
accepts a session cookie or user OAuth token only. Every run is attributed to the person who asked
for it and other users can't read it. A machine-to-machine access token identifies an integration rather
than a user, so an access token cannot create a generation run.
Creating a generation run
There are two possible request shapes based on the type you select.
steered_generation is a fresh generation for one segment and use case.
You name segment_id and use_case_id, and steering_text carries th euser's
instructions — what they want the content to say or do. It should be written like a
brief to an agency or colleague. The output is a suggestion_set, with variant_count
allowing for up to three variants in one set to be generated.
refinement takes a suggestion that already exists and adjusts it, based on a user prompt. This can be done on:
- Auto-generated "inspiration" suggestions.
- "Steered-generation" suggestions from a user briefing.
- Custom suggestions added by users (a draft).
- Already refined suggestions, to iterate on changes.
The full requests are in the request body.
For example:
Code
The call returns 202 with an entity representing the run itself, not the final content.
It has an id and status, intially queued. The run will be picked up in the background
and move through statuses, until it is done or it it fails. See below for the states.
Creating runs is rate-limited. A 429 carries the code rate_limited and a
Retry-After header to instruct you when the endpoint is available again.
A 404 means the segment, use case or parent suggestion does not exist or is
unaccesible to you.
Poll the run
The generation run has four states:
status | Meaning |
|---|---|
queued | Accepted, waiting to be processed. |
running | Generation has started but not finished. |
succeeded | Terminal. Exactly one of suggestion_set_id or suggestion_id is now set, depedning if it is a steered_generation or a refinement run. |
failed | Terminal. error is now set. |
States are not necessarily linear. A run that stalls goes back
to queued and is retried, so you can see running become queued
again. attempt_count tells you how many attempts have started; more than one
means the run was retried. Treat only succeeded and failed as terminal states.
We allow up to ten minutes per attempt before a run is considered failed. We do not enforce a specific a polling interval, but use a sensible backoff approach to avoid issues. For example:
- 10s for 3 minutes
- 30s for 5 minutes
- 1m for 13 minutes
After this, either an error should have been produced.
Read what the run produced
A succeeded run sets exactly one of two fields, and which one depends on
its type:
- A
steered_generationrun setssuggestion_set_idand leavessuggestion_idnull. Fetch the set withGET /v1/suggestion-sets/{suggestionSetId}. - A
refinementrun setssuggestion_idand leavessuggestion_set_idnull. A refinement produces one turn appended to a thread, with no set of its own. Fetch it withGET /v1/suggestions/{suggestionId}.
A set appears only once it is complete. There is no half-finished suggestion-set to handle.
The set carries the segment, use case and locale it was generated for, an
origin of steered, the generation_run_id that produced it, and the
steering_text it was generated from — so a set is self-describing and you do
not need to keep the request around to know what produced it. Its variants are
in suggestions.
A single suggestion
Each suggestion has a kind, and kind tells you which payload shape to
expect — an email payload is not shaped like a social_single_image one.
New kinds may be added, so ignore any kind you do not recognise rather
than failing on it.
variant_no is the variant's UI position in the set; use it for a stable display
order rather than relying on array position. rationale explains why the
content was suggested, based on behavioural AI. parent_suggestion_id is set when
the suggestion came from a refinement, and null otherwise. Any image sits inside
the payload as an opaque reference — see Asset URLs for turning it into something you can load.
Follow the suggestion version history
A refinement does not replace the suggestion it was refined from; they both
continue to exist, linked by parent_suggestion_id. Refine three times and
you have a chain four suggestions long, creating a "lineage".
You can walk that chain one parent_suggestion_id at a time, but there is a
single-call version. Pass expand=lineage to
GET /v1/suggestions/{suggestionId}
and the response gains a lineage array holding every earlier suggestion,
closest first and the original last:
Code
Each lineage entry is a short summary, not the full suggestion. It only contains an id, the
set it belongs to, its kind, its variant_no, when it was created, and the instruction
that produced it from the one before it, alloing a "history" UI to be created.
suggestion_set_id is null on a lineage entry that doesn't belong to a set,
such as a draft suggestion added by the user.
The same history as a conversation
A thread is another way to view a list of refined suggestions.
GET /v1/threads/{threadId}
returns the thread with its turns, each carrying a version, the
suggestion_id for that turn, and the instruction that produced it,
null for the first turn which would have been a suggestion set or a draft.
Use lineage when you have a suggestion and want its ancestry. Use the thread
when you want the whole conversation, including turns newer than the
suggestion you started from. head_suggestion_id is the thread's current
newest turn, and root_origin says where it began: precompute (inspriration),
steered (a briefing), or user_draft.
The thread id is stable across refinements, so a link to a thread keeps
resolving after a further refinement. GET /v1/threads
lists all threads for the user.
Refine, rather than starting over
To iterate on a result, a user refines the suggestion. This will keep the segment, use case, locale and everything the original established, and allow tracking of the history.
Code
The image field, available if the use-case being refined has an image component,
decides what happens to the image: reuse keeps the original's, regenerate makes
a new one. It defaults to reuse. Asking to regenerate for content that has no image
fails with image_not_applicable. This is to allow cheaper regeneration of text components,
like a caption, while no spending tokens on image regeneration.
The run comes back the same way as a steered run, and you poll it the same way. On success it
sets suggestion_id, not suggestion_set_id, as a refinement only ever generates one new iteration.
A user can also bring their own starting point. Today only a draft email can be added,
other usecases will be available in future.
POST /v1/suggestions
saves a draft the user wrote as a suggestion, and it becomes the root
of a refinement history they can steer exactly like generated content.
This allows the addition of behavioral AI to content that is already in progress.
When the run fails
A brief that is malformed fails at the create call, synchronously, with 422. A brief that is accepted can still fail afterwards — generation is a background job, and it reports its own outcome on the generation-run resource rather than on an HTTP response.
error.code | What happened | What to do |
|---|---|---|
generation_failed | Something went wrong while generating. | Retry the run as submitted. |
generation_timeout | It ran out of time. | Retry, and consider a shorter brief or fewer variants. |
steering_disabled | Generating from your own instructions was switched off while the run was waiting. | Stop retrying. This is an organisation setting; ask WeR. |
invalid_parent | The suggestion being refined is no longer available. | Stop retrying. Re-read the thread and refine its current head (threads cannot branch). |
image_not_applicable | You asked to regenerate an image for content that has none. | Stop retrying. Resubmit without the image field. |
output_rejected | The generated content failed a safety check. | Do not retry unchanged. Reword the brief. |
error.message is a human-readable summary meant for a person reading a log
or a support ticket.
The failed run stays readable, with its request attached, so
GET /v1/generation-runs
gives you an audit trail of what was asked for and what became of it. Note
that a user sees only their own runs.
A safety rejection can also happen up front rather than on the run: a brief
refused before it is accepted comes back as 422 with input_rejected, and
oversized or non-text input as input_too_long or input_invalid_encoding.