halfoff.devDocs
Get a key
API reference

Models endpoints

Request and response reference for Messages, Chat Completions, Responses, Gemini and model listing on Half Off Dev, with the headers each accepts.

These are the vendors' own endpoints, answered at https://halfoff.dev. Bodies follow each vendor's schema and pass through untouched, so this reference covers the parameters you'll reach for most and links out to the vendor docs for everything else. Behavior shared by all of them, like failover and caching, is explained in How the gateway works.

Any endpoint accepts your key as Authorization: Bearer, x-api-key, x-goog-api-key or a ?key= query parameter. Errors use the format of the endpoint you called; see Errors.

Response headers

x-halfoff-request-idstring

Id of the call, such as req_6f1c0b9e2d4a47a1b8c3d5e7, also listed in Logs. Sent on successful responses and on 429 capacity and 502 errors.

x-halfoff-groupstring

The channel group that answered.

x-halfoff-channelstring

That group's channel label, URL-encoded.

x-halfoff-queued-msinteger

Milliseconds spent waiting for capacity. Absent when there was no wait.

retry-afterinteger

Seconds to wait. Sent with 429 responses from key limits or busy capacity.

List models#

GET/v1/models

Returns every model you can call right now, with its context window, prices and the channel groups that serve it. No key needed. The shape is compatible with models.list() in both the OpenAI and Anthropic SDKs. Prices are what Half Off Dev charges, half the official list price. Field details are in Models & pricing.

Response fields

dataarray

One entry per model.

has_moreboolean

Always false: you get the full list in one response.

curl https://halfoff.dev/v1/models
Response
{
  "object": "list",
  "data": [
    {
      "id": "claude-sonnet-5.5",
      "object": "model",
      "type": "model",
      "display_name": "Claude Sonnet 5.5",
      "created": 1700000000,
      "created_at": "2025-01-01T00:00:00Z",
      "owned_by": "Anthropic",
      "context_length": 1000000,
      "pricing": {
        "input": 1,
        "output": 5,
        "cache_read": 0.05,
        "cache_write": 1.25,
        "unit": "USD per 1M tokens"
      },
      "groups": ["claude-value"]
    }
  ],
  "has_more": false,
  "first_id": "claude-fable-5.1",
  "last_id": "gpt-5.6-luna"
}

Create a message#

POST/v1/messages

The Anthropic Messages API. Set stream for server-sent events. Extended thinking, tool use and prompt caching work exactly as Anthropic documents them. Guide: Anthropic Messages.

Headers

x-api-keystringRequired

Your Half Off Dev key. Authorization: Bearer is accepted too.

anthropic-versionstringDefault 2023-06-01

Passed through to the model.

anthropic-betastring

Passed through unchanged.

x-halfoff-groupstring

Channel group for this call, for example claude-value. Ignored if the key is bound to a group.

x-session-idstring

A stable conversation id, so every turn goes to the channel that holds its cache.

Body parameters

modelstringRequired

A model id from GET /v1/models, for example claude-sonnet-5.5. Variants like claude-sonnet-5-5 resolve to the same model.

messagesarrayRequired

The conversation so far, alternating user and assistant turns.

max_tokensintegerRequired

Most tokens the model may generate.

systemstring or array

System prompt. Text blocks accept cache_control.

streambooleanDefault false

Stream the answer as server-sent events.

thinkingobject

Extended thinking settings.

output_configobject

Output settings such as effort and format.

toolsarray

Tools the model may call.

tool_choiceobject

How the model picks a tool.

metadataobject

Request metadata.

curl https://halfoff.dev/v1/messages \
  -H "x-api-key: $HALFOFF_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-5.5",
    "max_tokens": 512,
    "messages": [{"role": "user", "content": "Suggest a name for a budget tracking app."}]
  }'
Response
{
  "id": "msg_01XFDUDYJgAACzvnptvVoYEL",
  "type": "message",
  "role": "assistant",
  "model": "claude-sonnet-5-5",
  "content": [
    {"type": "text", "text": "How about \"Pennywise\"?"}
  ],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {
    "input_tokens": 16,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0,
    "output_tokens": 9
  }
}

Count message tokens#

POST/v1/messages/count_tokens

