Get early access Open the demo

Unified API

Base URL https://api.swurl.ai/v1. The request and response shapes follow the OpenAI chat completions schema, so most existing clients work by changing two settings.

Authentication

Bearer token in the Authorization header. Keys are workspace-scoped and carry the permissions of the seat that created them.

Authorization: Bearer swurl_sk_live_...
Server-side onlyA Swurl key can spend your credit balance. Never ship one to a browser or a mobile app. Proxy through your own backend.

Chat completions

POST /v1/chat/completions

FieldTypeNotes
modelstringA model id from /v1/models, or auto to let Auto-Route choose, or a tier such as auto:fast.
messagesarrayStandard role/content pairs. system, user, assistant, tool.
streambooleanServer-sent events when true.
max_tokensintegerClamped to the target model's ceiling rather than erroring.
temperaturenumberNormalised across providers to a 0–2 scale.
toolsarrayOpenAI tool-call schema. Translated per provider; unsupported models are excluded from routing when present.
swurlobjectSwurl-specific options. See below.

The swurl options object

{
  "swurl": {
    "route": "balanced",        // conservative | balanced | aggressive
    "exclude": ["grok-4"],      // never route here
    "require_zero_retention": true,
    "region": "eu",             // us | eu | au   (Enterprise)
    "budget_cents": 25,         // refuse if this call would exceed
    "trace": true               // include the routing rationale
  }
}

Streaming

Set "stream": true and read server-sent events. Chunks follow the OpenAI delta format. The final event before [DONE] carries the usage and routing summary.

data: {"choices":[{"delta":{"content":"Three"}}]}
data: {"choices":[{"delta":{"content":" conflicts"}}]}
data: {"swurl":{"model":"claude-opus-4.5","cost_cents":6.2,"tokens":{"in":18402,"out":611}}}
data: [DONE]

Routing metadata

Every non-streaming response carries a swurl object alongside the standard fields:

"swurl": {
  "model": "gemini-3-flash",
  "provider": "google",
  "routed": true,
  "task_class": "extraction",
  "reason": "cheapest model above quality threshold for extraction",
  "considered": ["gemini-3-flash", "haiku-4.5", "llama-4-scout"],
  "cost_cents": 0.14,
  "latency_ms": 412,
  "tokens": { "in": 1204, "out": 96 }
}

Set trace to false to omit considered and reason and shave a little response size.

List models

GET /v1/models returns everything your workspace can reach, filtered by plan, region pin and any exclusions set at workspace level.

{
  "data": [
    {
      "id": "claude-opus-4.5",
      "provider": "anthropic",
      "tier": "frontier",
      "context_window": 500000,
      "rate_per_1m_usd": 11.25,
      "supports": ["tools", "vision", "zero_retention"]
    }
  ]
}

Usage

GET /v1/usage?from=2026-08-01&to=2026-08-23&group_by=model returns metered spend for the period. group_by accepts model, provider, member, day or task_class. This is the same data the analytics view renders.

Errors

StatusCodeMeaning
400invalid_requestMalformed body, or a parameter the target model cannot accept.
401invalid_keyMissing, revoked or malformed key.
402credit_exhaustedPool is empty and the workspace has the hard stop enabled.
403model_not_availablePlan, region pin or workspace exclusion blocks this model.
404unknown_modelNo such model id.
429rate_limitedBack off; honour Retry-After.
502provider_errorUpstream failed and no fallback was eligible. Safe to retry.
503no_routeEvery candidate model is excluded or degraded. Widen the constraints.

Errors return a stable code; branch on that rather than on the message text, which may be reworded.