# API overview

> Conventions shared by every Mortar API endpoint — base URL, authentication, JSON bodies, the error envelope, status codes, rate limits, request IDs and OpenAI compatibility.

The Mortar API is served by the data plane: the gateway that runs the model, whether in Mortar's cloud or in yours. The chat-completions and model endpoints follow the OpenAI API; the rest are Mortar's own.

## Base URL

```text title="Base URL"
https://api.mortar.one/v1
```

The health and readiness checks are the only endpoints outside `/v1`. With [Bring Your Own Cloud](/docs/guides/deployment-models), your deployment has its own base URL inside your network, and every path on this page works the same there.

## Endpoints

| Endpoint | Purpose | Key needed |
| --- | --- | --- |
| [`POST /v1/chat/completions`](/docs/api/chat-completions#create-a-chat-completion) | Generate a reply, whole or streamed | Yes |
| [`POST /v1/generations/{id}/stop`](/docs/api/chat-completions#stop-a-generation) | Stop a reply that is still being generated | Yes |
| [`GET /v1/models`](/docs/api/models#list-models) | List the models in the catalog | Yes |
| [`POST /v1/cost/analyze`](/docs/api/cost#analyze-a-prompt) | Find what a prompt wastes on prefill | No |
| [`POST /v1/cost/estimate`](/docs/api/cost#estimate-a-monthly-bill) | Project a monthly bill at a cache hit rate | No |
| [`GET /v1/rooms`](/docs/api/conversations#list-conversations) | List saved conversations | Yes |
| [`POST /v1/rooms`](/docs/api/conversations#create-a-conversation) | Start a conversation | Yes |
| [`PATCH /v1/rooms/{id}`](/docs/api/conversations#update-a-conversation) | Rename or end a conversation | Yes |
| [`DELETE /v1/rooms/{id}`](/docs/api/conversations#delete-a-conversation) | Delete a conversation | Yes |
| [`GET /v1/rooms/{id}/messages`](/docs/api/conversations#list-messages) | Page through a conversation | Yes |
| [`POST /v1/rooms/{id}/messages`](/docs/api/conversations#add-a-message) | Save a message | Yes |
| [`GET /v1/telemetry/stream`](/docs/api/telemetry#stream-serving-telemetry) | Follow routing, cache and cost events | No |
| [`GET /v1/metrics/stream`](/docs/api/telemetry#stream-fleet-metrics) | Follow fleet-wide counters | No |
| [`GET /v1/fleet`](/docs/api/platform#get-the-fleet) | Read sample deployments, quotas and KPIs | Yes |
| [`GET /v1/platform`](/docs/api/platform#get-the-platform-overview) | Read the sample organization and catalog | No |
| [`GET /v1/suggestions`](/docs/api/platform#list-prompt-suggestions) | Read starter prompts | No |
| [`GET /health`](/docs/api/platform#check-health) | Check that the data plane is up | No |
| [`GET /readyz`](/docs/api/platform#check-readiness) | Check whether an instance should get traffic | No |

## Authentication

Send an API key as a bearer token on every endpoint marked above. [Authentication](/docs/authentication) covers creating and revoking keys.

```text title="Header"
Authorization: Bearer $MORTAR_API_KEY
```

## Requests

Send request bodies as JSON with `Content-Type: application/json`. A body in any other format is refused with `415`. Bodies are limited to 1 MiB; a larger one is refused with `413`.

Successful JSON responses carry a weak `ETag`. Send it back in `If-None-Match` and Mortar answers `304 Not Modified` with no body when nothing changed. JSON responses of 512 bytes or more are gzip-compressed when you send `Accept-Encoding: gzip`; event streams are never compressed.

## Request IDs

Every response carries an `X-Request-Id` header. Send your own `X-Request-Id` and Mortar echoes it back; otherwise it generates one. Log it next to your own request: it's the fastest way to find a request when you contact support.

| Header | Direction | Meaning |
| --- | --- | --- |
| `X-Request-Id` | Request and response | Correlates one request across your logs and Mortar's. |
| `X-Mortar-Request-Id` | Response | The chat completion's own ID, also returned as `id` in the body. |

## Errors

Errors use the same envelope as the OpenAI API, so SDK error handling works unchanged.

```json title="Error envelope" example
{
  "error": {
    "message": "The model `gpt-4o` does not exist.",
    "type": "invalid_request_error",
    "code": "model_not_found"
  }
}
```

`message` is written for a person. Branch on `code` where there is one, otherwise on the HTTP status; `code` is present only when there's something specific you can do about the error.

## Status codes

| Status | Type | Code | Meaning |
| --- | --- | --- | --- |
| 200 | | | The request succeeded. |
| 201 | | | A conversation or message was created. |
| 304 | | | Your cached copy is current. |
| 400 | `invalid_request_error` | `context_length_exceeded` | The body or a header isn't valid, or the prompt leaves too little room for a reply. |
| 401 | `authentication_error` | `missing_api_key`, `invalid_api_key` | The key is missing or not valid. |
| 403 | `forbidden_origin` | | A browser request came from a site Mortar doesn't trust. |
| 404 | `invalid_request_error` | `not_found`, `model_not_found` | The endpoint, conversation or model doesn't exist. |
| 405 | `invalid_request_error` | `method_not_allowed` | The path exists but not with this method. |
| 409 | `invalid_request_error` | `room_limit_reached` | You've reached the limit of 200 saved conversations. |
| 413 | `invalid_request_error` | `input_too_large` | The body is larger than 1 MiB, or the cost analyser was sent more than 64 KiB of text. |
| 415 | `unsupported_media_type` | | The body isn't JSON. |
| 429 | `rate_limit` | | You've sent more than 600 requests in a minute. |
| 500 | `server_error` | | Something failed inside Mortar. Retry, and quote the request ID if it persists. |
| 502 | `upstream_error` | `upstream_error`, `upstream_auth_error` | The model server failed or refused Mortar's credentials. |
| 503 | `invalid_request_error`, `upstream_error`, `authentication_error`, `draining` | `model_not_available`, `upstream_unavailable`, `verifier_unavailable` | The model or a service it depends on is unavailable, or the instance is shutting down. Retry with backoff. |
| 504 | `upstream_error` | `upstream_timeout` | The model server didn't answer in time. |

## Rate limits

Each API key may send 600 requests per minute to a data-plane replica. Requests without a key are counted by client IP address instead. The window is fixed: it starts with your first request and resets one minute later.

Over the limit, Mortar answers `429` with a `Retry-After: 60` header. Wait that long before retrying.

```json title="Response" example
{
  "error": {
    "message": "Too many requests. Slow down and try again shortly.",
    "type": "rate_limit"
  }
}
```

The [cost analyser](/docs/api/cost#analyze-a-prompt) has a tighter limit of its own: 30 requests per minute, counted the same way.

When Mortar proxies to a model server that is itself rate limiting, the response is `429` with type `rate_limit_error` and code `upstream_rate_limited`, and `Retry-After` carries the model server's own value when it sent one.

## OpenAI compatibility

Point an OpenAI SDK at the base URL above and chat completions work unchanged, with these differences:

- **Streaming is the default.** A request without `stream` gets an event stream. Pass `stream: false` for a single JSON response.
- **Supported fields.** Mortar reads `model`, `messages`, `stream`, `stream_options.include_usage`, `max_tokens`, `user`, `tools`, `tool_choice` and `parallel_tool_calls`, and the [tool calls and tool results](/docs/api/chat-completions#call-tools) messages carry. Other request fields, such as `temperature`, are accepted and ignored.
- **Text only.** Message content may be a string or an array of parts; only the text parts are used.
- **Default model.** Leave out `model` and the request goes to `Qwen/Qwen2.5-7B-Instruct`.
- **Prompt optimizer.** Mortar can remove insignificant whitespace from JSON in system prompts and tool results before it processes a request. Unless your deployment turns it on, or off, it runs in shadow mode: it measures what it would remove and leaves the prompt unchanged. The `X-Mortar-Optimizer` response header reports what it did or would have done, and `X-Mortar-Optimize: off` opts a request out. See [chat completions](/docs/api/chat-completions#request-headers).
- **Two OpenAI endpoints.** Mortar implements `POST /v1/chat/completions` and `GET /v1/models`. Calls to other OpenAI endpoints, such as embeddings, return `404`.
