> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sunra.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Response identifiers and tracking

Every LLM request Sunra accepts gets a Sunra-issued id — the same one whether you called Chat Completions, Messages, Responses, or Embeddings. This page documents where that id shows up, how to use it to reconcile a request after the fact, and a breaking change to where the `id` field in the response body points.

## The `x-sunra-prediction-id` header

Every response from `api-llm.sunra.ai` — success, failure, streaming or not — carries an `x-sunra-prediction-id` header once the request has cleared authentication, been routed to a model, and had its record durably persisted:

```bash cURL theme={null}
curl -sD - https://api-llm.sunra.ai/v1/chat/completions \
  -H "Authorization: Bearer <SUNRA_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemini-2.5-flash",
    "messages": [{ "role": "user", "content": "Hello" }]
  }' -o /dev/null
```

```
HTTP/2 200
x-sunra-prediction-id: chatcmpl_hGRKYZJjNDvu474VJSZGdQyX
content-type: application/json
...
```

<Note>
  The header name says `prediction` rather than `completion` — it reuses the same header Sunra's multimodal API sets on predictions. Both APIs identify a unit of work the same way; the name is not specific to images or video.
</Note>

The header is exposed for browser callers too (`Access-Control-Expose-Headers` includes it), so `fetch` in a browser can read it without a server-side proxy.

**When it's absent.** Persisting the completion record is the one thing that has to happen before an id exists to report. If that persistence step itself fails — a rare internal error, distinct from the request or the model failing — the response is a `5xx` with no `x-sunra-prediction-id` header, because there is nothing to report yet. Every other failure (rejected input, an upstream provider error, insufficient credit, a stream that gets aborted) happens after the record exists, so the header is present on those.

## Errors carry the id too

