Inference

Chat Completions

Generate model responses with the OpenAI-compatible Chat Completions API, including streaming and tool calls.

Endpoint

POST/v1/chat/completions

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

ParameterTypeRequiredDescription
modelstringRequiredModel ID from GET /v1/models. 1–128 characters.
messagesarrayRequiredThe conversation, 1–500 messages. See Messages.
max_completion_tokensintegerOptionalMaximum 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_tokensintegerOptionalLegacy alias. Ignored when max_completion_tokens is present.
temperaturenumberOptional0–2. Forwarded only to models that support sampling parameters; otherwise ignored.
top_pnumberOptional0–1. Forwarded only to models that support sampling parameters. Currently not forwarded to Anthropic models.
stopstring | string[]OptionalUp to 4 stop sequences.
streambooleanOptionalStream the response as Server-Sent Events. Default false.
stream_optionsobjectOptionalAccepts include_usage. Usage is always included in the final stream chunk regardless of this value.
toolsarrayOptionalUp to 32 function tools. See Tools.
tool_choicestringOptionalauto or none. none removes the tools from the upstream request. Forcing a specific function is not supported.
nintegerOptionalMust be 1 if provided.
userstringOptionalUp 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

RoleFieldsNotes
systemcontentInstructions for the model.
developercontentTreated as system.
usercontent, name?name is accepted but not forwarded.
assistantcontent?, tool_calls?content may be null when tool_calls is present. Up to 32 tool calls.
toolcontent, tool_call_idThe 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.name must match ^[a-zA-Z0-9_-]{1,64}$.
  • function.description is optional, up to 2,000 characters.
  • function.parameters is a JSON Schema object; it defaults to an empty object schema.
  • Tool call arguments in assistant messages may be up to 100,000 characters.
Request with a tool
{
  "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:

Follow-up with the tool result
{
  "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

200 OK
{
  "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"
  }
}
FieldTypeDescription
idstringThe request ID (req_…). Also sent as x-request-id.
objectstringchat.completion
createdintegerUnix time in seconds.
modelstringThe gateway model ID that served the request.
choices[0].messageobjectrole assistant; content (null when the model only called tools); tool_calls when present.
choices[0].finish_reasonstringstop, length (max_completion_tokens reached), tool_calls, or content_filter.
usageobjectprompt_tokens, completion_tokens, total_tokens as billed.
receiptobjectid, hash (receipt hash), signature, signer (address), url (public proof page). Also sent as x-receipt-id.
billing.cost_micro_usdstringExact amount charged, in integer µUSD.
billing.cost_usdstringThe same amount formatted as decimal USD, for display.
billing.usage_estimatedbooleantrue when the provider did not report token usage and the gateway estimated it.
billing.fingerprint_saltstring32-byte salt for the receipt's input and output fingerprints. Returned only here.
billing.request_idstringThe request ID.

Streaming

With stream: true the response is text/event-stream. Each event is a data: line with a chat.completion.chunk object:

  1. A first chunk with delta: { "role": "assistant", "content": "" }.
  2. Content chunks with delta.content, or a tool-call chunk with delta.tool_calls.
  3. A chunk with an empty delta and the finish_reason.
  4. A final chunk with "choices": [] plus usage, receipt, billing and latency_ms.
  5. The terminator data: [DONE].
Wire format
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 includes receipt and billing for 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-id header will not resolve.
Error after partial output
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_tokens at 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_estimated is true and 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 id is the gateway request ID, not an upstream completion ID.
  • Only one choice is returned; n must be 1.
  • tool_choice supports auto and none only; required or a named function returns 400.
  • Streaming always ends with a usage chunk followed by [DONE]. Clients that ignore chunks with empty choices are unaffected.