Skip to content

Documentation

SurgeX is an OpenAI-compatible inference gateway. Point an existing client at the base URL below, swap the key, and every model in the catalog becomes reachable through one endpoint.

Every example on this page is executed in CI

The snippets below run against a live server on every build, using the same local development adapter the playground uses. If one stops working, the build fails. Broken documentation examples are the most common defect in this category and this is the mechanism that prevents them here.

Quickstart

Create a key in the dashboard, then send a request. The key is shown once and stored only as a hash.

bash
curl https://surgex.network/api/v1/chat/completions \
  -H "Authorization: Bearer $SURGEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "surgex/auto",
    "messages": [{ "role": "user", "content": "Hello" }]
  }'

Base URL

text
https://surgex.network/api/v1

Configurable per deployment. It is read from NEXT_PUBLIC_API_BASE_URL and is never hardcoded in the client.

Authentication

Pass your key as a bearer token. Anthropic-style x-api-key is also accepted, so the Anthropic SDK works without modification.

bash
Authorization: Bearer sx-live-...

Keys are stored as hashes

SurgeX stores SHA-256 of your key combined with a server-side pepper. A database dump does not yield working keys, and a lost key cannot be recovered, rotate it instead, which mints a replacement and keeps the old one working for 24 hours.

Chat completions

POST /v1/chat/completions, the OpenAI Chat Completions dialect, with a few additive extensions.

FieldTypeDefaultDescription
modelstringrequiredA catalog id, or surgex/auto. May carry a routing suffix such as :rush.
messagesarrayrequiredStandard OpenAI message array. System, user, assistant and tool roles.
streambooleanfalseStream the response as SSE.
max_tokensintegermodel maxAlso accepted as max_completion_tokens.
temperaturenumberprovider default0 to 2.
toolsarray·Function definitions. Routing will only pick tool-capable models.
response_formatobject·text, json_object, or json_schema with strict mode.
reasoning_effortstring·low, medium or high, where the model supports it.
modelsstring[]·SurgeX extension. An ordered fallback chain tried after model.
routingstringflowSurgeX extension. rush, thrift, apex or flow.
providerobject·SurgeX extension. order, only, ignore, max_price, allow_fallbacks.
hedgebooleanrush onlySurgeX extension. Race a second route if the first stalls.

Response

Standard OpenAI shape, plus two additions: provider at the top level, and a surgex object carrying the routing receipt. Clients that ignore unknown fields are unaffected.

json
{
  "id": "chatcmpl-req_01J...",
  "object": "chat.completion",
  "model": "openai/gpt-4.1",
  "provider": "groq",
  "choices": [ /* ... */ ],
  "usage": {
    "prompt_tokens": 1847,
    "completion_tokens": 412,
    "total_tokens": 2259,
    "cost": "0.004192500"
  },
  "surgex": {
    "routing": {
      "mode": "rush",
      "task_class": "code",
      "complexity": "strong",
      "selected_model": "llama-3.3-70b-versatile",
      "selected_provider": "groq",
      "candidates_considered": ["groq:llama-3.3-70b-versatile", "together:..."],
      "attempts": [{ "route": "groq:llama-3.3-70b-versatile", "outcome": "ok", "ms": 91 }],
      "failover_used": false,
      "summary": "code → strong → rush → groq"
    },
    "ttft_ms": 91
  }
}

Cost is a string, on purpose

usage.cost is a decimal string rather than a JSON number. A cost like 0.000000001 loses precision the moment a JavaScript client parses it as a float, and small errors on millions of requests are how balances drift.

Routing

Send surgex/auto and Crossbar chooses. It runs in two stages, and the order matters.

1. Filter, hard requirements

Routes that physically cannot serve the request are removed before anything is scored. A model that cannot call tools is not a cheap option for a request with tools; it is not an option. Filters cover tool support, strict structured output, reasoning budgets, image input, context-window fit, max output tokens, provider allow/deny lists, price ceilings and provider health.

2. Score, soft preferences

Survivors are weighted according to the mode you asked for.

FieldTypeDefaultDescription
:rushRush·Lowest time-to-first-token. Price is a tiebreak.
:thriftThrift·Cheapest route that meets the request requirements.
:apexApex·Most capable route. Used when the task looks hard.
:flowFlow·Balanced speed, cost and capability. The default.
json
{
  "model": "surgex/auto:rush",
  "messages": [ /* ... */ ],
  "provider": {
    "order": ["groq", "together"],
    "ignore": ["fireworks"],
    "max_price": "2.00",
    "allow_fallbacks": true
  }
}

