---
title: API reference
description: Base URLs, auth headers, response headers, rate limits and retry rules for calling Claude, GPT and Gemini through Half Off Dev's native APIs.
---

Half Off Dev doesn't invent a new API. It serves the three that your code and SDKs already speak, Anthropic Messages, OpenAI Chat Completions (plus Responses) and Gemini, from a single host, with one key and one prepaid balance. Bodies go in and come out in each vendor's schema, so the official SDKs need nothing more than a new base URL and key. What Half Off Dev adds is small: a few response headers, key-level limits and its own billing.

:::cards
- :model: [Models endpoints](/docs/api/models) Every request and response shape, with samples in cURL, Python and Node.js.
- :shield: [Errors](/docs/api/errors) Status codes, error bodies in each format, and a retry recipe.
- :key: [Authentication](/docs/authentication) Where to put the key for each SDK.
- :bolt: [Quickstart](/docs/quickstart) Your first half-price request.
:::

## Base URLs {#base-urls}

Everything is served over HTTPS from `https://halfoff.dev`. Set the SDK's base URL to the value in the second column.

| API | SDK base URL | Endpoints |
| --- | --- | --- |
| Anthropic Messages | `https://halfoff.dev` | `POST /v1/messages`, `POST /v1/messages/count_tokens` |
| OpenAI Chat Completions | `https://halfoff.dev/v1` | `POST /v1/chat/completions` |
| OpenAI Responses (GPT models) | `https://halfoff.dev/v1` | `POST /v1/responses` |
| Gemini | `https://halfoff.dev` | `POST /v1beta/models/{model}:generateContent`, `:streamGenerateContent`, `:countTokens` |
| Model list | — | `GET /v1/models`, `GET /v1beta/models` |

Any other path, OpenAI's Embeddings API for example, answers `404`.

## Authentication {#authentication}

A bearer token works on every endpoint. Each vendor's own header works too, so every SDK can keep its defaults.

```http
Authorization: Bearer ho-...
```

| Header | Sent by |
| --- | --- |
| `Authorization: Bearer ho-...` | OpenAI SDK, plain HTTP clients |
| `x-api-key: ho-...` | Anthropic SDK |
| `x-goog-api-key: ho-...` | Google Gen AI SDK |

The model list is public. Every other endpoint needs a key. Details in [Authentication](/docs/authentication).

## Request and response format {#requests-and-responses}

- **JSON both ways.** Send bodies as JSON with `content-type: application/json`; responses are JSON too.
- **Streaming.** Anthropic and OpenAI requests with `"stream": true` answer with server-sent events (`text/event-stream`). Gemini's `:streamGenerateContent` returns a streamed JSON array, or server-sent events when you add `?alt=sse`, exactly as Google's API does. See [Streaming](/docs/models/streaming).
- **CORS is open** to every origin. That doesn't make browser calls a good idea: a key in front-end code is a key anyone can copy.

## Response headers {#response-headers}

On top of the vendor's own headers, model responses include:

| Header | Meaning |
| --- | --- |
| `x-halfoff-request-id` | Unique id of the call, shown in [Logs](/app/logs). Quote it to support. |
| `x-halfoff-group` | Channel group that served the call |
| `x-halfoff-channel` | Short, URL-encoded label for that channel group |
| `x-halfoff-queued-ms` | How long the call waited for capacity, in milliseconds (only when it waited) |
| `retry-after` | On `429`: seconds to wait before trying again |

## Versioning {#versioning}

Each API keeps the vendor's own versioning. Send what your SDK sends: `anthropic-version` and `anthropic-beta` for Anthropic, `openai-beta` for OpenAI; Gemini uses the `/v1beta` path like Google's API. Half Off Dev forwards those headers and no other vendor headers, so a feature that depends on some other vendor header won't be available.

Responses can gain fields over time. Write clients that skip fields they don't know.

## Rate limits {#rate-limits}

| Limit | Applies to | When you go over |
| --- | --- | --- |
| Requests per minute and concurrency set on a key | Requests made with that key | `429` with `retry-after` |
| Model capacity | All model requests. A short burst first waits a few seconds in a queue. | `429`, usually with `retry-after` |

More in [Rate limits & failover](/docs/models/rate-limits) and [API keys](/docs/account/api-keys#key-options).

## Retries and idempotency {#retries-and-idempotency}

When a channel fails, Half Off Dev tries the request on other channels before it gives up, so a `5xx` that reaches you has already been retried. Still, retry `429` and `5xx` on your side with exponential backoff, and respect `retry-after`. Other `4xx` errors need a changed request, not another attempt. The official Anthropic and OpenAI SDKs retry twice by default; raise `max_retries` (`maxRetries` in Node.js) for long batch jobs.

There are no idempotency keys, which matters in one case: **a request that timed out on your end may have finished and been charged anyway.** Retrying it can mean paying twice. Give long generations a generous client timeout, or stream them. Listing models and counting tokens are always safe to repeat.

Ready-made retry helpers are in [Errors](/docs/api/errors#retry-failed-requests).

## Errors {#errors}

Errors come back in the format of the endpoint you called, so each SDK throws the exception it normally would. For example, an OpenAI-format request with a revoked key gets:

```json
{"error": {"message": "Invalid or revoked API key", "type": "authentication_error", "code": "authentication_error"}}
```

All status codes, with sample bodies in every format, are listed in [Errors](/docs/api/errors).
