Inference
Chat Completions
Generate model responses with the OpenAI-compatible Chat Completions API, including streaming and tool calls.
Endpoint
Requires an API key with the inference scope. The body must be JSON with Content-Type: application/json and at most 2 MB.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| model | string | Required | Model ID from GET /v1/models. 1–128 characters. |
| messages | array | Required | The conversation, 1–500 messages. See Messages. |
| max_completion_tokens | integer | Optional | Maximum output tokens, 1–128,000. Reduced to the model's max_output_tokens. Defaults to 4,096 or the model cap, whichever is lower. This value determines the balance reservation. |
| max_tokens | integer | Optional | Legacy alias. Ignored when max_completion_tokens is present. |
| temperature | number | Optional | 0–2. Forwarded only to models that support sampling parameters; otherwise ignored. |
| top_p | number | Optional | 0–1. Forwarded only to models that support sampling parameters. Currently not forwarded to Anthropic models. |
| stop | string | string[] | Optional | Up to 4 stop sequences. |
| stream | boolean | Optional | Stream the response as Server-Sent Events. Default false. |
| stream_options | object | Optional | Accepts include_usage. Usage is always included in the final stream chunk regardless of this value. |
| tools | array | Optional | Up to 32 function tools. See Tools. |
| tool_choice | string | Optional | auto or none. none removes the tools from the upstream request. Forcing a specific function is not supported. |
| n | integer | Optional | Must be 1 if provided. |
| user | string | Optional | Up to 256 characters. Accepted for compatibility; not forwarded upstream. |
Fields not listed, such as response_format, seed or logprobs, are ignored. Validation failures return invalid_request with param set to the path of the offending field, for example messages.0.content.
Messages
| Role | Fields | Notes |
|---|---|---|
| system | content | Instructions for the model. |
| developer | content | Treated as system. |
| user | content, name? | name is accepted but not forwarded. |
| assistant | content?, tool_calls? | content may be null when tool_calls is present. Up to 32 tool calls. |
| tool | content, tool_call_id | The result of a tool call; tool_call_id matches the assistant's call id. |
content is a string or an array of { "type": "text", "text": "…" } parts, which are concatenated without separators.
Tools
Tools are supported only by models with the tools capability. Sending tools to any other model returns invalid_request with param: "tools".
function.namemust match^[a-zA-Z0-9_-]{1,64}$.function.descriptionis optional, up to 2,000 characters.function.parametersis a JSON Schema object; it defaults to an empty object schema.- Tool call arguments in assistant messages may be up to 100,000 characters.
{
"model": "claude-sonnet",
"messages": [
{ "role": "user", "content": "What is 2^61 - 1? Use the calculator." }
],
"tools": [
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate an integer arithmetic expression.",
"parameters": {
"type": "object",
"properties": { "expression": { "type": "string" } },
"required": ["expression"]
}
}
}
]
}When the model calls a tool, the response has finish_reason: "tool_calls" and message.tool_calls. Run the tool yourself, then send its result back:
{
"model": "claude-sonnet",
"messages": [
{ "role": "user", "content": "What is 2^61 - 1? Use the calculator." },
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "toolu_01",
"type": "function",
"function": { "name": "calculate", "arguments": "{\"expression\":\"2**61 - 1\"}" }
}
]
},
{ "role": "tool", "tool_call_id": "toolu_01", "content": "2305843009213693951" }
],
"tools": [ ... ]
}Each round trip is a separate billed request with its own receipt. The gateway does not execute your tools; for hosted code execution see Sandboxes and Agents.
Example
curl https://www.hushcompute.xyz/v1/chat/completions \
-H "Authorization: Bearer $HUSH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet",
"max_completion_tokens": 1024,
"messages": [
{ "role": "system", "content": "Answer in one paragraph." },
{ "role": "user", "content": "Why do reservations make prepaid billing safe?" }
]
}'Response
{
"id": "req_01K58ZQ4W5V8E2R7PKD3N6A0TG",
"object": "chat.completion",
"created": 1789387200,
"model": "claude-sonnet",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "A reservation sets aside ..." },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 480, "completion_tokens": 310, "total_tokens": 790 },
"receipt": {
"id": "rcpt_01K58ZQ4W6N3T2B9XRC7D1M5HJ",
"hash": "0x6f0d…a3c9",
"signature": "0x9b1f…c07e1b",
"signer": "0x3A4c…91De",
"url": "https://www.hushcompute.xyz/proofs/rcpt_01K58ZQ4W6N3T2B9XRC7D1M5HJ"
},
"billing": {
"request_id": "req_01K58ZQ4W5V8E2R7PKD3N6A0TG",
"cost_micro_usd": "4466",
"cost_usd": "0.004466",
"usage_estimated": false,
"fingerprint_salt": "0x51c2…7f08"
}
}| Field | Type | Description |
|---|---|---|
| id | string | The request ID (req_…). Also sent as x-request-id. |
| object | string | chat.completion |
| created | integer | Unix time in seconds. |
| model | string | The gateway model ID that served the request. |
| choices[0].message | object | role assistant; content (null when the model only called tools); tool_calls when present. |
| choices[0].finish_reason | string | stop, length (max_completion_tokens reached), tool_calls, or content_filter. |
| usage | object | prompt_tokens, completion_tokens, total_tokens as billed. |
| receipt | object | id, hash (receipt hash), signature, signer (address), url (public proof page). Also sent as x-receipt-id. |
| billing.cost_micro_usd | string | Exact amount charged, in integer µUSD. |
| billing.cost_usd | string | The same amount formatted as decimal USD, for display. |
| billing.usage_estimated | boolean | true when the provider did not report token usage and the gateway estimated it. |
| billing.fingerprint_salt | string | 32-byte salt for the receipt's input and output fingerprints. Returned only here. |
| billing.request_id | string | The request ID. |
Streaming
With stream: true the response is text/event-stream. Each event is a data: line with a chat.completion.chunk object:
- A first chunk with
delta: { "role": "assistant", "content": "" }. - Content chunks with
delta.content, or a tool-call chunk withdelta.tool_calls. - A chunk with an empty
deltaand thefinish_reason. - A final chunk with
"choices": []plususage,receipt,billingandlatency_ms. - The terminator
data: [DONE].
data: {"id":"req_01K5…","object":"chat.completion.chunk","created":1789387200,"model":"claude-sonnet","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"req_01K5…","object":"chat.completion.chunk","created":1789387200,"model":"claude-sonnet","choices":[{"index":0,"delta":{"content":"A reservation"},"finish_reason":null}]}
data: {"id":"req_01K5…","object":"chat.completion.chunk","created":1789387200,"model":"claude-sonnet","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"req_01K5…","object":"chat.completion.chunk","created":1789387200,"model":"claude-sonnet","choices":[],"usage":{"prompt_tokens":480,"completion_tokens":310,"total_tokens":790},"receipt":{"id":"rcpt_01K5…","hash":"0x6f0d…","signature":"0x9b1f…","signer":"0x3A4c…","url":"https://www.hushcompute.xyz/proofs/rcpt_01K5…"},"billing":{"request_id":"req_01K5…","cost_micro_usd":"4466","cost_usd":"0.004466","usage_estimated":false,"fingerprint_salt":"0x51c2…"},"latency_ms":2130}
data: [DONE]Tool calls are delivered in a single chunk containing complete arguments for every call, each with an index, rather than as incremental fragments.
const stream = await client.chat.completions.create({
model: "claude-sonnet",
messages: [{ role: "user", content: "Write a haiku about ledgers." }],
stream: true,
});
for await (const chunk of stream) {
const text = chunk.choices[0]?.delta?.content;
if (text) process.stdout.write(text);
// The final chunk has no choices and carries usage, receipt and billing.
if (chunk.choices.length === 0) {
const final = chunk as typeof chunk & {
receipt: { id: string; url: string };
billing: { cost_micro_usd: string; fingerprint_salt: string };
latency_ms: number;
};
console.log("\n", final.usage, final.billing.cost_micro_usd, final.receipt.url);
}
}The x-request-id and x-receipt-id headers are sent when the stream opens, before the outcome is known.
Errors during a stream
Authentication, validation, policy, rate-limit, budget and balance checks all run before the stream opens, so those failures arrive as a normal JSON error with an HTTP error status. Once HTTP 200 has been sent, a failure is reported in-band as a data: event with an error object, followed by data: [DONE]:
- Output already delivered. The delivered portion is metered (estimated if the provider reported no usage) and receipted with status
failed. The error event includesreceiptandbillingfor that portion. - Nothing delivered. The reservation is released in full, nothing is charged, and no receipt is issued. The receipt ID in the
x-receipt-idheader will not resolve.
data: {"error":{"type":"provider_error","message":"The upstream provider could not be reached.","request_id":"req_01K5…"},"receipt":{"id":"rcpt_01K5…","hash":"0x…","signature":"0x…","signer":"0x…","url":"https://www.hushcompute.xyz/proofs/rcpt_01K5…"},"billing":{"request_id":"req_01K5…","cost_micro_usd":"913","cost_usd":"0.000913","usage_estimated":true,"fingerprint_salt":"0x…"}}
data: [DONE]If the client disconnects mid-stream, the gateway cancels the upstream request and applies the same rules.
Billing behaviour
- Before calling the model, the gateway reserves a conservative estimate of the input cost plus
max_completion_tokensat the output price. After completion it charges the actual cost and releases the remainder. See Usage. - A non-streaming request that fails upstream is not charged: the reservation is released and no receipt is issued.
- When a provider omits token usage,
usage_estimatedistrueand the gateway bills an estimate: half its conservative input estimate, and one output token per four UTF-8 bytes of generated content.
Compatibility notes
- The response
idis the gateway request ID, not an upstream completion ID. - Only one choice is returned; n must be 1.
tool_choicesupportsautoandnoneonly;requiredor a named function returns 400.- Streaming always ends with a usage chunk followed by
[DONE]. Clients that ignore chunks with emptychoicesare unaffected.