OpenAI compatible
Chat completions with the OpenAI SDKs, curl, streaming, tools, JSON output, reasoning and token usage.
Point any OpenAI client at https://ai.helipod.io/v1 and use a Helipod AI key. The request and response follow the OpenAI Chat Completions API.
/v1/chat/completionsRequest
The body is an OpenAI chat completion request. The gateway reads these fields itself and passes the rest to the model unchanged:
Prop
Type
About the output cap:
- No cap sent. The gateway applies a default of 16,384 tokens, or the model's own output limit when that is lower.
- Cap above the model's limit. It is lowered to the model's limit instead of failing the request.
- The cap sets the hold. Before the call, the gateway reserves enough balance to pay for the full cap. A very large cap with a small balance can answer
402even if the real answer would be short. See Pricing and billing.
Other standard fields (temperature, top_p, stop, tools, tool_choice, response_format, reasoning_effort, stream_options, user and so on) are passed to the model. Whether a model honours a given option depends on the model; see the capabilities in Models & pricing in the dashboard.
Examples
curl https://ai.helipod.io/v1/chat/completions \
-H "Authorization: Bearer hel_ai_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3",
"max_completion_tokens": 2000,
"messages": [
{ "role": "system", "content": "You are a concise assistant." },
{ "role": "user", "content": "Explain what a VPS is in two sentences." }
]
}'Response
A standard chat completion:
{
"id": "chatcmpl-8f3c2a1b9d7e4f60a1b2c3d4",
"object": "chat.completion",
"created": 1792130400,
"model": "glm-5.3",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "A VPS is a virtual server with its own operating system and root access...",
"reasoning_content": "The user wants a short definition..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 1250,
"completion_tokens": 310,
"total_tokens": 1560,
"prompt_tokens_details": { "cached_tokens": 1024 },
"completion_tokens_details": { "reasoning_tokens": 240 }
}
}The model field in the body comes from the model's own answer and can be spelled differently from the id you sent. The x-helipod-model response header always carries the catalog id the request was billed as. See Response headers.
Token usage
| Field | Meaning | Billed as |
|---|---|---|
usage.prompt_tokens | The whole prompt, including cached tokens. | Input, minus the cached part |
usage.prompt_tokens_details.cached_tokens | The part of the prompt read from the model's prompt cache. | Cached input (cheaper) |
usage.completion_tokens | Everything the model wrote, including reasoning. | Output |
usage.completion_tokens_details.reasoning_tokens | The reasoning part of completion_tokens, when the model reports it. | Output (already inside completion_tokens) |
So a request costs (prompt_tokens - cached_tokens) x input price + cached_tokens x cached price + completion_tokens x output price, with prices per 1M tokens. Prompt caching is automatic on models that support it: repeat the same long prefix (system prompt, documents, tool definitions) at the start of your messages and later requests read it from the cache.
Streaming
Set "stream": true to receive server-sent events (text/event-stream), each data: line a chat.completion.chunk, ending with data: [DONE].
curl -N https://ai.helipod.io/v1/chat/completions \
-H "Authorization: Bearer hel_ai_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"stream": true,
"stream_options": { "include_usage": true },
"messages": [{ "role": "user", "content": "Write a haiku about Jakarta rain." }]
}'Good to know:
- Usage at the end. The last chunk before
[DONE]carriesusage. Sendstream_options.include_usageto be sure your client keeps it. - Reasoning arrives separately. Reasoning models stream their thinking in
delta.reasoning_contentbefore the answer indelta.content. Show it or hide it, but do not treat it as the answer. - Keep-alive while the model starts. If the model has not started answering after about 10 seconds, the gateway answers
200right away and sends SSE comment lines (: waiting for the model, then: keep-aliveevery 15 seconds) until it does. SSE clients ignore comment lines. - Errors after the
200(while the gateway was waiting for a slow model) arrive as a final event,data: {"error": {...}}followed bydata: [DONE]. A stream that ends withoutdata: [DONE]did not finish. See Limits and errors. - Stopping early. If you close the connection mid-stream, the request is charged for what was generated up to then.
Tools (function calling)
tools, tool_choice and parallel_tool_calls are passed through in the OpenAI format. Use models with the tools capability.
const res = await client.chat.completions.create({
model: "kimi-k3",
messages: [{ role: "user", content: "What is the weather in Bandung?" }],
tools: [
{
type: "function",
function: {
name: "get_weather",
description: "Current weather for a city",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
},
],
});
const call = res.choices[0].message.tool_calls?.[0];
// call.function.name === "get_weather", call.function.arguments === '{"city":"Bandung"}'Run the function yourself, then send the result back as a tool message with the matching tool_call_id, in the same conversation. When a reasoning model made the tool call, send its assistant message back unchanged, including reasoning_content.
JSON output
response_format is passed through: { "type": "json_object" } for any valid JSON object, or { "type": "json_schema", "json_schema": { ... } } for a fixed shape, on models with the structured_output capability. Also say in the prompt that you want JSON.
{
"model": "glm-5.3",
"response_format": { "type": "json_object" },
"messages": [
{ "role": "user", "content": "Return {\"city\": string, \"province\": string} for Bandung as JSON." }
]
}Reasoning
Most models reason before they answer. Reasoning tokens count as output, are billed at the output price, and use up max_completion_tokens. Give reasoning models a generous cap (2,000 tokens or more), or the visible answer can be cut short or empty with finish_reason: "length".
Where a model supports it, reasoning_effort ("low", "medium", "high") is passed through to control how much it thinks. The reasoning text, when the model returns it, is in message.reasoning_content (or delta.reasoning_content when streaming).
Images
Models with the vision capability accept OpenAI image_url content parts, with an https:// URL or a data:image/...;base64,... URL. The request body can be up to 8 MB.
{
"role": "user",
"content": [
{ "type": "text", "text": "What is in this picture?" },
{ "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }
]
}Response headers
| Header | Meaning |
|---|---|
x-request-id | The gateway's id for this request, for example req_6f1d2c9e-.... The same id appears in Usage in the dashboard. Log it and include it when you contact support. |
x-helipod-model | The catalog id the request was routed to and billed as. |
x-helipod-model-sunset | Only for a model retiring within 30 days: the retirement time (ISO 8601). |
x-helipod-model-replacement | With the sunset header, when there is one: the model id to move to. |
In a browser, only x-request-id can be read by your script (it is the only header exposed through CORS). Server-side clients see all of them.