halfoff.devDocs
Get a key
Models API

How the gateway works

One host serves the Anthropic, OpenAI and Gemini APIs natively. Endpoints, base URLs, accepted model ids, auth headers and what Half Off Dev touches.

The point of Half Off Dev is that you keep the code you already have. Claude, GPT and Gemini are each served in their own vendor's wire format, on a single host, at half the list price. Your SDK sends the same JSON it would send to the vendor; only the base URL and the key change.

There is no translation layer in the middle. An Anthropic-style request is handed to a channel that speaks Anthropic, an OpenAI-style request to one that speaks OpenAI, and so on. Apart from two narrow edits, the body arrives as you wrote it, so vendor features like thinking, cache_control, tools or generationConfig behave exactly as each vendor's own reference describes.

Endpoints at a glance#

Give your SDK the base URL from this table and let it build the path.

Wire formatPathsBase URL for the SDKGuide
Anthropic MessagesPOST /v1/messages, POST /v1/messages/count_tokenshttps://halfoff.devAnthropic Messages
OpenAI Chat CompletionsPOST /v1/chat/completionshttps://halfoff.dev/v1OpenAI
OpenAI Responses (GPT only)POST /v1/responseshttps://halfoff.dev/v1Responses API
GeminiPOST /v1beta/models/{model}:generateContent, :streamGenerateContent, :countTokenshttps://halfoff.devGemini
Model catalogGET /v1/models, GET /v1beta/modelsPricing

Anything outside this list is unsupported; see what you can't call.

Sending your key#

Each endpoint looks for the key in four places, so whatever an SDK does by default just works:

LocationTypical sender
Authorization: Bearer ho-...OpenAI SDKs, Claude Code via ANTHROPIC_AUTH_TOKEN, plain HTTP clients
x-api-key: ho-...Anthropic SDKs
x-goog-api-key: ho-...Google Gen AI SDKs, Gemini CLI
?key=ho-...Gemini REST snippets

The two catalog endpoints are public and need no key. Creating, limiting and rotating keys is covered in Authentication.

Native format per model family#

Each family was designed around one API. Calling it there gives you every feature the vendor ships.

FamilyNative formatAlso accepted today
Claude (claude-*)Anthropic MessagesOpenAI Chat Completions, Gemini
GPT (gpt-*)OpenAI Chat Completions, OpenAI ResponsesAnthropic Messages, Gemini
Gemini (gemini-*)GeminiOpenAI Chat Completions, Anthropic Messages

Using a non-native format means the channel converts your request for the model, and vendor-only options may be dropped or refused on the way. Anthropic's cache_control and thinking, OpenAI's reasoning_effort and Gemini's thinkingConfig are only guaranteed in the model's home format.

Which formats a model accepts follows from the channels serving it, so the list can move. The model marketplace shows the current formats per model. When no channel takes your format, you get 404 with a message such as Model 'gpt-6.1-sol' is not served in gemini format in any group.

Model ids#

The canonical ids are the ones in GET /v1/models, for example claude-opus-5.5, gpt-6.1-sol or gemini-3-flash. Tools that spell ids their own way still land on the right model, because the gateway normalizes what it receives:

SentTreated as
Claude-Opus-5-5claude-opus-5.5
anthropic/claude-opus-5.5claude-opus-5.5
claude-haiku-4-5-20251001claude-haiku-4.5
gemini-3.1-pro-previewgemini-3.1-pro
models/gemini-3-flashgemini-3-flash

Normalization lower-cases the id, strips a leading vendor/ or models/, treats . and - as the same character, and removes a trailing date (-20251001), -preview or -latest. Billing, logs and key allow-lists always record the canonical id.

An id that maps to nothing returns 404: Model '<id>' is not available. See GET /v1/models.

The path of one request#

  1. Key check. An inactive or expired key gets 401.
  2. Model lookup. The id is normalized as above and compared with the key's model allow-list; a model outside it gets 403.
  3. Balance check. A balance at or below zero, or a key that has used up its spend limit, gets 402.
  4. Key limits. Per-key requests-per-minute and concurrency caps apply (429). Details in Rate limits & failover.
  5. Channel choice. A conversation that is already under way returns to the channel holding its prompt cache; a new one goes to the healthiest channel in its channel group. See Prompt caching.
  6. Model call. A channel that fails before any output is swapped for another, up to three attempts in total. Streams are held back until the first token so the swap is invisible. See Streaming.
  7. Metering. The token counts in the response decide the charge, at half the list price, and it leaves your balance within seconds. See Pricing.

What the gateway changes#

Two edits are made to the body, and nothing else:

  1. model. The gateway writes in the spelling the serving channel expects. As a result, the model field you get back can use the vendor's form, such as claude-opus-5-5.
  2. Usage in OpenAI streams. Every streaming Chat Completions request gets stream_options.include_usage: true so the call can be billed. Your stream therefore ends with one extra chunk whose choices array is empty and which carries usage. Make sure your loop tolerates an empty choices.

Headers you send are dropped, with three exceptions that are forwarded: anthropic-version, anthropic-beta and openai-beta. If anthropic-version is absent, 2023-06-01 is filled in. On Gemini paths, query parameters such as alt=sse go through, while key is stripped.

Response bodies are returned byte for byte. content-type and cache-control are kept and the gateway adds its own headers; vendor request ids and vendor rate-limit headers are not passed on.

If the model refuses the request as invalid (400, 404, 413, 422), you receive that status and reason in the error format of the API you called. Any other failure is first retried on another channel. More in Rate limits & failover and Errors.

Headers on every response#

HeaderContents
x-halfoff-request-idThis call's id, like req_6f1c.... It is the key to the entry in Logs; quote it when you write to support.
x-halfoff-groupThe channel group that answered, such as gpt-value.
x-halfoff-channelThat group's channel label, URL-encoded, such as Pooled%20capacity.
x-halfoff-queued-msPresent only if the call waited for capacity: the wait in milliseconds.

The first three are listed in access-control-expose-headers, so client-side JavaScript can read them.

What you can't call#

The endpoints in the table above are the whole surface for models. In particular, these are not served:

  • OpenAI: stored responses (/v1/responses/{id}), legacy Completions, embeddings, audio, files and batches. The Responses API itself works for GPT models only.
  • Anthropic: Message Batches, Files, and fetching a single model by id.
  • Gemini: cachedContents (explicit context caching), file uploads, embedContent and the Live API.

To wire up a specific client, see Integrations.