halfoff.devDocs
Get a key
Models API

Gemini

Call Gemini at half price through its native API with the Google Gen AI SDK or Gemini CLI: generateContent, SSE streaming, thinking and countTokens.

Half Off Dev exposes Gemini's own generateContent, streamGenerateContent and countTokens methods under /v1beta/models/{model}. Give the Google Gen AI SDK the base URL https://halfoff.dev and your Half Off Dev key as its API key, and existing Gemini code keeps working at half the list price. Bodies are forwarded unchanged, so Google's API reference describes every field.

Configure the SDK#

The SDKs authenticate with x-goog-api-key. A ?key= query parameter is accepted too.

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": "Summarize what a CDN does for a small blog."}]}]
  }'

Install with pip install google-genai or npm install @google/genai. Model ids come from GET /v1/models, and Google's own spellings such as gemini-3.1-pro-preview resolve to the same model. For a list in Gemini's response shape, call GET /v1beta/models.

Stream with server-sent events#

Request :streamGenerateContent?alt=sse to receive SSE. Every event is a partial GenerateContentResponse, and the final one holds the complete usageMetadata. The SDKs append alt=sse on their own.

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": "Write a four-line poem about compilers."}]}]}'

Leave out alt=sse and the method streams a single JSON array instead. It works, but SSE is simpler to parse incrementally and is the only mode in which the gateway can deliver a proper error event if the stream stalls. See Streaming.

System instructions and thinking#

systemInstruction, generationConfig, safetySettings and function declarations in tools go through exactly as Google documents them. Gemini 3 models think by default; tune that with thinkingConfig following Google's thinking guide.

response = client.models.generate_content(
    model="gemini-3.1-pro",
    contents="Suggest a weekly study plan for learning Rust in two months.",
    config=types.GenerateContentConfig(
        system_instruction="You are a practical programming coach. Use bullet points.",
        max_output_tokens=4096,
    ),
)
print(response.text)
print(response.usage_metadata)

Thinking shows up as usageMetadata.thoughtsTokenCount and is billed at the output rate along with candidatesTokenCount. As in Google's API, thought summaries come back as parts marked "thought": true only if you set generationConfig.thinkingConfig.includeThoughts: true; otherwise the response contains no thought parts.

Count tokens#

:countTokens takes the same contents you would generate with and returns totalTokens. It is free and runs on a zero balance. Treat the number as approximate: good for checking against the context window, not for predicting the charge to the cent.

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": "Measure this sentence."}]}]}'

Gemini CLI#

The Gemini CLI takes its endpoint and key from environment variables:

Shell
export GOOGLE_GEMINI_BASE_URL=https://halfoff.dev
export GEMINI_API_KEY="$HALFOFF_API_KEY"
gemini -m gemini-3.1-pro

The full walkthrough is in Gemini CLI.

Caching#

Gemini caches repeated prompt prefixes automatically (implicit caching). Cached tokens are reported in usageMetadata.cachedContentTokenCount and billed at the cache-read rate. Each conversation is routed back to the same channel so that cache stays warm; see Prompt caching.

Not supported#

These Gemini API features return 404: cachedContents (explicit context caching), file uploads, embedContent and batchEmbedContents, batch mode, and the Live API.

Request headers#

HeaderBehavior
x-goog-api-keyYour Half Off Dev key. Authorization: Bearer and ?key= work as well.
x-halfoff-groupOptional. Picks the channel group for this request, unless the key is already bound to one.
x-session-idOptional. Marks requests as one conversation for cache-aware routing.

Errors use Gemini's shape, {"error": {"code": 429, "message": "...", "status": "RESOURCE_EXHAUSTED"}}; see Errors.