Once the completion record exists, every error response also carries the id in the body — not just the header. This covers 4xx/5xx responses, a non-2xx JSON body from the upstream provider, an in-stream error event on an otherwise-`200` SSE response, and the [stream abort frame](/llm/limits#the-abort-frame).

Where the id lands depends on the shape of the error body, because Sunra never restructures an error shape to fit a new field — it slots the id into whichever shape is already there:

| Error body shape                                                                                                                                                      | Where the id appears                                            |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| An object-shaped `error` — `{"error": {"message": ..., "type": ...}}` and its variants across Chat Completions, Messages, Responses, and their streaming error events | `error.prediction_id`, alongside `error.message` / `error.type` |
| Any other recognized error shape — `error` as a plain string, a body with only `detail`, a Responses-style `{"status": "failed", "error": {...}}` payload, and so on  | top-level `prediction_id`, alongside the existing fields        |

```json Object-shaped error theme={null}
{
  "error": {
    "message": "Insufficient credit to reserve for this request.",
    "type": "insufficient_credit",
    "prediction_id": "chatcmpl_hGRKYZJjNDvu474VJSZGdQyX"
  }
}
```

```json Non-object error shape theme={null}
{
  "detail": "The requested model does not support this input.",
  "prediction_id": "chatcmpl_hGRKYZJjNDvu474VJSZGdQyX"
}
```

The [stream abort frame](/llm/limits#the-abort-frame) gets the same treatment — the id sits inside the frame's own `error` object:

```
data: {"error":{"message":"Stream aborted by gateway (stream_lifetime_ceiling); partial output above is complete as delivered","type":"gateway_stream_aborted","code":"stream_lifetime_ceiling","prediction_id":"chatcmpl_hGRKYZJjNDvu474VJSZGdQyX"}}
```

As with the header, a pre-persistence failure carries no `prediction_id` anywhere in the body — there was never an id minted for that request.

## The `id` field is switching to a Sunra-issued value

<Warning>
  **Breaking change.** Effective date to be announced — this section will be updated with the exact cutover time once it ships. Watch for Sunra's migration notice before this lands; see [Migrating existing integrations](#migrating-existing-integrations) below.
</Warning>

Today, the `id` in a Chat Completions, Messages, or Responses body is the upstream model provider's own identifier, forwarded as-is. Its format varies by provider and by protocol — `chatcmpl-` on some Chat Completions providers, `msg_` on Messages, `resp_` on Responses, or something else entirely depending on who served the request.

After this change, `id` becomes the **same Sunra-issued value as the `x-sunra-prediction-id` header** — `chatcmpl_` followed by a 24-character identifier — on every protocol, including `/v1/responses` (which will no longer return a `resp_`-prefixed id):

| Endpoint               | Where `id` appears                                                                                                                        |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `/v1/chat/completions` | top-level `id` — non-streaming, and on every streamed chunk                                                                               |
| `/v1/messages`         | top-level `id` — non-streaming; `message_start.message.id` — streaming                                                                    |
| `/v1/responses`        | top-level `id` — non-streaming; `response.id` on `response.created`, `response.completed`, and every other response-carrying stream event |

**Only the envelope id changes.** Any `id` nested inside the generated content itself — a tool call id, a reasoning item id, a message item id inside `output` — is untouched. SSE's own protocol-level `id:` field (used for reconnection, distinct from anything inside the JSON payload) is also untouched.

The upstream provider's id is not discarded — it moves to `provider_response_id`, retrievable through [`GET /completions?completion_id=`](#looking-up-a-completion-later) below, where it always has been for reconciliation purposes.

## Embeddings: no `id` field

The Embeddings protocol has never had an `id` field, on Sunra or on the API it mirrors, and this change does not add one — introducing a field outside the documented response shape would itself be a breaking change for a strict response parser. For an embeddings request, the [`x-sunra-prediction-id` header](#the-x-sunra-prediction-id-header) is the only place to find the tracking id.

## Looking up a completion later

Use an id — from the header, or from the body `id` once the [switch](#the-id-field-is-switching-to-a-sunra-issued-value) above ships — to fetch the completion's full record:

```bash cURL theme={null}
curl "https://api.sunra.ai/v1/completions?completion_id=chatcmpl_hGRKYZJjNDvu474VJSZGdQyX" \
  -H "Authorization: Key <SUNRA_KEY>"
```

The fields most relevant to reconciliation:

| Field                                 | Description                                                                                                                                                             |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                  | The same Sunra-issued id, echoed back.                                                                                                                                  |
| `provider_response_id`                | The upstream model provider's own id for this completion — the value that used to be the body `id`, and remains available here regardless of when the request happened. |
| `status`, `model`, `usage`, `invoice` | The completion's status, model, token usage, and billing record.                                                                                                        |

This is the same endpoint whether the completion predates or postdates the `id` cutover — pass whichever id you have (a Sunra `chatcmpl_` id, or a provider-native id captured from a response before the switch) and it resolves the same record.

## Continuing a conversation: `previous_response_id`

On `/v1/responses`, pass the `id` you got back from a prior response as `previous_response_id` to continue that conversation:

```json theme={null}
{
  "model": "openai/gpt-5.1",
  "previous_response_id": "chatcmpl_hGRKYZJjNDvu474VJSZGdQyX",
  "input": "And in French?"
}
```

Send exactly the id Sunra gave you. The gateway resolves it to whatever the upstream provider needs internally, so continuation keeps working across the `id` field's [switch to a Sunra-issued value](#the-id-field-is-switching-to-a-sunra-issued-value) — you never need to know which upstream shape is behind a given response.

An id Sunra doesn't recognize — mistyped, belonging to a different organization, or one it never linked a continuation for — is rejected with `400 invalid_request_error` rather than silently starting a fresh conversation.

<Note>
  **Transition period.** Sunra also still accepts the upstream provider's own native response id (a pre-cutover `id` value, e.g. `resp_...`) in `previous_response_id`, so continuations started before the switch keep working. This fallback is temporary and will be removed once the migration window closes — send the id Sunra returns rather than relying on it.
</Note>

**If linking fails.** In rare cases the gateway cannot record the mapping needed to translate `previous_response_id` for one specific response (an internal write failure). That response still delivers normally and is billed normally — nothing about the conversation you already had is affected. The only consequence is narrow: a *follow-up* request that names that one response's id as `previous_response_id` gets `400 invalid_request_error` instead of continuing. Start a new conversation (omit `previous_response_id`) and carry on from there.

## Migrating existing integrations

This section is for callers who already parse or store the `id` from a Chat Completions, Messages, or Responses response, ahead of the [breaking `id` change](#the-id-field-is-switching-to-a-sunra-issued-value) above.

**Who is affected:**

* Anything that parses `id` to infer the upstream provider or model family — its format is provider-specific today; after the change every provider returns the same Sunra-issued shape.
* Anything that stores `id` and later looks it up in the *provider's own* dashboard, logs, or billing export — that value is still available, just no longer in the response body (see below).
* `/v1/responses` callers using the returned `id` as `previous_response_id` for multi-turn conversations. This keeps working — the gateway translates transparently, see [above](#continuing-a-conversation-previous_response_id) — but stop assuming the value looks like `resp_...`; treat it as an opaque Sunra token.

**What doesn't change:**

* `x-sunra-prediction-id` has carried the Sunra-issued value since it shipped and is unaffected by this cutover — a caller who already reads the header sees the same value the body now also carries.
* The upstream provider's id is still recorded and still retrievable, just relocated from the body to `provider_response_id` via [`GET /completions?completion_id=`](#looking-up-a-completion-later).

**Reconciling across the cutover.** Whichever value you have on hand — the header, or a stored body `id` from before or after the switch — [`GET /completions?completion_id=`](#looking-up-a-completion-later) resolves it to the full completion record, including the upstream provider's own id. Use it to bridge historical data that was keyed on the old provider-native ids.

**Timeline.**

| Change                                                                                            | Status                                                                                 |
| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `x-sunra-prediction-id` header; `error.prediction_id` / top-level `prediction_id` on error bodies | Shipped — non-breaking                                                                 |
| body `id` becomes the Sunra-issued value                                                          | Breaking — date to be announced; a migration notice goes out in advance of the cutover |

Treat `id` as an opaque, Sunra-owned token from here forward — stable in shape, not meaningful to parse — and use `GET /completions` whenever you need the value a specific upstream provider actually assigned.
