API reference
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.
Every request and response shape, with samples in cURL, Python and Node.js.
Status codes, error bodies in each format, and a retry recipe.
Where to put the key for each SDK.
Your first half-price request.
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#
A bearer token works on every endpoint. Each vendor's own header works too, so every SDK can keep its defaults.
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.
Request and response format#
- JSON both ways. Send bodies as JSON with
content-type: application/json; responses are JSON too. - Streaming. Anthropic and OpenAI requests with
"stream": trueanswer with server-sent events (text/event-stream). Gemini's:streamGenerateContentreturns a streamed JSON array, or server-sent events when you add?alt=sse, exactly as Google's API does. See 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#
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. 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#
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#
| 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 and API keys.
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.
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:
{"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.