Core

Chat completions

The endpoint the product exists for. Every OpenAI parameter is forwarded verbatim, and anything genuinely unsupported is rejected by name rather than dropped.

POST /v1/chat/completions

The request and response are OpenAI’s. Field names are theirs and are not tidied, because the whole migration story is that an unmodified client works.

Parameters

FieldNotes
modelRequired. A model id or an alias. See Models
messagesRequired. Roles system, user, assistant, tool. content is a string, or an array of parts for images
max_tokensDefaults to 1024, ceiling 32000. The reservation is priced off this
max_completion_tokensOpenAI’s newer spelling for the same cap. Send either one. Sending both with different values is a 400
temperature, top_p, stop, seedForwarded. Ranges are OpenAI’s
frequency_penalty, presence_penaltyForwarded
stream, stream_optionsSee Streaming
tools, tool_choiceSee Tool calling
response_formatSee Structured output
reasoning_effortnone, low, medium, high. See Models
logprobs, top_logprobsForwarded
prompt_cache_keySee Prompt caching

The response

{
  "id": "chatcmpl-8f2a1c",
  "object": "chat.completion",
  "created": 1785097284,
  "model": "llama-3.1-8b",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "The capital of Kenya is Nairobi." },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 21,
    "completion_tokens": 8,
    "total_tokens": 29,
    "prompt_tokens_details": { "cached_tokens": 0 }
  },
  "dawnstack": { "cost_micros": 2, "cost_usd": 0.000002, "request_id": "req_8f2a1c" }
}

dawnstack is additive and non-standard. An OpenAI client ignores unknown fields, so it costs you nothing, and it means you do not have to reconcile a call against your ledger afterwards to know what it cost. cost_micros is the integer of record; cost_usd is the same number in dollars.

finish_reason is passed through from the model. stop means it finished, length means it hit max_tokens, tool_calls means it wants you to call a function, and error means the stream failed after it had started.

Multi-turn

Send the whole conversation. There is no server-side state, no thread id, and nothing to clean up.

messages = [{"role": "system", "content": "Answer in one sentence."}]

while True:
    messages.append({"role": "user", "content": input("> ")})
    reply = client.chat.completions.create(model="llama-3.1-8b", messages=messages)
    text = reply.choices[0].message.content
    messages.append({"role": "assistant", "content": text})
    print(text)

Set prompt_cache_key to the same value across a conversation’s turns and the repeated prefix gets cheaper. See Prompt caching.

Images

Some models accept images. Send content as an array of parts instead of a string, exactly as you would against OpenAI:

{
  "model": "gemma-3-27b",
  "messages": [
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "What is in this picture?" },
        { "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }
      ]
    }
  ]
}

Check input_modalities on GET /v1/models before you send one. A model that reads images publishes ["text", "image"]; everything else publishes ["text"]. Sending an image to a text-only model is a 400 naming messages, before any billable work.

input_modalities is the field to filter on, not modality. modality says which endpoint serves the model and therefore what its price is counted in, so every chat model is "text" there whether or not it reads images.

Images are estimated generously when your balance is reserved, and the reservation is refunded down to the real figure once the supplier reports actual usage. You are billed on that figure, never on the estimate.

What is not supported

n > 1 is not implemented: ask twice. Sending n: 1 is fine and is what we already do. Everything else that is missing is on Not supported yet.