Estimates how many input tokens a Messages request would use, without running it. Free, and it works even with a zero balance. The number is approximate: good for context-window checks and budgeting, while the actual charge comes from a real call's usage. See Anthropic's token counting guide.

Headers

x-api-keystringRequired

Your Half Off Dev key. Authorization: Bearer is accepted too.

x-halfoff-groupstring

Channel group to check the model against. Ignored if the key is bound to a group.

Body parameters

modelstringRequired

Model id, same as for POST /v1/messages.

messagesarrayRequired

Messages to count.

systemstring or array

System prompt to include.

toolsarray

Tool definitions to include.

curl https://halfoff.dev/v1/messages/count_tokens \
  -H "x-api-key: $HALFOFF_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model": "claude-sonnet-5.5",
       "messages": [{"role": "user", "content": "How long is this prompt?"}]}'
Response
{
  "input_tokens": 15
}

Create a chat completion#

POST/v1/chat/completions

The OpenAI Chat Completions API. Every model in GET /v1/models answers in this format, Claude and Gemini included. Streaming requests always get stream_options.include_usage: true, so the final chunk carries usage. Guide: OpenAI Chat Completions.

Headers

AuthorizationstringRequired

Bearer and your Half Off Dev key.

openai-betastring

Passed through unchanged.

x-halfoff-groupstring

Channel group for this call, for example gpt-value. Ignored if the key is bound to a group.

x-session-idstring

A stable conversation id, so every turn goes to the channel that holds its cache.

Body parameters

modelstringRequired

A model id from GET /v1/models, for example gpt-6.1-sol.

messagesarrayRequired

The conversation so far: system or developer, user, assistant and tool messages.

streambooleanDefault false

Stream server-sent events, ending with data: [DONE].

max_completion_tokensinteger

Cap on generated tokens, reasoning tokens included.

reasoning_effortstring

How hard a reasoning model thinks. Accepted values depend on the model.

toolsarray

Functions the model may call.

tool_choicestring or object

How the model picks a function.

response_formatobject

Ask for JSON output or a JSON Schema.

prompt_cache_keystring

Raises cache hit rates; also used as the conversation id for routing.

userstring

Your end user's id. Used as the conversation id when nothing more specific is sent.

curl https://halfoff.dev/v1/chat/completions \
  -H "Authorization: Bearer $HALFOFF_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "gpt-6.1-sol",
    "messages": [{"role": "user", "content": "Translate \"good morning\" into French."}]
  }'
Response
{
  "id": "chatcmpl-B9MHDbslfkBeAs8l4bebGdFOJ6PeG",
  "object": "chat.completion",
  "created": 1791700000,
  "model": "gpt-6.1-sol",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "Bonjour."},
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 15,
    "completion_tokens": 3,
    "total_tokens": 18,
    "prompt_tokens_details": {"cached_tokens": 0},
    "completion_tokens_details": {"reasoning_tokens": 0}
  }
}

Create a response#

POST/v1/responses

The OpenAI Responses API, which Codex CLI uses. GPT models only; any other model returns 404. Nothing is stored between calls, so send the full conversation in input each time. Guide: OpenAI Chat Completions.

Headers

AuthorizationstringRequired

Bearer and your Half Off Dev key.

openai-betastring

Passed through unchanged.

x-halfoff-groupstring

Channel group for this call, for example gpt-value. Ignored if the key is bound to a group.

x-session-idstring

A stable conversation id, so every turn goes to the channel that holds its cache.

Body parameters

modelstringRequired

A GPT model id from GET /v1/models, for example gpt-6.1-sol.

inputstring or arrayRequired

A prompt, or the conversation as a list of input items.

instructionsstring

System instructions. The response echoes them back, or null.

streambooleanDefault false

Stream typed server-sent events, ending with response.completed, which carries usage.

max_output_tokensinteger

Cap on generated tokens, reasoning tokens included.

reasoningobject

Reasoning settings such as effort. Accepted values depend on the model.

toolsarray

Functions and other tools the model may call.

storeboolean

Send false: responses aren't kept for later retrieval.

prompt_cache_keystring

Raises cache hit rates; also used as the conversation id for routing.

