anoman
Sign InGet API Key
Documentation home

Chat completions.

POST · /v1/chat/completions — full OpenAI-compatible chat endpoint. Streaming, vision, tool calling, batching, and the Anoman response annotations all on a single route.

Quick example

Make your first request

from openai import OpenAI
 
client = OpenAI(base_url="https://api.anoman.io/v1", api_key="anm-sk-...")
 
response = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "What is the capital of Indonesia?"},
    ],
    temperature=0.7,
    max_tokens=200,
)
print(response.choices[0].message.content)
import OpenAI from "openai";
 
const client = new OpenAI({
  baseURL: "https://api.anoman.io/v1",
  apiKey: process.env.ANOMAN_API_KEY,
});
 
const response = await client.chat.completions.create({
  model: "claude-sonnet-4-6",
  messages: [
    { role: "system", content: "You are a helpful assistant." },
    { role: "user", content: "What is the capital of Indonesia?" },
  ],
  temperature: 0.7,
  max_tokens: 200,
});
console.log(response.choices[0].message.content);
curl https://api.anoman.io/v1/chat/completions \
  -H "Authorization: Bearer anm-sk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-6",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "What is the capital of Indonesia?"}
    ],
    "temperature": 0.7,
    "max_tokens": 200
  }'

Identical request shape to the OpenAI /v1/chat/completions. The only difference is the base URL.

Request body

Parameters

FieldTypeReq?Description
modelstring✓Model slug from our catalog. See GET /v1/models.
messagesarray✓Conversation history. Each item has role (system / user / assistant / tool) and content (string or array for vision).
max_tokensinteger—Cap on completion tokens. Default = the model context budget.
temperaturenumber—Sampling temperature (0–2). Default 1.0. Set 0 for deterministic-ish output.
top_pnumber—Nucleus sampling (0–1). Use with or instead of temperature.
streamboolean—Stream tokens via SSE. See streaming guide.
toolsarray—Function definitions the model may call. Same shape as OpenAI.
tool_choicestring / object—"auto" (default), "none", or a specific function.
response_formatobject—{"type": "json_object"} for JSON mode.
seedinteger—Deterministic sampling seed. Not all upstream providers honor it.
stopstring / array—Up to 4 stop sequences.
presence_penaltynumber—−2 to 2. Encourages new topics.
frequency_penaltynumber—−2 to 2. Discourages repetition.
userstring—Opaque end-user identifier — surfaces in traces for per-user attribution.
metadataobject—Free-form key/value tags (max 16 keys) attached to the trace.

Anoman request headers

Optional headers

HeaderValuesEffect
x-anoman-realtime1 / trueForce realtime even if the customer key prefers batch.
x-anoman-prefer-batch1 / trueOpt this request into batch routing (cheaper, longer SLA).
x-anoman-no-cache1 / true / yesSkip the response cache for this request.
anoman-session-idstringGroup requests into an agent session — surfaces in the dashboard Sessions view.
anoman-agent-idstringIdentifier for the agent making the call (e.g. support-bot-v2).

200 response

Response body

Identical to the OpenAI response shape, plus an _anoman extension with our guardrail results, routing decisions, cache status, and cost accounting.

{
  "id": "chatcmpl-9k4LpQ8mZx7TbF2Vn",
  "object": "chat.completion",
  "created": 1740000123,
  "model": "claude-sonnet-4-6",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "The capital of Indonesia is Jakarta."
      },
      "finish_reason": "stop",
      "logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 24,
    "completion_tokens": 9,
    "total_tokens": 33,
    "prompt_tokens_details": {
      "cached_tokens": 0
    }
  },
  "_anoman": {
    "guardrails": {
      "injection":          { "status": "pass", "score": 0.02 },
      "pii":                { "status": "pass" },
      "content":            { "status": "pass" },
      "policy":             { "status": "pass" },
      "response_content":   { "status": "pass" }
    },
    "routing": {
      "mode":             "realtime",
      "region":           "id",
      "provider_type":    "cloud_direct",
      "provider_region":  "ID"
    },
    "cache": {
      "hit":  false,
      "type": "none"
    },
    "weighted_tokens":   1240,
    "cost_usd":          "0.000041",
    "burst":             { "active": false },
    "billing":           null
  }
}

Want to use a strict OpenAI SDK that rejects unknown fields? Set request header x-anoman-strip-extension: 1 and the _anoman block is removed from the body (data still reaches headers + traces).

