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

# API

> OpenAI- and Anthropic-compatible endpoints exposed by the Manifest LLM Gateway.

The gateway exposes both OpenAI and Anthropic-format endpoints on one proxy. Point your client at the gateway URL, send `auto` as the model, and routing picks the real model behind the scenes.

## Base URL

| Mode        | URL                                           |
| ----------- | --------------------------------------------- |
| Cloud       | `https://app.manifest.build`                  |
| Self-hosted | `http://localhost:2099` (or your custom port) |

## Authentication

Every request requires a harness key:

```http theme={"theme":{"light":"github-light","dark":"github-dark"}}
Authorization: Bearer mnfst_YOUR_KEY_HERE
```

Generate a key from the dashboard's **Harnesses** page. Keys always start with `mnfst_`.

## Endpoints

| Method | Path                   | Format           | Use it for                                                        |
| ------ | ---------------------- | ---------------- | ----------------------------------------------------------------- |
| `POST` | `/v1/chat/completions` | OpenAI           | Most clients (OpenAI SDK, LangChain, Vercel AI SDK, custom HTTP)  |
| `POST` | `/v1/responses`        | OpenAI Responses | Codex, `*-pro`, `o1-pro`, deep-research models                    |
| `POST` | `/v1/messages`         | Anthropic        | Anthropic SDK, Claude Code, anything that speaks the Messages API |
| `GET`  | `/v1/models`           | OpenAI           | Listing the models your harness can route to                      |

The proxy translates between formats internally, so you can send an OpenAI-shaped request and the gateway will reshape it before forwarding to an Anthropic-only model. The reverse works too.

Translation carries what the request itself contains: messages, tools, and tool results. The gateway does not resolve `previous_response_id`, so send the full conversation in `input` on every `/v1/responses` request.

## Chat completions

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST http://localhost:2099/v1/chat/completions \
  -H "Authorization: Bearer mnfst_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "messages": [
      {"role": "user", "content": "What is the capital of France?"}
    ]
  }'
```

The gateway adapts the body to the provider it picked. It replaces `model` with the real model ID. It converts the body to the format that provider expects. It renames parameters the provider spells differently, like `max_tokens` and `max_completion_tokens`. And it merges the model parameters saved on your harness into every attempt. Some fields only exist at OpenAI: `stream_options`, `reasoning_effort`, `modalities`, `audio`, and `prediction`. Those reach OpenAI and OpenRouter. Every other provider gets the request without them, and nothing warns you.

## Anthropic messages

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST http://localhost:2099/v1/messages \
  -H "Authorization: Bearer mnfst_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "auto",
    "max_tokens": 1024,
    "messages": [
      {"role": "user", "content": "Hello"}
    ]
  }'
```

## Listing models

`GET /v1/models` returns the models your harness can reach, in OpenAI format. The first entry is always `auto` (routing); the rest are the real model IDs from your connected providers.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl http://localhost:2099/v1/models \
  -H "Authorization: Bearer mnfst_YOUR_KEY_HERE"
```

Send `auto` to let the gateway route, or send any listed model ID to skip routing and go straight to that provider. If you send a model ID that no connected provider can serve, the gateway returns [M302: Model not available](/llm-gateway/docs/llm-gateway/docs/errors/M302). See [Routing → Route a specific model](/llm-gateway/docs/llm-gateway/docs/llm-gateway#route-a-specific-model).

### Inspect model capabilities

Add `?capabilities=true` to include known capability metadata for each concrete model. Without this query parameter, the response keeps the standard OpenAI model-list shape.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "http://localhost:2099/v1/models?capabilities=true" \
  -H "Authorization: Bearer mnfst_YOUR_KEY_HERE"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "object": "list",
  "data": [
    {
      "id": "auto",
      "object": "model",
      "created": 0,
      "owned_by": "manifest"
    },
    {
      "id": "openai/gpt-5.4-mini-subscription",
      "object": "model",
      "created": 0,
      "owned_by": "openai",
      "capabilities": {
        "input_modalities": ["text", "image"],
        "output_modalities": ["text"],
        "features": ["stream", "tools"]
      }
    }
  ]
}
```

| Field                 | Meaning                                                                                                      |
| --------------------- | ------------------------------------------------------------------------------------------------------------ |
| `input_modalities`    | Input types the model accepts, such as `text` or `image`                                                     |
| `output_modalities`   | Output types the model can produce                                                                           |
| `features`            | Known feature support: `stream` and `tools`                                                                  |
| `supported_endpoints` | API endpoint formats the model supports. Present only when the provider publishes them in its own model list |

Capability fields are optional. A missing field means that support is unknown, not that the model does not support it. The gateway omits the entire `capabilities` object when it has no known metadata for a model.