The classifier is local and free

Auto-routing reads structural facts from your request, how many tools are offered, whether a response format is set, how large the context is, whether the text contains code. It does not call a model to decide which model to call, so it adds no latency, has nothing to fail, and sends your prompt nowhere.

When the signals are ambiguous, the classifier reports low confidence and the task class is dropped rather than guessed. The complexity tier, which comes from structural facts like size, still applies.

Failover and hedging

Failover stops at the first byte

Before any content has reached you, a failed route is retried on the next candidate automatically. After the first byte, SurgeX does not switch. Splicing a second provider's tokens into a half-delivered response corrupts the output, breaks tool-call framing and desynchronizes your parser.

A mid-stream failure arrives as an error frame in the stream and is not billed. A 4xx from an upstream is never retried elsewhere, because the request is the problem and three providers will reject it three times.

Hedged first token

Most gateways only react to errors. A provider that is merely slow is invisible to them, and p95 latency is what users feel. When a route has not produced a first token by its own observed median times 1.6, SurgeX starts a second route in parallel and takes whichever answers first. The loser is aborted before it emits anything, so you are not billed for it.

On by default in rush. Set "hedge": true to enable it in any mode, or false to turn it off.

Streaming

Set stream: true. The response is Server-Sent Events in the OpenAI format, terminated by data: [DONE].

Usage and cost arrive in a final chunk before the terminator. This is on by default; set stream_options.include_usage to false to suppress it.

typescript
const stream = await client.chat.completions.create({
  model: 'surgex/auto:rush',
  messages: [{ role: 'user', content: 'Hello' }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}

Tool call arguments arrive as fragments

Each tool_calls[].function.arguments delta is a fragment of a JSON string, not valid JSON on its own. Concatenate every fragment for a given index and parse once at the end. Parsing a partial fragment is the most common tool-calling bug in clients.

Errors

Errors use the OpenAI envelope, extended with a remedy field saying what to actually do, and the upstream status where one exists.

json
{
  "error": {
    "message": "No such model: openai/gpt-9",
    "type": "not_found_error",
    "param": "model",
    "code": "model_not_found",
    "request_id": "req_01J...",
    "remedy": "Call GET /v1/models for the live catalog. Model IDs are case-sensitive."
  }
}
StatusCodeRetryableMeaning
400invalid_requestnoMalformed request. Fix it; retrying will not help.
401invalid_api_keynoKey missing, wrong or revoked.
402insufficient_creditnoNot enough balance to reserve this request.
404model_not_foundnoUnknown model id. Check /v1/models.
413payload_too_largenoBody exceeds 10MB.
429rate_limit_*yesHonour Retry-After. The suffix says which limit tripped.
502upstream_erroryesUpstream failed. SurgeX already tried failover.
503no_routenoNo route satisfies the request. The message says why.
504upstream_timeoutyesNo first token within the deadline. Not billed.

Rate limits

Every response carries the current limit state.

text
x-ratelimit-limit-requests: 60
x-ratelimit-remaining-requests: 58
x-ratelimit-limit-tokens: 200000
x-ratelimit-remaining-tokens: 198420
x-ratelimit-reset-requests: 47s

Limits are per key and configurable in the dashboard. Defaults are 60 requests and 200,000 tokens per minute.

Billing

SurgeX takes 2.00% once, at deposit, and passes provider list price through with no per-token markup. The price in the catalog is the price you are charged.

Streaming requests reserve an upper bound against your balance before the first token, because the final cost is unknown until the stream ends. The reservation is released and replaced with the real cost when it completes. A request that fails before producing a billable token is written off entirely.

Balance is derived from an append-only ledger, not a mutable column. Every credit and charge is a row with an idempotency key, so a retried deposit or a retried settlement cannot double-count.

Data handling

Prompt and completion bodies are not stored. The request log keeps metadata, model, provider, token counts, latency, cost, status, and the columns for bodies are left null unless an operator explicitly enables retention via LOG_REQUEST_BODIES, which is off by default.

The routing receipt returned with each response contains operational facts only: task class, complexity tier, which routes were considered and what was tried. It never contains prompt content or model reasoning.

Documentation · SurgeX