curl https://halfoff.dev/v1/responses \
  -H "Authorization: Bearer $HALFOFF_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "gpt-6.1-sol",
    "input": "Give me a one-line git tip.",
    "store": false
  }'

Generate content#

POST/v1beta/models/{model}:generateContent

The Gemini API. Guide: Gemini.

Path parameters

modelstringRequired

Model id, for example gemini-3.1-pro. Google's spellings such as gemini-3.1-pro-preview resolve to the same model.

Headers

x-goog-api-keystringRequired

Your Half Off Dev key. Authorization: Bearer or a ?key= query parameter also work.

x-halfoff-groupstring

Channel group for this call, for example gemini-value. Ignored if the key is bound to a group.

x-session-idstring

A stable conversation id, so every turn goes to the channel that holds its cache.

Body parameters

contentsarrayRequired

The conversation so far, as user and model turns built from parts.

systemInstructionobject

System instruction, as a content object with text parts.

generationConfigobject

Output settings: maxOutputTokens, temperature, responseMimeType, thinkingConfig and more.

toolsarray

Function declarations and other tools.

toolConfigobject

How the model picks functions.

safetySettingsarray

Safety thresholds per category.

curl "https://halfoff.dev/v1beta/models/gemini-3.1-pro:generateContent" \
  -H "x-goog-api-key: $HALFOFF_API_KEY" \
  -H "content-type: application/json" \
  -d '{"contents": [{"role": "user", "parts": [{"text": "What is a haiku?"}]}]}'
Response
{
  "candidates": [
    {
      "content": {"role": "model", "parts": [{"text": "A haiku is a three-line Japanese poem of 5, 7 and 5 syllables."}]},
      "finishReason": "STOP",
      "index": 0
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 5,
    "candidatesTokenCount": 18,
    "thoughtsTokenCount": 84,
    "totalTokenCount": 107
  },
  "responseId": "mFHoaNC1Lo3HqtsP"
}

Stream generated content#

POST/v1beta/models/{model}:streamGenerateContent

The same request as generateContent, delivered as a stream. Add alt=sse for server-sent events, one partial response per event; the SDKs add it for you. Without it you get a single JSON array streamed piece by piece. See Streaming.

Query parameters

altstring

sse for server-sent events. Recommended.

Path parameter, headers and body are identical to Generate content.

curl -N "https://halfoff.dev/v1beta/models/gemini-3-flash:streamGenerateContent?alt=sse" \
  -H "x-goog-api-key: $HALFOFF_API_KEY" \
  -H "content-type: application/json" \
  -d '{"contents": [{"role": "user", "parts": [{"text": "List three primary colors."}]}]}'
Response
data: {"candidates":[{"content":{"role":"model","parts":[{"text":"Red, yellow"}]}}],"usageMetadata":{"promptTokenCount":6,"candidatesTokenCount":3,"totalTokenCount":9}}

data: {"candidates":[{"content":{"role":"model","parts":[{"text":" and blue."}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":6,"candidatesTokenCount":6,"totalTokenCount":12}}

Count Gemini tokens#

POST/v1beta/models/{model}:countTokens

Estimates the tokens in contents without running the model. Free, and it works even with a zero balance. The number is approximate. Gemini reference.

Body parameters

contentsarrayRequired

Content to count, shaped as for generateContent.

Path parameter and headers are identical to Generate content.

curl "https://halfoff.dev/v1beta/models/gemini-3.1-pro:countTokens" \
  -H "x-goog-api-key: $HALFOFF_API_KEY" \
  -H "content-type: application/json" \
  -d '{"contents": [{"role": "user", "parts": [{"text": "How long is this prompt?"}]}]}'
Response
{
  "totalTokens": 7
}

List models in Gemini format#

GET/v1beta/models

Lists the models served through the Gemini endpoints, shaped the way the Gemini API shapes them. No key needed. The Google Gen AI SDKs call it from models.list(). Prices appear only in GET /v1/models.

cURL
curl https://halfoff.dev/v1beta/models
Response
{
  "models": [
    {
      "name": "models/gemini-3.1-pro",
      "displayName": "Gemini 3.1 Pro",
      "inputTokenLimit": 1048576,
      "supportedGenerationMethods": ["generateContent", "streamGenerateContent", "countTokens"]
    }
  ]
}