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

> ## Agent Instructions
> Phaseo Gateway base URL: https://api.phaseo.app/v1. Send `Authorization: Bearer $PHASEO_API_KEY` with every request.
> Model IDs look like `provider/model`, for example `openai/gpt-6-astra`. Do not guess IDs: list them with `GET /v1/models` or check https://phaseo.app/models. `phaseo/free` routes to an available free model and needs no wallet balance.
> For new text integrations prefer `POST /v1/responses`. `POST /v1/chat/completions` (OpenAI format) and `POST /v1/messages` (Anthropic format) suit existing code.
> The full API contract is https://phaseo.app/docs/openapi/v1/openapi.yaml. Official SDKs: `@phaseo/sdk` (TypeScript) and `phaseo` (Python), plus Go, C#, PHP, Ruby and Rust.
> Search these docs over MCP at https://phaseo.app/docs/mcp, and install Phaseo skills with `npx skills add https://phaseo.app/docs`. If a page is wrong or out of date, report it with the MCP `submit_feedback` tool.
> Manage Phaseo accounts with the `phaseo` CLI (`npm install -g @phaseo/cli`, then `phaseo login` or `phaseo login --device-code`), the `/v1` Management API or the MCP server at `https://mcp.phaseo.app/mcp?access=manage`. Do not send users to the dashboard for account tasks. A person is still needed for sign-up, approving a sign-in, paying the first top-up on the checkout link that `phaseo credits add` returns when no card is saved, confirming each saved-card top-up from the emailed link, and account security (password, MFA, passkeys). Create API keys with `phaseo keys create --name <name> --json`; never guess one. Credit top-ups need `phaseo login --scopes billing:write` first. Task map: https://phaseo.app/docs/v1/developers/manage-from-an-agent.md.

# Errors and Debugging

> Understand Phaseo error responses and fix common request, provider, and routing issues faster.

Use this page to understand what A Phaseo error means and what to do next.

Every error response follows the same JSON format, so your app can handle failures consistently across models and providers. The `error` field is an object with `code`, `message` and `type`, the shape OpenAI-compatible SDKs read, so their exceptions carry the gateway's message.

## Example error response

```json theme={null}
{
  "error": {
    "code": "upstream_error",
    "message": "Provider \"google-ai-studio\" failed with HTTP 403 for endpoint \"responses\" on model \"google/gemini-2.5-pro\".",
    "type": "server_error",
    "param": null,
    "metadata": {
      "reason": "all_candidates_failed",
      "attempt_count": 1,
      "failed_providers": ["google-ai-studio"],
      "failed_statuses": [403],
      "upstream_error": {
        "code": "PERMISSION_DENIED",
        "message": "The caller does not have permission.",
        "description": null,
        "param": null
      }
    }
  },
  "code": "upstream_error",
  "message": "Provider \"google-ai-studio\" failed with HTTP 403 for endpoint \"responses\" on model \"google/gemini-2.5-pro\".",
  "generation_id": "G-abc123",
  "status_code": 502,
  "error_type": "system",
  "error_origin": "upstream",
  "reason": "all_candidates_failed",
  "description": "Provider \"google-ai-studio\" failed with HTTP 403 for endpoint \"responses\" on model \"google/gemini-2.5-pro\".",
  "attempt_count": 1,
  "failed_providers": ["google-ai-studio"],
  "failed_statuses": [403],
  "provider_failure_diagnostics": {
    "category": "provider_access_missing",
    "hint": "The provider account appears not to have access to this model or feature yet. Verify account entitlements and provider-side access.",
    "provider": "google-ai-studio"
  },
  "upstream_error": {
    "code": "PERMISSION_DENIED",
    "message": "The caller does not have permission.",
    "description": null,
    "param": null
  },
  "failure_sample": [
    {
      "provider": "google-ai-studio",
      "type": "upstream_non_2xx",
      "status": 403,
      "upstream_error_code": "PERMISSION_DENIED",
      "upstream_error_message": "The caller does not have permission.",
      "upstream_error_description": null,
      "upstream_error_param": null,
      "upstream_payload_preview": "{\"error\":{\"status\":\"PERMISSION_DENIED\"}}",
      "retryable": false
    }
  ]
}
```

## Fields you will always get