The synthetic `auto` model never includes capabilities because it can resolve to a different model for each request. Concrete model IDs remain directly routable exactly as listed, including IDs with the `-subscription` suffix.

### Inspect model costs

Add `?cost=true` to include known token prices for each concrete model. Prices are in USD per million tokens. Without this query parameter, the response keeps the standard OpenAI model-list shape.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "http://localhost:2099/v1/models?cost=true" \
  -H "Authorization: Bearer mnfst_YOUR_KEY_HERE"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "object": "list",
  "data": [
    {
      "id": "auto",
      "object": "model",
      "created": 0,
      "owned_by": "manifest"
    },
    {
      "id": "openai/gpt-5.4-mini",
      "object": "model",
      "created": 0,
      "owned_by": "openai",
      "cost": {
        "input": 0.75,
        "output": 4.5
      }
    },
    {
      "id": "openrouter/example-free-model",
      "object": "model",
      "created": 0,
      "owned_by": "openrouter",
      "cost": {
        "input": 0,
        "output": 0
      }
    }
  ]
}
```

| Field    | Meaning                       |
| -------- | ----------------------------- |
| `input`  | USD per million input tokens  |
| `output` | USD per million output tokens |

A zero value means the model has no per-token charge, as with some free or subscription-backed routes. If one price is unknown, the gateway omits only that field. If both prices are unknown, the gateway omits the entire `cost` object.

The synthetic `auto` model never includes cost because its concrete model is selected for each request. To inspect both metadata types in one response, combine the query parameters: `?capabilities=true&cost=true`.

## Streaming

Set `"stream": true` to get an SSE stream back. The stream format matches the upstream protocol: OpenAI-style `data: {...}` chunks for `/v1/chat/completions`, Anthropic event blocks for `/v1/messages`.

Routing and fallback both work with streams. If the primary model fails before the first chunk, the request restarts on the fallback. If it fails mid-stream, the connection closes. There's no silent mid-stream retry.

## Errors

Errors come in two shapes, depending on the caller. A tool or SDK call gets a real HTTP status and the JSON envelope below. A chat or streaming client gets an HTTP `200` that looks like a normal completion, and the error text sits inside the assistant message.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "error": {
    "message": "[🦚 Manifest M005] I don't recognize this key. ...",
    "type": "auth_error",
    "code": "manifest_auth"
  }
}
```

`error.code` is a string identifier, never the numeric HTTP status.

| Status | Meaning                                                                                                                                                                                        |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Invalid or missing `Authorization` header                                                                                                                                                      |
| `402`  | LLM Gateway Cloud Free plan quota reached (`error.code = PLAN_LIMIT_REQUESTS`, [M204](/llm-gateway/docs/llm-gateway/docs/errors/M204)), or provider billing/quota error from the upstream                       |
| `429`  | gateway rate limit tripped ([M201](/llm-gateway/docs/llm-gateway/docs/errors/M201), [M202](/llm-gateway/docs/llm-gateway/docs/errors/M202), [M203](/llm-gateway/docs/llm-gateway/docs/errors/M203)), or a rate limit the provider itself returned |
| `5xx`  | Upstream provider error (triggers [fallback](/llm-gateway/docs/llm-gateway/docs/llm-gateway#fallback))                                                                                                          |

A [hard limit](/llm-gateway/docs/llm-gateway/docs/llm-gateway#hard-limits) block ([M200](/llm-gateway/docs/llm-gateway/docs/errors/M200)) is not in this table: it comes back as an HTTP `200` chat completion whose assistant message carries the block text. When the fallback chain is exhausted, the response keeps the primary model's real error status and carries `X-Manifest-Fallback-Exhausted: true`; the body keeps the provider's own error code, or `fallback_exhausted` when there is none.

## Rate limits

The gateway enforces three caps. A workspace can send **200 requests per minute** ([M201](/llm-gateway/docs/llm-gateway/docs/errors/M201)). An IP can send **500 requests per minute** ([M202](/llm-gateway/docs/llm-gateway/docs/errors/M202)). A workspace can have **10 requests in flight at once** ([M203](/llm-gateway/docs/llm-gateway/docs/errors/M203)). The caps are the same on Cloud and self-hosted. No setting changes them.

Your Cloud plan sets your monthly request quota ([M204](/llm-gateway/docs/llm-gateway/docs/errors/M204)). The per-minute caps stay the same on every plan.

## Response headers

Routed responses carry [routing headers](/llm-gateway/docs/llm-gateway/docs/reference/headers). They tell your client which model and tier handled the request, with no need to parse the response body. A request rejected before routing (bad key, quota, rate limit) carries none.
