# Routing and failover

> How ACOG chooses the connection that serves a request, when it spreads load and fails over, and how to pin a request to one platform.

Product: AI Cost Optimizer Gateway (ACOG). Web version: https://tessarac.com/docs/acog/providers/routing-and-failover. Last updated: 2026-10-16.

ACOG routes **by model**. The `model` in a request selects the connections that declared it; you never choose a provider in the request body.

## Which connection serves a request

1. **No connection declares the model.** The request is refused with `409 model_not_configured`. Declare the model on a connection under **Admin → Providers**.
2. **Your default connection declares the model.** The default connection serves it, and only the default connection: there is no spreading and no failover to other connections. If the default answers `429` or `5xx`, that is the answer you get. While its circuit breaker is open (see below) you get `503 upstream_unavailable` with `Retry-After: 5`.
3. **Other connections declare the model.** ACOG spreads requests across them and fails over between them. When it must order them, it prefers `direct`, then `bedrock`, `vertex`, `azure` and `custom`.

Model ids match loosely on version: a request for `claude-haiku-4-5` reaches a connection that declared `claude-haiku-4-5@20251001` and the other way round, and is sent with the id the connection declared. A match never crosses model families or major versions.

## Failover

For a model that is not pinned to the default connection, ACOG retries on another connection that declares the same model when the first attempt:

- fails with a `5xx` or a network error,
- is rate-limited with `429` (retried only on a different connection), or
- is refused before sending because its settings are no longer allowed (`connection_not_usable`).

A request gets up to three attempts in total, with a short randomized backoff between them. Errors that are about the request itself (`400`, `401`, `403`, `404`, `422`) are returned at once.

## Circuit breaker

After five consecutive failures on a platform, ACOG stops sending to it for 30 seconds and then lets one request through to test it. While the breaker is open, requests that cannot go elsewhere receive `503 upstream_unavailable` with `Retry-After: 5`.

## What can move a request off the default connection

Four things can send a model your default connection declares to another connection, which then spreads and fails over normally:

| Cause | When |
|---|---|
| [Cost arbitrage](https://tessarac.com/docs/acog/optimize/cost-arbitrage.md) | You turned it on, and another platform serves the same model for less |
| The `x-acog-channel` header | The request pins a platform (below) |
| A [DLP route restriction](https://tessarac.com/docs/acog/govern/data-loss-prevention.md) | A rule allows the request only on certain platforms |
| The policy-block fallback | The provider's safety system refused the request (below) |

## Pin a request to one platform: `x-acog-channel`

When the same model is declared on several platforms (Claude on Anthropic, Bedrock and Vertex, say), send `x-acog-channel` to choose one:

```bash
curl -s https://gateway.acog.tessarac.com/v1/messages \
  -H "Authorization: Bearer $ACOG_API_KEY" \
  -H "x-acog-channel: vertex" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-sonnet-4-5", "max_tokens": 256, "messages": [{"role": "user", "content": "Hello"}]}'
```

Values are `direct`, `bedrock`, `vertex`, `azure` and `custom`. The pin overrides everything else, including cost arbitrage. An unknown value, or a platform with no connection that declares the model, is refused with `409 model_not_configured`.

## Policy-block fallback

Occasionally a provider's safety classifier refuses a harmless request with an empty answer and `stop_reason: "refusal"`. When that happens, ACOG sends the request once more to a fallback model (by default `claude-opus-4-8`) on the same connection, adds a short routing note to the answer saying so, and sets the response header `x-acog-policy-fallback`:

| Value | Meaning |
|---|---|
| `applied` | The fallback model answered |
| `skipped` | The request was already for the fallback model, so there was nothing to fall back to |
| `unavailable` | No fallback answer: the request opted out, the fallback was not allowed (by model access rules, say), or the fallback model also declined or failed. You get the original refusal. |

The header is present only when a refusal was seen. To opt out for a request, send `x-acog-policy-fallback: off`.

## Which model answered

Unified routes set `x-acog-resolved-model` to the model that was actually sent, which differs from the request when you asked for `auto` or a version alias. See [Response headers](https://tessarac.com/docs/acog/reference/response-headers.md).