* `error`: an object describing the failure:
  * `error.code`: a machine-readable error code, such as `validation_error`.
  * `error.message`: a plain-language explanation of what happened.
  * `error.type`: the error type (see [Error types](#error-types)).
  * `error.param`: the request parameter that caused the error, when the gateway knows it, otherwise `null`.
  * `error.metadata` (optional): structured detail such as the failing provider, the subreason, the failed statuses or `retry_after_seconds`. The same values are also returned at the top level.
* `code`: the same code as `error.code`. Earlier responses returned this code string as `error`; read `code` if your client expected a string there.
* `message`: the same text as `error.message`.
* `generation_id`: a stable request ID you can share with support.
* `status_code`: mirrors the HTTP status code.
* `error_type`: a high-level class, usually `user` or `system`.
* `error_origin`: whether the problem was mainly caused by the caller, Phaseo, or an upstream provider.
* `description`: a plain-language explanation of what happened.
* `details` (optional): structured validation details when the error is a request-validation problem.

## Extra fields you may see

Some errors include more detail to help you fix the issue faster:

* `reason`: a more specific subreason such as `all_candidates_failed` or `pricing_not_configured`.
* `provider_candidate_diagnostics` and `provider_enablement`: explain why a model could not be used with the requested endpoint.
* `routing_diagnostics`: extra detail about how routing or availability checks narrowed the request.
* `provider_failure_diagnostics`: hints for missing credentials, missing access, region restrictions, rate limits, and similar provider-side failures.
* `upstream_error` and `failure_sample`: best-effort summaries of the first provider failure when the request reached a provider.
* `failed_providers`, `failed_statuses`, and `attempt_count`: extra retry and failover context.

## Error types

`error.type` follows OpenAI's [documented error types](https://platform.openai.com/docs/guides/error-codes) and is set from the HTTP status:

| Status | `error.type` |
| - | - |
| `400`, `409`, `410`, `413`, `422` and other 4xx | `invalid_request_error` |
| `401` | `authentication_error` |
| `402` | `insufficient_quota` |
| `403` | `permission_error` |
| `404` | `not_found_error` |
| `429` | `rate_limit_error` |
| `503` | `service_unavailable` |
| `529` | `overloaded` |
| `500`, `502` and other 5xx | `server_error` |

Errors on `/v1/messages` use the same body, so Anthropic SDKs also find `error.type` and `error.message`.

## Status class guidance

| Status | Meaning | Recommended action |
| - | - | - |
| `400-499` | Request, auth, or permission problem | Fix the request or credentials before retrying. |
| `429` | Provider or route-level throttling | Retry with backoff and respect `Retry-After`. |
| `500-599` | Phaseo or upstream provider failure | Retry with jittered backoff and log the request ID. |

## Common error codes

| `error.code` | HTTP Status | `error.type` | Description |
| - | - | - | - |
| `unauthorised` | `401` | `authentication_error` | Missing or invalid API key. |
| `guardrail_blocked` | `403` | `permission_error` | A workspace guardrail or access policy blocked the request. |
| `not_found` | `404` | `not_found_error` | The endpoint or resource could not be found. |
| `model_retired` | `410` | `invalid_request_error` | The model is retired and no provider serves it any more. Switch to `replacement_model`. |
| `insufficient_funds` | `402` | `insufficient_quota` | The workspace does not have enough credits for the request. |
| `key_limit_exceeded` | `429` | `rate_limit_error` | The API key reached one of its limits. |
| `phaseo_free_model_limit_exceeded` | `429` | `rate_limit_error` | Phaseo's shared daily free-model allowance is exhausted (1,500 requests by default). Honor `Retry-After`, wait for the 00:00 UTC reset, or choose a paid model. |
| `rate_limit_exceeded` | `429` | `rate_limit_error` | A workspace [rate limit policy](https://phaseo.app/docs/v1/api-reference/limits.md#rate-limit-policies) is exhausted. `policy_id` and `limit_scope` identify it. Honor `Retry-After`, or ask a workspace admin to change the policy. |
| `validation_error` | `400` | `invalid_request_error` | Invalid parameters or request body. `error.param` names the first invalid field. |
| `upstream_error` | `429` | `rate_limit_error` | Every provider tried for the request rate limited it. Honor `Retry-After`; BYOK requests also carry `X-Phaseo-Upstream-RateLimit-*` headers. |
| `upstream_error` | `502` | `server_error` | The upstream model provider failed to respond correctly. |
| `upstream_stream_failure` | `502` | `server_error` | A non-streaming request whose provider failed after it started generating. No partial output is returned and the request is not charged. |
| `gateway_error` | `500` | `server_error` | An unexpected issue happened inside Phaseo. |

## When a provider fails

If Phaseo reached a provider but the request still failed, you may see:

* `provider_failure_diagnostics.category`
* `provider_failure_diagnostics.hint`
* `provider_failure_diagnostics.provider`

Current categories include:

* `credentials_not_configured`
* `credentials_invalid_or_forbidden`
* `provider_access_missing`
* `region_or_project_restriction`
* `model_unavailable_for_endpoint`
* `rate_limited`
* `server_error`

These fields are meant to help you fix the problem without turning on full debug mode.

## When a model or endpoint is not available

For `unsupported_model_or_endpoint` responses, Phaseo may include:

* `provider_candidate_diagnostics`
* `provider_enablement`
* `missing_pricing_providers`
* `routing_diagnostics`

This helps you distinguish between:

* a known model that is not active yet
* a model that does not support the endpoint you asked for
* missing pricing data
* rollout or internal availability restrictions

Common fields:

* `provider_candidate_diagnostics.totalProviders`: how many providers were known for the model before endpoint filtering.
* `provider_candidate_diagnostics.supportsEndpointCount`: how many of those providers support the requested endpoint.
* `provider_candidate_diagnostics.candidateCount`: how many providers remained after adapter checks.
* `provider_candidate_diagnostics.droppedUnsupportedEndpoint`: providers dropped because they do not support the endpoint.
* `provider_candidate_diagnostics.droppedMissingAdapter`: provider/endpoint pairs dropped because there is no gateway adapter for that endpoint yet.
* `provider_enablement.capability`: the capability gate being enforced, such as `video_generation`.
* `provider_enablement.providersBefore` / `provider_enablement.providersAfter`: providers before and after capability or enablement filtering.
* `provider_enablement.dropped[].reason`: machine-readable reasons such as `pricing_missing`.
* `routing_diagnostics.filterStages[].stage`: the routing stage, for example capability, rollout, or routing-status filtering.
* `routing_diagnostics.filterStages[].beforeCount` / `routing_diagnostics.filterStages[].afterCount`: provider counts before and after each stage.
* `routing_diagnostics.filterStages[].droppedProviders[].reason`: machine-readable reasons such as rollout or routing restrictions.

### Example

```json theme={null}
{
  "error": {
    "code": "unsupported_model_or_endpoint",
    "message": "No provider is currently routable for endpoint \"responses\" on model \"example/model\".",
    "type": "invalid_request_error",
    "param": null
  },
  "code": "unsupported_model_or_endpoint",
  "generation_id": "G-unsupported123",
  "status_code": 400,
  "description": "No provider is currently routable for endpoint \"responses\" on model \"example/model\".",
  "provider_candidate_diagnostics": {
    "totalProviders": 3,
    "supportsEndpointCount": 2,
    "candidateCount": 1,
    "droppedUnsupportedEndpoint": ["provider-a"],
    "droppedMissingAdapter": [
      {
        "providerId": "provider-b",
        "endpoint": "responses"
      }
    ]
  },
  "provider_enablement": {
    "capability": "responses",
    "providersBefore": ["provider-b", "provider-c"],
    "providersAfter": ["provider-c"],
    "dropped": [
      {
        "providerId": "provider-b",
        "reason": "pricing_missing"
      }
    ]
  },
  "routing_diagnostics": {
    "filterStages": [
      {
        "stage": "provider_routing_status",
        "beforeCount": 1,
        "afterCount": 0,
        "droppedProviders": [
          {
            "providerId": "provider-c",
            "reason": "provider_status_not_ready"
          }
        ]
      }
    ]
  }
}
```

## When a model is retired

A retired model is still served while any provider serves it. Once none does,
requests for it fail with `410` and the `model_retired` error:

```json theme={null}
{
  "error": {
    "code": "model_retired",
    "message": "Model \"lab/old-model\" was retired on 2026-09-30 and no provider serves it any more. Use \"lab/new-model\" instead.",
    "type": "invalid_request_error",
    "param": null,
    "metadata": {
      "retired_at": "2026-09-30T00:00:00Z",
      "replacement_model": "lab/new-model"
    }
  },
  "code": "model_retired",
  "generation_id": "G-retired123",
  "status_code": 410,
  "reason": "model_retired_without_providers",
  "description": "Model \"lab/old-model\" was retired on 2026-09-30 and no provider serves it any more. Use \"lab/new-model\" instead.",
  "model": "lab/old-model",
  "retired_at": "2026-09-30T00:00:00Z",
  "replacement_model": "lab/new-model"
}
```

* `model`: the model you requested.
* `retired_at`: when the model was retired, or `null` if no date is recorded.
* `replacement_model`: the suggested replacement, or `null` if there is none.

Retrying does not help. Update the model ID in your request. To keep serving
these requests while you migrate, add the replacement to your request's
`models` list or turn on retired model routing for the workspace. See
[Model Deprecation and Retirement](https://phaseo.app/docs/v1/guides/model-retirement.md).

## Optional debug mode

Most request schemas support a `debug` object for controlled troubleshooting:

```json theme={null}
{
  "debug": {
    "enabled": true,
    "return_upstream_request": true,
    "return_upstream_response": false,
    "trace": true,
    "trace_level": "summary"
  }
}
```

Available fields:

* `enabled`
* `return_upstream_request`
* `return_upstream_response`
* `trace`
* `trace_level` (`summary` or `full`)

Use debug mode in development or tightly controlled environments only.

## Retry strategy

* **400-series errors other than 429:** Fix the request, credentials, or access policy before retrying.
* **429:** Implement exponential backoff and honor the `Retry-After` header.
* **500-series errors:** Use bounded retries only when repeating the operation is safe. An ambiguous submission may already have created a job or incurred a charge; retrieve an accepted job instead of submitting it again.

Set an attempt limit and an overall deadline, and add jitter to backoff delays. See [rate limits](https://phaseo.app/docs/v1/api-reference/limits.md) for response headers and retry handling.

## Streaming-specific notes

* If the request fails before streaming begins, you get a standard JSON error payload.
* If it fails mid-stream, the stream ends with the endpoint's failure event instead of a normal finish: an `error` frame on Chat Completions, `response.failed` (or `error`) on Responses, and an `error` event on Messages. Treat the partial stream as incomplete and show a retry option.
* The Chat Completions `error` frame carries the same error object, with `error.type` set from the code:

  ```json theme={null}
  {
    "object": "error",
    "message": "The upstream stream ended before the response completed.",
    "code": "upstream_stream_failure",
    "error": {
      "code": "upstream_stream_failure",
      "message": "The upstream stream ended before the response completed.",
      "type": "server_error",
      "param": null
    }
  }
  ```
* A non-streaming request whose provider fails part-way returns `502` with `upstream_stream_failure`, not a partial response.
* Always capture `generation_id` and endpoint/model metadata in logs.

## Troubleshooting tips

* Check the [Gateway status page](https://status.phaseo.app) for ongoing incidents.
* Review your request payload against the endpoint docs.
* Share `generation_id` when contacting support.

## Related resources

> [card] [Authentication](https://phaseo.app/docs/v1/developers/authentication.md)
>
> Authenticate with bearer API keys.

> [card] [Limits](https://phaseo.app/docs/v1/api-reference/limits.md)
>
> Handle provider-driven throttling and retries.

> [card] [Streaming](https://phaseo.app/docs/v1/guides/streaming.md)
>
> Use SSE safely in production flows.

Error-handling rules for agents, summarising this page:

* Branch on `error.code` (also returned as top-level `code`), `error.type` and `status_code`, not on the message text. Log `generation_id` with every failure.
* `400`, `401`, `403` and `404`: fix the request, key or access first. Do not retry the same request unchanged.
* `410` with `model_retired`: switch to `replacement_model` when it is not `null`; otherwise choose a new model from `GET /v1/models`.
* `429`: when `Retry-After` is present (seconds or an HTTP date), wait that long before retrying; otherwise use jittered exponential backoff. A `key_limit_exceeded` error without `Retry-After` does not reset by itself, so do not retry it.
* `500` to `599`: retry only when repeating the call is safe, with an attempt limit, an overall deadline and jittered exponential backoff. For async jobs, look up the accepted job instead of submitting it again.
* Treat debug payloads as sensitive and redact them before writing persistent logs.
* For tested retry and error-parsing patterns, install the `phaseo-gateway` skill: `npx skills add https://phaseo.app/docs --skill phaseo-gateway`.


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