# Provider selection

https://modelportal.ai/docs/provider-routing

Use the `provider` object to control which provider and endpoint serve a single request. Its fields are compatible with OpenRouter's `provider` object, so most settings carry over unchanged; differences are listed at the end.

## How to send it

- OpenAI protocol: in the `provider` field of the request body.
- All three protocols: the `X-ModelPortal-Provider-Preferences` header, whose value is a JSON object with the same fields as `provider`, at most 4096 bytes. The official Anthropic and Gemini SDKs make extra body fields awkward, so the header is the easy route there.
- If both the body and the header carry preferences, you get 400 `invalid_request`; the platform does not merge them.
- `provider` is for the platform only: it is removed before the request goes to a provider, and the header is never forwarded.

```bash
curl https://modelportal.ai/v1/chat/completions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MODEL_ID",
    "messages": [{"role": "user", "content": "Hello"}],
    "provider": {"order": ["PROVIDER_ID"], "data_collection": "deny"}
  }'
```

```bash
curl https://modelportal.ai/v1/messages \
  -H "x-api-key: YOUR_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -H 'X-ModelPortal-Provider-Preferences: {"only": ["PROVIDER_ID"]}' \
  -d '{"model": "MODEL_ID", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello"}]}'
```

## Fields

| Field | Type | Meaning |
| --- | --- | --- |
| `order` | string array | Provider or endpoint identifiers to try first, in order; other endpoints follow by the default rules. For a provider identifier, its endpoints are still ordered by the default rules |
| `only` | string array | Allow only these providers or endpoints |
| `ignore` | string array | Exclude these providers or endpoints |
| `allow_fallbacks` | boolean | Whether to switch between endpoints of the same model; defaults to `false`. See [Fallbacks](/docs/fallbacks) |
| `sort` | string | `price` (comparison price ascending), `latency` (median time to first token over the last hour, ascending), `throughput` (median throughput over the last hour, descending) |
| `data_collection` | string | `allow` (default) or `deny`; see [Data policy](/docs/data-policy) |
| `quantizations` | string array | Allowed quantizations: `int4`, `int8`, `fp4`, `fp6`, `fp8`, `fp16`, `bf16`, `fp32`, `unknown` (not disclosed) |
| `max_price` | object | Price caps with `prompt` and `completion` (USD per million tokens), `request` (USD per request) and `image` (USD per image); endpoints above any cap are excluded |
| `data_regions` | string array | Use only endpoints whose data processing region is listed; two-letter uppercase country or region codes such as `US` or `SG` |
| `exclude_regions` | string array | Exclude endpoints whose data processing region is listed |
| `require_parameters` | boolean | Only `true` is accepted. The platform always requires an endpoint to support every parameter in the request |

Fields set to `null` count as unset. Accounts and keys can store default preferences too (the account's live on the Routing preferences page of the console, linked from the Keys page). When several layers set a field, request beats key beats account; restrictive settings only tighten: `ignore` and `exclude_regions` are unioned, `data_regions` is intersected, and `data_collection: deny` anywhere wins.

## Locking to one endpoint

`order`, `only` and `ignore` accept provider identifiers as well as endpoint identifiers, which start with `route_`. To have exactly one endpoint serve the request, put only that one in `only`:

```json
{"provider": {"only": ["route_xxxxxxxx"]}}
```

Endpoint identifiers are listed on the model page and in `offerings[].offering_id` from `GET /api/v1/models/<model ID>`. If the locked endpoint is paused, under maintenance or fails another constraint, you get 422 `no_eligible_endpoint` with a reason code in `excluded`; see [Common errors](/docs/errors).

## Validation

Fields are checked before routing. These cases return 400 `invalid_request` with a message naming `provider.<field>` and the reason, and no request reaches any provider:

- Unknown fields, or OpenRouter fields this platform does not implement, such as `zdr` (use `data_collection: deny` instead).
- `require_parameters: false`.
- Provider or endpoint identifiers in `order`, `only` or `ignore` that do not exist on the platform.
- Wrong types or values, for example `only` given as a string.

## Differences from OpenRouter

1. `allow_fallbacks` defaults to `false`: when the first endpoint fails you get the error, and the request does not move on.
2. Unsupported model parameters are errors: if no candidate endpoint supports a parameter in the request, you get 400 `unsupported_parameter` instead of the parameter being dropped.
3. Unknown fields, unimplemented fields and nonexistent identifiers in `provider` all return 400 instead of being ignored.