Response headers

What every 200 carries

HeaderExampleDescription
x-anoman-regionIDRegion that processed the request.
x-anoman-cachenone / provider / responseCache hit type.
x-anoman-guardrail-injectionpass score=0.02Prompt injection scan result.
x-anoman-guardrail-piipass redacted 2 entitiesPII detector result.
x-anoman-rate-limit-rpm120RPM burst-guard cap for your tier.
x-anoman-rate-remaining-rpm86Requests left in the current minute.
x-anoman-burst-activetrue / (absent)Whether a burst credit is currently multiplying your limits.

See rate limits for the full list of rate headers.

Vision

Image input

Wrap user content as an array with type: text and type: image_url entries. The URL can be public or a base64-encoded data URL. Only vision-capable models accept image input — see the residency badge + /models Modality filter to find them.

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "What's in this chart?"},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/chart.png",
                        # Or pass a data URL:
                        # "url": "data:image/png;base64,iVBORw0..."
                    },
                },
            ],
        },
    ],
)
curl https://api.anoman.io/v1/chat/completions \
  -H "Authorization: Bearer anm-sk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o",
    "messages": [{
      "role": "user",
      "content": [
        {"type": "text", "text": "Describe this image."},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
      ]
    }]
  }'

Data URLs. Max 5 MB per image. PNG, JPEG, and WebP accepted. Animated GIFs are downsampled to a single frame.

Tool calling

Functions / tools

Pass tool definitions and the model may emit a tool_calls message instead of free text. The Anoman policy enforcement can deny specific tool names per API key — see the policies page in the dashboard.

response = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[{"role": "user", "content": "What's the weather in Jakarta?"}],
    tools=[
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "Get current weather for a city",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "city":  {"type": "string"},
                        "units": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["city"],
                },
            },
        },
    ],
    tool_choice="auto",  # or {"type": "function", "function": {"name": "get_weather"}}
)
# The model decides whether to call the tool. If so:
tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.function.name, tool_call.function.arguments)
# get_weather  {"city": "Jakarta", "units": "celsius"}
{
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [{
        "id": "call_8mZx7TbF2Vn",
        "type": "function",
        "function": {
          "name": "get_weather",
          "arguments": "{\"city\":\"Jakarta\",\"units\":\"celsius\"}"
        }
      }]
    },
    "finish_reason": "tool_calls"
  }],
  "_anoman": {
    "guardrails": { "policy": { "status": "pass" } },
    "routing": { "mode": "realtime" }
  }
}

Batch routing

Opt into batch (cheaper, longer SLA)

Set x-anoman-prefer-batch: true and the same endpoint returns a 202 with a poll_url. Batch jobs cost ~50% less for non-interactive workloads (data analysis, overnight extraction, eval runs).

# Same endpoint — opt in via header. 202 means queued; poll the
# returned poll_url to retrieve when complete.
curl https://api.anoman.io/v1/chat/completions \
  -H "Authorization: Bearer anm-sk-..." \
  -H "Content-Type: application/json" \
  -H "x-anoman-prefer-batch: true" \
  -d '{
    "model": "deepseek-v3",
    "messages": [{"role": "user", "content": "Summarize this 50-page doc..."}]
  }'
{
  "id": "job_abc123def456",
  "object": "batch_job",
  "status": "queued",
  "sla_minutes": 15,
  "poll_url": "/anoman/v1/batch/job_abc123def456",
  "reason": "customer_preference",
  "meta": {
    "request_id": "req_xyz",
    "timestamp": "2026-05-28T10:00:00Z",
    "region": "id"
  }
}

Full batch lifecycle in /v1/batch endpoint reference.

Streaming

Set stream:true

stream = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[{"role": "user", "content": "Tell me a short story."}],
    stream=True,
)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="", flush=True)

Full wire format + mid-stream error handling in the streaming guide.

Errors

Possible non-200 responses

  • 400 — model_not_found, context_length_exceeded, vision_not_supported
  • 401/403 — auth failures (see authentication) or guardrail blocks (prompt_injection, tool_denied, content_violation)
  • 402 — budget_exceeded
  • 429 — rate-limit or inflight cap (see rate limits)
  • 503/504 — provider issues (retryable)

Full reference at /docs/errors.

Try it from the playground.

3-pane compare with live streaming output.