# Errors

An error response has a JSON body. Match on `error.code`, not on the message. The status tells you what kind of fix to make.

## What to do

| Status | What to do                                                               |
| ------ | ------------------------------------------------------------------------ |
| 400    | Change the request. No offer can serve it as it is.                      |
| 401    | Send a valid key. See [API keys](https://inference.ai.exchange/docs/keys.md).                             |
| 402    | Add funds, raise the limit, or lower the token limit.                    |
| 403    | Use a key with the right scope, or change the request.                   |
| 404    | Check the name. `GET /v1/models` lists your models.                      |
| 409    | Use a new `idempotency-key` for a new body.                              |
| 413    | Send less data. See [Modalities](https://inference.ai.exchange/docs/modalities.md).                       |
| 429    | Wait for `Retry-After`, then send the request again.                     |
| 503    | Send it again later. Some codes need a change; the tables say which.     |
| 500    | Send it again. If it repeats, give the job id in the message to support. |

When the auction refuses a request, `eligibility.excluded` lists each offer that it left out and why. See [Exclusion reasons](#exclusion-reasons).

## Error format

```json
{
  "error": {
    "message": "`reasoning.effort` is not supported by any offer for kimi-k3",
    "type": "liquid_error",
    "code": "unsupported_parameter",
    "param": "reasoning.effort",
    "eligibility": { "excluded": [ … ] }
  }
}
```

| Field          | Description                                                                                    |
| -------------- | ---------------------------------------------------------------------------------------------- |
| `code`         | Machine-readable error code. Match on this.                                                    |
| `message`      | Human-readable description.                                                                    |
| `param`        | The request field that the error names, if any.                                                |
| `eligibility`  | Present when the auction refused the request. Lists each excluded offer and the reason.        |
| `auto`         | On `no_eligible_offer` for an Auto request: what Auto ranked by. See [Auto Router](https://inference.ai.exchange/docs/auto.md). |
| `binding_rule` | On `no_eligible_offer` when a provider allowlist left no offer.                                |
| `fixes`        | On `no_eligible_offer`: what to change. See [Fixes](#fixes).                                   |
| `provider`     | On `provider_failed`, `request_rejected` and `tools_unsupported`: the awarded provider.        |

A `502` or `504` with an HTML body comes from the network in front of the exchange, not from the exchange.

`POST /v1/messages` returns the Anthropic format, `{"type": "error", "error": {"type": ..., "message": ...}}`. An auction error keeps the `code`, `param`, `eligibility`, `auto`, `binding_rule` and `provider` fields next to `type` and `message`.

| HTTP status | Anthropic `error.type`  |
| ----------- | ----------------------- |
| 400         | `invalid_request_error` |
| 401         | `authentication_error`  |
| 403, 451    | `permission_error`      |
| 404         | `not_found_error`       |
| 413         | `request_too_large`     |
| 429         | `rate_limit_error`      |
| 502, 503    | `overloaded_error`      |
| other       | `api_error`             |

## Error codes

### Authentication

| Status | Code                            | Description                                                                                                    |
| ------ | ------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| 401    | `invalid_api_key`               | Missing, unknown, revoked or expired API key.                                                                  |
| 403    | `insufficient_permission`       | The key's scope does not cover the endpoint.                                                                   |
| 403    | `address_not_allowed`           | The key is restricted to other source addresses.                                                               |
| 403    | `impersonated_session`          | An operator views the account, and the request would change it.                                                |
| 451    | `jurisdiction_restricted`       | The request came from an unavailable location, or through Tor. See [Regional availability](https://inference.ai.exchange/docs/availability.md). |
| 503    | `identity_provider_unavailable` | The exchange could not verify a browser session.                                                               |
| 401    | `unauthenticated`               | Provider endpoints: missing or invalid provider key.                                                           |

### Request

| Status | Code                     | Description                                                                                                                                                               |
| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`        | The body cannot be parsed, or a field is not valid. `param` names the field. A removed `exchange` field gets this code. See [Purchasing rules](https://inference.ai.exchange/docs/rules.md).               |
| 400    | `conflicting_parameters` | An `exchange` rule is set in the flat form and in a group with different values. The message names both fields. See [Purchasing rules](https://inference.ai.exchange/docs/rules.md).                       |
| 400    | `tools_unsupported`      | The awarded provider cannot run the request with tools. Remove `tools`.                                                                                                   |
| 400    | `request_rejected`       | The awarded provider refused the request. The message is its reason.                                                                                                      |
| 400    | `invalid_header`         | A purchasing header cannot be parsed.                                                                                                                                     |
| 400    | `unsupported_parameter`  | No offer for the model supports a parameter. `param` names it. See below.                                                                                                 |
| 400    | `unsupported_content`    | No offer can serve a content part, or `/v1/responses` does not read an input item. The message gives its path, such as `messages[0].content[1]`.                          |
| 400    | `unsupported_model`      | An image or speech request named a router model that ranks by price. Name the model, or use a router model that ranks by Auto.                                            |
| 400    | `unsupported_rule`       | An image or speech request had a rule that limits a price or a speed in tokens, such as `exchange.rank.cost_tier`. `param` names it.                                      |
| 413    | `request_too_large`      | The body is over 20 MB (the default limit). See [Modalities](https://inference.ai.exchange/docs/modalities.md).                                                                                            |
| 404    | `model_not_found`        | The `model` is not a router model, and no offer quotes it.                                                                                                                |
| 409    | `idempotency_conflict`   | The `idempotency-key` was reused with a different body.                                                                                                                   |
| 409    | `idempotency_no_result`  | The exchange saw the `idempotency-key` before, but the first request produced no receipt.                                                                                 |
| 429    | `rate_limited`           | Too many requests from the address, key or workspace, or a [guardrail rate limit](https://inference.ai.exchange/docs/guardrails.md#rate-limits) refused the call. `Retry-After` gives the seconds to wait. |
| 503    | `overloaded`             | The exchange is at its limit of work, or its database is too busy. It charged nothing. `Retry-After` gives 1 to 5 seconds.                                                |
| 400    | `invalid_end_user`       | The [end-user id](https://inference.ai.exchange/docs/guardrails.md#end-users) is empty, over 512 characters or not printable ASCII.                                                                        |

`unsupported_parameter` also applies to these cases:

- A control that no offer declared. `param` is the feature name: `image.seed`, `image.background`, `image.output_compression`, `image.stream`, `video.seed`, `video.creativity`, `video.upscale_factor`, `video.continue`, `speech.instructions`, `speech.turns`, `transcription.diarize`, `transcription.keyterms` or `embedding.input_type`. The message reads "`image.seed` is not supported by any offer for <model>". The exchange holds nothing.
- A reference input that no offer reads. `param` is `input.image`, `input.audio`, `input.video` or `input.file`.
- An output option that no offer serves on an image, speech, video or embedding request. `param` is `n`, `size`, `output_format`, `voice`, `response_format`, `seconds` or `dimensions`. `/v1/models` lists `image_sizes` for an image model.
- On `/v1/responses`: a hosted tool other than `web_search`, `previous_response_id`, `conversation` and `background: true`. The exchange stores no responses.
- On `/v1/messages`: a hosted tool such as `web_search`, `web_fetch` or `code_execution`. A tool that your client runs is allowed.
- A `provider` setting that the exchange cannot apply. See [Move from OpenRouter](https://inference.ai.exchange/docs/move-from-openrouter.md).

### Models and strategy

These codes come before the auction.

| Status | Code                              | Description                                                                                                                   |
| ------ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| 400    | `basket_requires_models`          | `architect.basket` needs `exchange.candidates.models.allow`. Send the models, or `[]` for every listed model.                 |
| 400    | `address_takes_no_basket`         | The `model` is one model, and the request also sent `exchange.candidates.models.allow`. Remove one.                           |
| 400    | `basket_names_router`             | An entry in `exchange.candidates.models.allow` is a router model or `architect.basket`. Use `exchange.rank.strategy` to rank. |
| 400    | `invalid_basket`                  | An entry in `exchange.candidates.models.allow` is blank.                                                                      |
| 400    | `strategy_conflicts_with_address` | The strategy `kind` does not match the `model`, such as `auto` with a model. Send `architect.basket`.                         |
| 400    | `invalid_price_band`              | `exchange.rank.strategy.price_band_bps` is above 10000.                                                                       |
| 403    | `key_policy_violation`            | The request is outside the preset of the API key: no model matches, or the `exchange.rank.workload_profile` differs.          |

### Auction

The price cap is the most that a job can cost. The exchange sets it before the first token.

| Status | Code                    | Description                                                                                                                                                  |
| ------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400    | `no_eligible_offer`     | The request, or the rules of the key or workspace, excluded every offer. Do not retry. Change the request.                                                   |
| 503    | `no_eligible_offer`     | No offer met the rules, and a wait can change that. For example, an offer failed a recent job. See `eligibility.excluded`.                                   |
| 403    | `no_eligible_offer`     | A provider allowlist of the key or of a guardrail matches no provider that sells the model. `binding_rule` names it. Do not retry.                           |
| 503    | `model_unavailable`     | No seller can serve the named model now. `param` is `model`. `Retry-After` is 30. Retry later, or select another model from `GET /v1/models`.                |
| 402    | `insufficient_balance`  | The cheapest eligible cap is more than the workspace balance. `needed_usd` and `balance_usd` give both amounts.                                              |
| 402    | `budget_exhausted`      | The key reached its spending limit, or a guardrail budget is spent. Jobs in flight count at their price cap.                                                 |
| 402    | `deposit_required`      | The workspace holds only free credit, and keys of several such accounts send requests from this address. Add funds.                                          |
| 403    | `guardrail_violation`   | The request is outside a workspace guardrail. See [Guardrails](https://inference.ai.exchange/docs/guardrails.md).                                                                             |
| 403    | `content_blocked`       | A guardrail blocks the request because its text matched a detector. See [Guardrails](https://inference.ai.exchange/docs/guardrails.md).                                                       |
| 503    | `guardrail_unavailable` | The router could not read the workspace guardrails, so it refused the call. Retry later.                                                                     |
| 403    | `pin_not_permitted`     | A key that cannot pin offers sent `x-liquid-pin-offer`.                                                                                                      |
| 503    | `provider_failed`       | The awarded provider failed, or the exchange could not reach it. If the provider refused the request, the code is `tools_unsupported` or `request_rejected`. |
| 404    | `not_found`             | The job does not exist or belongs to another workspace.                                                                                                      |

If an unserved parameter is the only reason for every exclusion, the error is `400 unsupported_parameter`.

### When a provider fails

If the awarded provider fails before any output, the exchange releases the hold and tries the next offer.

- If no offer takes the job, you receive `provider_failed`. The exchange keeps that offer out of the ranking for a while.
- If no other seller has the model, the first request 30 seconds after the failure is a trial on that offer. Until then, and during the trial, requests get `model_unavailable`. Each failed trial doubles the wait, up to 7.5 minutes.

After any output, or if the provider may still run the job, the exchange tries no other offer. You get one cap and one charge.

### When an allowlist leaves no offer

An offer can serve the model while a provider allowlist matches none of the providers. The `no_eligible_offer` error then has `error.binding_rule`.

- `message` names the providers and the model. For `allowed_providers`, it also suggests `exchange.candidates.providers.prefer`.
- The exchange ranks the request again without the allowlist. If an offer can then win, `message` names its providers and the lowest estimate for the job, and `fixes` has the same data in `admits` and `estimate_nanos`.
- The status is `400` for `allowed_providers` and `403` for the allowlist of a key or a guardrail. `retryable` is `false`.
- If the quotes of an allowed provider expired, the status is `503`.

| `binding_rule`                | The allowlist                                                                            |
| ----------------------------- | ---------------------------------------------------------------------------------------- |
| `allowed_providers`           | `exchange.candidates.providers.allow` in the request, or in the preset it names.         |
| `key_allowed_providers`       | `exchange.candidates.providers.allow` in the preset of the API key. Use another key.     |
| `guardrail_allowed_providers` | `exchange.candidates.providers.allow` in a workspace guardrail. Change it in Guardrails. |

### Auto

These codes refuse a request that Auto ranks. See [Auto Router](https://inference.ai.exchange/docs/auto.md).

| Status | Code                            | Description                                                                                                                            |
| ------ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| 503    | `auto_unavailable`              | Auto is off on this exchange, or it has no score snapshot yet.                                                                         |
| 400    | `auto_version_unknown`          | The mode version is not defined. Send 1, 2, 3, 4 or 5.                                                                                 |
| 400    | `auto_requires_price_bound`     | No price bound. Send `exchange.rank.cost_tier`, `exchange.limits.price.output_usd_per_mtok`, `exchange.cap_usd` or `x-liquid-cap-usd`. |
| 400    | `auto_preference_unsupported`   | The mode version does not accept the preference. Version 1 accepts only `premium`; version 2 accepts all three.                        |
| 400    | `auto_policy_unsupported`       | The same, for a preference sent as `exchange.policy` or `x-liquid-policy`.                                                             |
| 400    | `auto_pin_unsupported`          | The request sent `x-liquid-pin-offer`.                                                                                                 |
| 400    | `preferred_providers_with_auto` | The request sent `exchange.candidates.providers.prefer` or `provider.order`. Auto ranks by score.                                      |
| 400    | `auto_reasoning_unsupported`    | Auto has no scores for the requested reasoning effort.                                                                                 |
| 503    | `auto_scores_stale`             | The exchange's copy of the scores is older than its limit.                                                                             |

A `no_eligible_offer` error for an Auto request also has `error.auto`: the decision that Auto made before the rules left no offer.

### Ranking axis

A model-name suffix (`smartest`, `fastest` or `cheapest`) or a `smartest` strategy can return these codes. See [Axis routing](https://inference.ai.exchange/docs/axis-routing.md). `smartest` is Auto mode version 4, so it can also return the [Auto](#auto) codes.

| Status | Code                            | Description                                                                                                                        |
| ------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `smartest_needs_several_models` | `smartest` needs more than one model. Name a creator, a router model, a basket or a preset, or use `fastest`.                      |
| 400    | `axis_conflict`                 | The model name and `exchange.rank.strategy`, `x-liquid-policy` or `exchange.candidates.creators.allow` ask for different rankings. |
| 400    | `ambiguous_scope`               | The name before the axis is both a served model and a creator. Send `architect.basket` with a pattern, such as `deepseek-*`.       |

### Provider endpoints

A buyer key does not reach these seller endpoints.

| Status | Code                   | Description                                                              |
| ------ | ---------------------- | ------------------------------------------------------------------------ |
| 400    | `invalid_registration` | `deployment_name`, `precision` and `region` are required.                |
| 400    | `invalid_bands`        | `max_input_tokens` and, if stated, `max_output_tokens` must be positive. |
| 404    | `no_model`             | The registration does not exist or belongs to another provider.          |
| 409    | `not_listed`           | The exchange has not listed the registration.                            |

### Reservations

You get these codes when a preset names a reservation. See [Reservations](https://inference.ai.exchange/docs/reservations.md).

| Status | Code                              | Description                                                                                                                                                                                |
| ------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 404    | `reservation_not_found`           | This workspace does not have the reservation contract that the preset names.                                                                                                               |
| 409    | `reservation_not_serving`         | The reservation does not serve this month, and the preset names no other model. The term has not started, no deployment is linked, or the month is suspended.                              |
| 429    | `reservation_pool_used`           | This month's pool is used, and the preset names no other model. Add overflow models, or wait for next month.                                                                               |
| 429    | `reservation_rate_limited`        | You passed the contract's tokens or requests a minute, and the preset names no other model. Retry after `Retry-After`, or add overflow models. The `x-ratelimit-*` headers show the limit. |
| 400    | `reservation_needs_price_ranking` | The preset ranks a reservation by Auto, which has no score for it. Rank by price or by speed.                                                                                              |

### Exchange

| Status | Code                  | Description                                                                                          |
| ------ | --------------------- | ---------------------------------------------------------------------------------------------------- |
| 503    | `storage_unavailable` | A required exchange service is unavailable. The exchange did not process the request. Send it again. |
| 500    | `internal_error`      | Unexpected failure. If the request has a job, the message includes the job id.                       |

## Fixes

Each entry in `fixes` is one change. `reason` is the exclusion reason, `offers` is the count of offers it left out, and `message` says what to change. The `message` of the error holds the same text, because many clients show only that field.

The exchange tests each change before it answers. It drops the one rule and ranks the request again. `admits` lists the providers that can then win. `estimate_nanos` is the lowest charge, in nano-USD (one USD is 1,000,000,000), that you can expect for this job at one of them. The charge at settlement follows the tokens that the job uses.

| The `message` says       | What the test found                                                           |
| ------------------------ | ----------------------------------------------------------------------------- |
| `Any one of these works` | Each entry is enough alone, and has `admits`.                                 |
| `no single change`       | The exchange tested each entry, and none is enough alone. Make more than one. |
| `Change one of these`    | The exchange did not test each entry. An entry may not be enough alone.       |

`owner` says who can make the change. A field of the request cannot widen a rule of a key preset or a guardrail.

| `owner.kind` | Who changes it                                                                                                                                | `param` and `header`                                    |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| `request`    | The sender of the request.                                                                                                                    | Name the field to set, when one field fixes the reason. |
| `key_preset` | An administrator of the workspace, in the preset that `owner.name` names.                                                                     | Absent.                                                 |
| `guardrail`  | An administrator of the workspace, in the guardrail that `owner.name` names. `owner.on_key` is true when the guardrail is linked to this key. | Absent.                                                 |

`owner` is absent when the state of the market caused the reason.

## Exclusion reasons

Each entry in `eligibility.excluded` has `provider`, `offer_id`, `reason` and sometimes `detail`. The tables group the reasons by what you change.

### Change the model or the provider

- `job_kind_not_served`: the offer makes a different output, such as an image offer for a chat job.
- `model_not_served`: the offer does not serve the requested model.
- `model_not_allowed`: excluded by `exchange.candidates.models.exclude`, or outside the preset of the API key.
- `provider_not_allowed`: excluded by `exchange.candidates.providers.allow`.
- `vendor_not_allowed`: excluded by `exchange.candidates.creators.allow`.
- `provider_blocked`: excluded by `exchange.candidates.providers.exclude`.
- `pinned_offer_not_resting`: the pinned offer is not on the book.

### Change a price limit

- `cap_exceeds_buyer_cap`: the worst-case charge is more than `cap_usd`. Raise `cap_usd`, or lower `max_tokens`.
- `cap_exceeds_budget`: the worst-case charge is more than the balance or the key limit. Add funds, raise the limit, or lower `max_tokens`.
- `rate_above_ceiling`: a rate is more than `exchange.limits.price.input_usd_per_mtok`, `exchange.limits.price.output_usd_per_mtok`, `exchange.limits.price.cache_read_usd_per_mtok` or `exchange.limits.price.cache_write_usd_per_mtok`.
- `above_cost_tier`: the price is above the requested `exchange.rank.cost_tier`.

### Change a speed limit

- `service_too_slow`: the deadlines of the offer are longer than `exchange.limits.ttft_ms` or `exchange.limits.completion_ms`.
- `throughput_too_low`: the offer gives fewer tokens each second than `exchange.limits.min_tps`.

### Change the request itself

- `feature_unsupported`: the offer does not support a parameter of the request. `detail` names it.
- `media_unsupported`: the offer does not accept the attachment in the form sent. `detail` names the constraint, such as `image.max_bytes`.
- `context_too_large`: the input and `max_tokens` together exceed the context window of the offer. See [Output limits](#output-limits).
- `output_bound_unsupported`: `max_tokens` is outside the output limit of the offer. On a text job, `detail` gives the request and the range, such as `asked 200000, accepts 1 to 8192`. On an image, speech, video or embedding job, `detail` names the parameter that the deployment does not serve. See [Output limits](#output-limits).

### Change a data rule

- `region_policy`: excluded by `exchange.privacy.regions`.
- `retention_policy`: the offer cannot meet the requested `exchange.privacy.retention`.
- `workload_profile_not_targeted`: the offer does not target the requested `exchange.rank.workload_profile`.

### Nothing to change

- `offer_expired`: the offer expired before the auction ran. Send the request again.
- `superseded_by_own_offer`: the same provider had an eligible offer that ranked ahead of this one.

### Auto has no score

- `score_unmapped`: Auto has no score for the deployment at the reasoning setting of the request.
- `score_missing`: the deployment has no score for the task Auto ranked by. `detail` names the score field.

## Output limits

`max_tokens` is a maximum for the answer. If every offer is excluded, and `output_bound_unsupported` or `context_too_large` excluded at least one, the auction runs again. It tries a lower `max_tokens` for each of those offers, from the largest value to the smallest.

- `output_bound_unsupported`: the lower value is the output limit of the offer.
- `context_too_large`: the lower value is the room that the input leaves in the context window, and not more than the output limit.
- An offer gives no value if the room is less than 1,024 tokens, or if another reason excluded it.
- The auction keeps the first value that an offer accepts.

The exchange calculates the cap from the lower value. An answer that reaches it stops with `finish_reason: "length"` (`stop_reason: "max_tokens"` on the Anthropic endpoint).

A request with no limit (`max_tokens`, `max_completion_tokens` or `max_output_tokens`) starts at 32,768 tokens. If the cap or your balance excludes all offers, the exchange halves that limit until an offer fits, down to 1,024 tokens. It never changes a limit that you set.

- If your limit does not fit your balance, the response is `402 insufficient_balance`. `needed_usd` is the lowest cap for your limit.
- If your limit does not fit your cap, the response is `400 no_eligible_offer` with `param` set to `exchange.cap_usd`.
- The exchange never lowers `max_tokens` to or below a `thinking` budget. It refuses such a request with `no_eligible_offer`.

### Reasoning models

The thinking of a reasoning model counts toward `max_tokens`, and the exchange bills it as output.

- On `POST /v1/chat/completions`, the thinking arrives as `reasoning`, on each streamed `delta` or on the `message`.
- `usage.completion_tokens_details.reasoning_tokens` and `exchange.reasoning_tokens` give the tokens that the thinking used.
- A model that thinks until the limit stops with `finish_reason: "length"` and an empty `content`. You pay for the thinking. Use a larger `max_tokens`.
- A model that stops after its thinking with no text and no tool call gives you nothing to use. The job fails, you pay nothing, and the receipt records `empty_response`.
- On `POST /v1/messages`, the response has thinking blocks only if `thinking` is not `disabled`. Without it, a small `max_tokens` can end with no text.

## Tool calls

If the request sends `tools`, the exchange checks each tool call in the answer. A call is bad when it has one of these faults:

| Fault             | Meaning                                          |
| ----------------- | ------------------------------------------------ |
| `invalid_json`    | The arguments are not JSON.                      |
| `unknown_name`    | The name is not one of the tools in the request. |
| `schema_mismatch` | The arguments break the schema of the tool.      |

The [receipt](https://inference.ai.exchange/docs/rules.md#response-fields) has the counts in `answer_checks.tool_calls`, `answer_checks.tool_call_errors` and `answer_checks.tool_call_faults`.

- **Not streamed.** The exchange reads the whole answer first. If a call is bad, it drops the answer and sends the job to the next offer. If no offer is left, the job fails with `invalid_tool_call`.
- **Streamed.** The exchange counts the bad calls and keeps the answer.

**Tool-call markup.** Some providers do not turn the calls of a model into `tool_calls`, and your agent loop stops. The exchange sets `answer_checks.tool_markup` to `1` when the request sent tools, the answer has no tool call, and the text holds DeepSeek, Hermes, Qwen or Kimi markup. It keeps the answer and records the markup for the deployment.

## Structured output

If you send `response_format` with `json_object` or `json_schema`, the exchange checks the answer when the job ends. `json_object` asks for one JSON value. `json_schema` also asks that the value matches your schema. On `POST /v1/messages` it checks `output_config.format`, and on `POST /v1/responses` it checks `text.format`.

The result is in `exchange.answer_checks.structured_output`, on the last chunk of a stream.

| `result`          | Meaning                                                                                                  |
| ----------------- | -------------------------------------------------------------------------------------------------------- |
| `passed`          | The answer is JSON, and for `json_schema` it matches the schema.                                         |
| `not_json`        | The answer is not one JSON value. Text around the value, or a code fence, gives this result.             |
| `schema_mismatch` | The answer is JSON but breaks the schema. `keyword` names the JSON Schema keyword, such as `required`.   |
| `cut`             | The answer stopped at `max_tokens`. Send the request again with a larger `max_tokens`.                   |
| `refused`         | The answer has no text, or the model refused.                                                            |
| `unchecked`       | The exchange did not check the answer: only tool calls, or the schema or answer is too large or invalid. |

- **Not streamed.** For `not_json` or `schema_mismatch`, the exchange drops the answer and sends the job to the next offer. If no offer is left, the job fails with no charge, and the receipt records `invalid_structured_output`.
- **Streamed.** You receive the answer as the provider sent it, and you pay for it.

Only `not_json` and `schema_mismatch` count against the provider.

If the answer writes more than 1,024 whitespace characters in a row outside a JSON string, the exchange stops it and counts it against the provider. A request that is not streamed goes to the next offer. A stream fails with `provider_failed`, and you pay nothing.

The receipt keeps the result and the keyword, not the path of the failed field.
