# 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`](/reference/generation-runs#generate-content-from-your-own-instructions)
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](/reference/generation-runs#generate-content-from-your-own-instructions/request-body).

For example:

```bash
curl -sS -X POST "$WER_API_BASE/v1/generation-runs" \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $USER_TOKEN" \
  -d '{
        "type": "steered_generation",
        "segment_id": "'"$SEGMENT_ID"'",
        "use_case_id": "'"$USE_CASE_ID"'",
        "steering_text": "Content for the launch of our new low deposit mortgage product",
        "variant_count": 3
      }'
```

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_generation` run sets `suggestion_set_id` and leaves
  `suggestion_id` null. Fetch the set with
  [`GET /v1/suggestion-sets/{suggestionSetId}`](/reference/suggestion-sets#fetch-a-suggestion-set-by-id).
- A `refinement` run sets `suggestion_id` and leaves `suggestion_set_id` null.
  A refinement produces one turn appended to a thread, with no set of its own.
  Fetch it with
  [`GET /v1/suggestions/{suggestionId}`](/reference/suggestions#fetch-a-single-suggestion-by-id).


**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](/guides/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}`](/reference/suggestions#fetch-a-single-suggestion-by-id/query-parameters)
and the response gains a `lineage` array holding every earlier suggestion,
**closest first and the original last**:

```bash
curl -sS -G "$WER_API_BASE/v1/suggestions/$SUGGESTION_ID" \
  -b "$WER_SESSION_COOKIE" \
  --data-urlencode 'expand=lineage'
```

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}`](/reference/threads#fetch-a-thread-by-id)
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`](/reference/threads#list-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.

```bash
curl -sS -X POST "$WER_API_BASE/v1/generation-runs" \
  -H 'Content-Type: application/json' \
  -b "$WER_SESSION_COOKIE" \
  -d '{
        "type": "refinement",
        "parent_suggestion_id": "'"$SUGGESTION_ID"'",
        "instruction": "Warmer opening, and add more information about the mortgage product.",
        "image": "reuse"
      }'
```

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`](/reference/suggestions#save-content-you-wrote-yourself)
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`](/reference/generation-runs#list-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`.
