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

# Migrating to the v2 core.

> What changes for API clients on Sideren's v2 core: the endpoints that are gone for good (410), one public id per model, errors by cause, disclosure and opt-in headers, idempotency keys, and stateful Responses.

> [!TIP]
>
> You may have landed here from a `410` response — this page names the replacement for every
> retired endpoint. If you are moving **from another provider** to Sideren, start with
> [Migrating from other providers](https://docs.sideren.io/guides/migrating.md) instead.

Your base URL, your API keys and your request shapes do not change on the
v2 core. What changes: a set of legacy endpoints is **gone for good**, every model has one public id,
errors carry more precise statuses, and a few new headers tell you — and let you control —
what happened on each request.

## Retired endpoints (410 Gone)

The following surfaces are retired. A request to any of them — any method, any sub-path —
returns **`410`** with a JSON error body whose `message` names the replacement, plus a
`link: <...>; rel="help"` header pointing here. A `410` is permanent: don't retry it, move
the call.

| Retired path | Use instead |
| - | - |
| `POST /v1/completions` | [Chat Completions](https://docs.sideren.io/api/chat-completions.md) — put your prompt in a single `user` message. |
| `/v1/messages/batches` and `/v1/batches` (all sub-paths) | Send the requests individually with an [Idempotency-Key](https://docs.sideren.io/api/idempotency.md) so retries are safe, or use Responses with `background: true` for long-running work. |
| The Responses WebSocket transport | `POST /v1/responses` over HTTPS, with `stream: true` for [Server-Sent Events](https://docs.sideren.io/guides/streaming.md). |
| `/v1/agent-runs` and everything under it | Run your agent loop in your own client against [Chat Completions](https://docs.sideren.io/api/chat-completions.md), [Responses](https://docs.sideren.io/api/responses.md) or [Messages](https://docs.sideren.io/api/messages.md). Claude Code, Codex and other harnesses work through those endpoints directly. |
| `/v1/states` and `/v1/agent-states` | Keep per-agent state in your own storage, or use [stateful Responses](#stateful-responses) (`store: true` + `previous_response_id`) for conversation state that should survive a dropped connection. |
| `/v1/artifacts` | Keep large results in your own storage and send the slice you need in the prompt. |
| `/v1/tool-connectors` and `/v1/tool-catalog` | Declare only the tools the model itself executes. Hosted server-side tools are not run by Sideren — see [Tool calling](https://docs.sideren.io/guides/tool-calling.md#hosted-tools): tools we don't host are dropped from the request and reported in `x-sideren-dropped-tools`. |

```json title="Example 410 body" theme={null}
{
  "error": {
    "type": "gone",
    "code": "endpoint_retired",
    "message": "/v1/completions is retired in Sideren v2. See https://docs.sideren.io/guides/core-v2-migration",
    "docs_url": "https://docs.sideren.io/guides/core-v2-migration"
  }
}
```

Everything else keeps working: [Chat Completions](https://docs.sideren.io/api/chat-completions.md),
[Messages](https://docs.sideren.io/api/messages.md), [Responses](https://docs.sideren.io/api/responses.md), [Embeddings](https://docs.sideren.io/api/embeddings.md),
[count\_tokens](https://docs.sideren.io/api/count-tokens.md), [`GET /v1/models`](https://docs.sideren.io/api/models-endpoint) and
[Idempotency-Key](https://docs.sideren.io/api/idempotency.md). The per-endpoint notes are on
[Retired endpoints](https://docs.sideren.io/api/retired.md).

## One public id per model

Every model has one public id. Closed-weight models are called by their plain name
(`gpt-6.1-sol`, `sonnet-4.6`); open-weight models are called `@sdn/<name>`
(`@sdn/glm-5.3`). The older spellings (`sdn-glm-5.3` and the bare `glm-5.3`)
keep resolving to the same model, and the response echoes the id **you** sent; canonical
ids appear in `x-sideren-requested-model`. You don't have to change anything day one —
but new tooling should write public ids, and `GET /v1/models` lists them that way, with
the older spellings under each entry's `aliases`. In YAML, quote an id that starts with
`@`: `model: "@sdn/glm-5.3"`.

> [!NOTE]
>
> If the model you ask for can't be served (a disabled id, for example), Sideren serves a
> **similar available model** and discloses it — unless exact mode is on. With exact mode
> (per key, or the `x-sideren-exact` header below), you instead get a **`404 model_unavailable`**:
> non-retryable, with an `alternatives` list containing only models your plan can actually
> serve. Don't auto-retry it — pick an alternative. The same `404` is also returned **without**
> exact mode when no usable alternative exists (every candidate is unavailable or outside your
> plan) — there is nothing to fall back to, so refusing is the honest answer.

## Errors say why, by cause

Statuses map to causes, so your existing retry logic keeps working without configuration —
but only retry what can succeed:

| Cause | Status | Retry-After | Retry? |
| - | - | - | - |
| A temporary limit on your key or plan (requests/min, tokens/min, concurrency) | `429` | **Yes** — seconds, honored exactly | Yes, after the wait |
| A temporary Sideren capacity shortage | `503` (OpenAI-shaped ingresses) · **`529 overloaded_error`** (Anthropic-shaped `/v1/messages` — Anthropic's native overload signal) | **Yes** | Yes, after the wait |
| Tokens or balance used up, allowance exhausted on a stop-at-allowance key | `402` | Never | No — top up, upgrade, or switch the key to PAYG |
| A model or feature your key's plan doesn't include (`400 plan_upgrade_required` names the cheapest plan that includes the model) | `400` | Never | No — upgrade, pick an included model, or use PAYG mode |
| The requested model can't be served and exact mode asked for it — or no usable alternative exists | `404 model_unavailable` | Never | No — use an `alternatives` entry |
| The inference request body exceeds the v2 core's **16 MiB** JSON parse limit | `413 body_too_large` | Never | No — reduce or split the request, including inline media |

The 16 MiB limit applies to Chat Completions, Responses, and Messages request bodies before
upstream dispatch. The error message names the limit.

Two behaviors worth knowing beyond the table:

* **No hung connections.** Before admitting a request, Sideren predicts whether it can start
  (and finish, for non-streaming) inside the time your client will actually wait. When it
  can't, you get the immediate `429`/`503` with `retry-after` from the table — not a
  connection that dies later. See [Reliability](https://docs.sideren.io/reliability.md).
* **Streams keepalive.** While a queued stream waits, Sideren sends SSE keepalive traffic so
  client idle timeouts don't fire; first-byte timing is bounded per client profile.

Full status/code tables and retry recipes: [Errors](https://docs.sideren.io/errors.md). `retry-after` semantics:
[Headers](https://docs.sideren.io/headers.md#on-error-responses).

## Disclosure headers on the response

The v2 core tells you what actually happened. All are read-only facts, all documented on
[Headers](https://docs.sideren.io/headers.md):

| Header | When you see it |
| - | - |
| `x-sideren-served-model` | Sideren's own CLI (`se`) only: the model that served the request, when it differs from the id you sent. |
| `sideren-model` | Always. The model id of the response: the model you asked for (in `se`, the model that served it). |
| `x-sideren-routing` | How the served model was chosen for this request (for example `exact` when exact mode was on). |
| `x-sideren-reasoning-effort` | Only when the reasoning effort that ran differs from the one you asked for (for example `high (requested xhigh)`). |
| `x-sideren-dropped-tools` | Only when your request declared tools Sideren doesn't host: those were removed before the model saw the request. Forcing one is a `400`, not a silent drop. |
| `x-sideren-reasoning-continuity` | Only when reasoning from a previous turn couldn't be carried into this one (or can't be carried onward): the turn ran without it. |
| `x-ratelimit-limit-requests` · `x-ratelimit-remaining-requests` · `x-ratelimit-reset-requests` (and the `-tokens` variants) | Always, on every wire format: your request (and token) budget for the current window, so a client can pace itself instead of discovering the limit by hitting it. |
| `anthropic-ratelimit-requests-limit` · `anthropic-ratelimit-requests-remaining` · `anthropic-ratelimit-requests-reset` (and the tokens variants) | On `/v1/messages` responses only — the same rate-limit facts in Anthropic's native spelling, alongside the `x-ratelimit-*` trio. |

The rate-limit headers replaced the ad-hoc `x-sideren-rpm-*` names: those are still sent as
**deprecated aliases for one release**, then removed — move parsers to the names above.

## Opt-in request headers

Three switches are **off by default** and opt-in per request (or per key, in your dashboard's
key settings):

| Header | Value | What it does |
| - | - | - |
| `x-sideren-exact` | `1` | Serve exactly the model you named. If it can't be served: the non-retryable `404 model_unavailable` with alternatives, instead of a similar model. |
| `x-sideren-allow-degrade` | `effort` · `features` · `effort,features` | Allow a reasoning-effort change (or listed feature degradation) that isn't proven equivalent, instead of a refusal. Reported on the response. |
| `x-sideren-tool-guard` | `repair` | Buffer and validate tool calls against your schemas; repair or refuse malformed ones. Off by default. |

Session affinity headers are optional and separate — `x-sideren-session` (your stable id for
one conversation), `x-sideren-principal` (your id for the end user, when one key serves many),
`x-sideren-branch` (a fork of a conversation) and `x-sideren-client` (`name/version`). They
keep an agent loop's turns on a warm cache. The
[setup helper](https://docs.sideren.io/setup.md) stamps them for you in the tools it configures.

## Idempotency-Key

Send an `Idempotency-Key` (a fresh UUID per logical request) on any `POST`, and a retried
request is answered from the first attempt instead of running the model — and billing it —
twice. Keys are remembered for **24 hours**, scoped to your API key and matched against a
canonical hash of the body:

| You send | You get |
| - | - |
| Same key, same body, first request finished | The stored first result (or its status) |
| Same key, same body, first request still running | `409 idempotency_in_progress` — wait, retry the same key |
| Same key, **different** body | `422 idempotency_body_mismatch` — use a new key |

Details, edge cases and what idempotency can't undo: [Idempotency](https://docs.sideren.io/api/idempotency.md).

## Stateful Responses

`POST /v1/responses` with `store: true` persists the response under your key; later turns
chain on it with `previous_response_id` and send only the new input. Stored responses — and
only those you asked to store — are kept for **up to 1 year**, scoped to the key that created
them, and you can delete them at any time from the dashboard. Request/response content
retention, deletion and zero-retention keys are covered in our
[privacy policy](https://www.sideren.io/privacy); the API surface on [Responses](https://docs.sideren.io/api/responses.md).

For work that should outlive its connection without holding one open, use
`background: true` and read the result back with `GET /v1/responses/{id}` — the replacement
pattern for the retired batches surface.

## A five-point client checklist

**1. Grep for retired paths**

`grep -rn 'v1/completions\|/batches\|agent-runs\|/states\|/artifacts\|tool-connectors\|tool-catalog' src/` — anything that hits these needs the replacements above before v2, and gets a helpful `410` after.

**2. Retry only what can succeed**

`429` and `503`/`529` with `retry-after` yes; `400`, `402`, `403`, `404` and every `410` no. The official SDKs already follow this — hand-rolled clients should match [Errors](https://docs.sideren.io/errors.md).

**3. Read the disclosure headers**

Log `x-sideren-served-model` next to the id you sent; branch on `x-sideren-dropped-tools` and `x-sideren-reasoning-continuity` when they appear.

**4. Put idempotency keys on every POST**

Especially agent loops and payment-adjacent calls. One UUID per logical request.

**5. Prefer public model ids**

Older spellings still resolve, but write public ids (`@sdn/glm-5.3`, `gpt-6.1-sol`) in new configs and read the catalog at runtime with [`GET /v1/models`](https://docs.sideren.io/api/models-endpoint).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.