---
title: "Provider routing"
description: "When several providers serve the same model, pirouter picks one for each request. This page covers the default choice, the workspace settings, per-request overrides, and how to debug an empty result."
canonical: "https://pirouter.ai/docs/routing"
last-updated: "2026-10-13"
lang: "en"
source: "content/docs/en/(start)/routing.mdx"
---

# Provider routing

When several providers serve the same model, pirouter picks one for each request. This page covers the default choice, the workspace settings, per-request overrides, and how to debug an empty result.

A model is often served by several providers. For each request pirouter picks one provider, and moves to the next provider when that one fails. You can set a default for the workspace and override it on a single request.

## The default

With nothing set, pirouter spreads requests across the providers that serve the model in proportion to their weight. Price plays no part in the default order.

A provider marked as down is never picked.

To see which providers serve a model, open its page or call `GET /v1/models/{vendor}/{model}/endpoints`. The `provider` of each entry is the name used everywhere below.

## Set the workspace default

Open **Routing** in the console. Only workspace owners can change it; other members can view it.

- [Open the console](https://app.pirouter.ai): app.pirouter.ai

| Setting               | What it does                                                                                                         |
| --------------------- | -------------------------------------------------------------------------------------------------------------------- |
| **Sort**              | Which provider goes first when several serve the same model: **Balanced**, **Lowest price** or **Custom order**      |
| **Allowed providers** | Only send requests to these providers                                                                                |
| **Ignored providers** | Never send requests to these providers                                                                               |
| **Fallback models**   | Tried in order when the requested model is unavailable; see [Model fallbacks](https://pirouter.ai/docs/fallbacks.md) |

A saved change applies from the next request.

## Override per request

Add a `provider` object at the top level of the request body. pirouter accepts the `provider` object on requests of all three protocols: OpenAI, Anthropic and Gemini.

```bash
curl https://api.pirouter.ai/v1/chat/completions \
  -H "Authorization: Bearer $PIROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-haiku-5.5",
    "messages": [{ "role": "user", "content": "Hello!" }],
    "provider": { "only": ["azure"] }
  }'
```

You should see status `200`. This request can only go to `azure`.

| Field             | Type                    | What it does                                                                             |
| ----------------- | ----------------------- | ---------------------------------------------------------------------------------------- |
| `order`           | array of strings        | Try these first, in this order. Providers not listed come after them and are still tried |
| `allow_fallbacks` | boolean, default `true` | With `false` and an `order`, only the providers in `order` may serve                     |
| `sort`            | `"price"`               | Rank by input price plus output price, lowest first                                      |
| `only`            | array of strings        | Use only these providers                                                                 |
| `ignore`          | array of strings        | Never use these providers. Wins over `only`                                              |
| `data_collection` | `"allow"` or `"deny"`   | `"deny"` excludes providers that train on your data or have not said whether they do     |
| `zdr`             | boolean                 | `true` keeps only providers with zero data retention                                     |
| `quantizations`   | array of strings        | Use only deployments at these precisions                                                 |

### Provider names

Names are case-insensitive. Besides the names in the catalog, three aliases are accepted: `google-vertex` (for `gcp`), `google-ai-studio` (for `gemini`) and `amazon-bedrock` (for `aws`).

An unknown name returns `422`, and the message names the field and the position. A name followed by `/` and a region is not supported and also returns `422`.

An empty array and `null` both mean the field is not set. `"only": []` does not mean no provider is allowed.

### Order and price

`order` is a preference, not an allowlist. To confine a request to the providers you list, add `"allow_fallbacks": false`:

```json
{ "provider": { "order": ["anthropic", "azure"], "allow_fallbacks": false } }
```

`"sort": "price"` compares input price plus output price, at the price tier this request would be billed at. Used with `order`, price only ranks providers within the same `order` position. Sorting by `"throughput"` or `"latency"` is not supported and returns `422`.

### Data policy and quantization

`"data_collection": "deny"` excludes two kinds of provider: those that train on prompts or have not declared, and those whose data retention is neither zero nor limited. `"zdr": true` is stricter and keeps only zero retention.

`quantizations` takes `fp4`, `int4`, `int8`, `fp8`, `fp16`, `bf16`, `fp32` and `unknown`. Once it is set, a deployment that has not declared its precision is excluded too.

## How the two layers combine

When the workspace settings and a request's `provider` both apply:

- **Limits only tighten.** `only` and `quantizations` are intersected across the two layers; `ignore` is the union. If the workspace denies data collection, requires zero retention or turns fallbacks off, a request cannot relax it.
- **The sort mode is replaced as a whole.** A request that sets either `order` or `sort` replaces the workspace sort mode entirely. If the workspace has a custom order and the request sends `"sort": "price"`, that request is ranked by price alone.

## When no provider is left

When the settings exclude every provider, the request returns `404` with the error code `no_eligible_provider`. Nothing was forwarded and nothing is billed.

```json
{
  "error": {
    "message": "No allowed provider can serve this model: the request's provider preferences, together with the workspace routing settings, exclude every provider that serves it. Providers serving it: anthropic, azure.",
    "type": "invalid_request_error",
    "code": "no_eligible_provider",
    "metadata": {
      "available_providers": ["anthropic", "azure"],
      "routing_funnel": [
        { "step": "initial", "providers": 2 },
        { "step": "data_policy", "providers": 2 },
        { "step": "quantization", "providers": 2 },
        { "step": "provider_ignore", "providers": 2 },
        { "step": "provider_only", "providers": 0 }
      ],
      "failed_step": "provider_only"
    }
  }
}
```

`metadata` tells you which step emptied the set:

- `available_providers`: the providers that serve the model before any setting applies.
- `routing_funnel`: how many are left after each step.
- `failed_step`: the step that left none.

| `failed_step`       | Setting behind it                                  |
| ------------------- | -------------------------------------------------- |
| `data_policy`       | `data_collection`, `zdr`                           |
| `quantization`      | `quantizations`                                    |
| `provider_ignore`   | `ignore`, plus the workspace **Ignored providers** |
| `provider_only`     | `only`, plus the workspace **Allowed providers**   |
| `fallback_disabled` | `allow_fallbacks` set to `false`                   |

`404 no_eligible_provider` is not the same as `404 model_not_found`: `model_not_found` means the model name is wrong; `no_eligible_provider` means the model exists and the settings left it no provider.

## Automatic retry on another provider

When the chosen provider fails before the first byte of the response, pirouter retries on the next one. You do not need to retry yourself.

- **Retried**: the upstream returns `429`, `500`, `502` or `503`, the connection fails, or the upstream refuses the platform's own credentials (`401`, `403`; this is not about your key).
- **Not retried**: any other `4xx` (a bad parameter, a context that is too long), a timeout, and any error after the response has started.
- A request is tried at most 5 times, streaming or not.
- When every attempt fails, you get the status and body of the last upstream answer.

With `"allow_fallbacks": false` and no `order`, retries stay on the provider that was picked first.

## Which provider served a request

Read the response headers. A response a provider actually answered carries these; add `-i` to curl to see them:

| Response header            | What it holds                                                                                             |
| -------------------------- | --------------------------------------------------------------------------------------------------------- |
| `X-Pi-Provider`            | The provider that served this request                                                                     |
| `X-Pi-Model`               | The ID of the model that served it, the same as `model` in the response body                              |
| `X-Pi-Upstream-Model`      | The model name the upstream itself reported                                                               |
| `X-Pi-Upstream-Request-Id` | The upstream's own request ID, for raising an issue with the provider; absent when the upstream sent none |
| `X-Pi-Trace-Id`            | The platform's request ID, on every response; quote it when reporting a problem                           |

An error the gateway raised itself (authentication, balance, rate limit, validation, no provider) was never served by a provider and carries `X-Pi-Trace-Id` only.

After a retry on another provider, the headers describe the attempt that answered. You can also find the request in **Logs** in the console by its `X-Pi-Trace-Id`.

## What a mistake returns

A mistake inside `provider` always returns `422` with the error code `validation_failed`. The `message` starts with `Invalid provider preferences:` and names the field:

| Case                                    | What the `message` says                     |
| --------------------------------------- | ------------------------------------------- |
| An unknown key                          | `provider: unrecognized key "foo"`          |
| A known key that is not implemented yet | `provider.max_price is not supported yet`   |
| An unknown provider                     | `provider.only[0]: unknown provider "nope"` |
| A wrong type                            | `provider.zdr: must be a boolean`           |

## Not available today

- **Reading or changing the workspace routing settings through the API.** They are set in the console only; what the API offers is `provider` on each request.
- **Sorting by throughput or latency.** `sort` accepts `"price"` only.
- **Price ceilings and the remaining fields.** `max_price`, `require_parameters`, `preferred_min_throughput`, `preferred_max_latency`, `enforce_distillable_text` and `options` return `422`.

## Next steps

- [Model fallbacks](https://pirouter.ai/docs/fallbacks.md): Switch to another model when the one you asked for is unavailable.
- [API reference](https://pirouter.ai/docs/api/chat-completions.md#body): The full definition of the `provider` object in a request body.
