Documentation

EURU API

The gateway implements the OpenAI chat-completions dialect. If your code already talks to an OpenAI-compatible endpoint, the only changes are the base URL, the API key and the model string.

Quickstart

Base URL for every request:

base-url
https://api.euru.io/v1

Your first call:

curl https://api.euru.io/v1/chat/completions \
  -H "Authorization: Bearer $EURU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5",
    "messages": [{ "role": "user", "content": "Say hello in Lithuanian." }]
  }'

Authentication

Send your key as a bearer token on every request. Keys are created in the dashboard, are scoped to a single account balance, and can carry a spend ceiling and a model allow-list.

headers
Authorization: Bearer euru_sk_live_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Never ship a key to a browser or mobile app. Call the gateway from your own backend. If a key is exposed, revoke it in the dashboard — revocation takes effect immediately.

Listing models

Fetch the live catalogue, including current per-token pricing and context length.

GET /v1/models
curl https://api.euru.io/v1/models \
  -H "Authorization: Bearer $EURU_API_KEY"

// 200 OK
{
  "data": [
    {
      "id": "anthropic/claude-sonnet-5",
      "context_length": 1000000,
      "pricing": { "input": "1.90", "output": "9.50", "unit": "usd_per_million_tokens" },
      "capabilities": ["text", "vision", "tools"]
    }
  ]
}

Chat completions

POST /v1/chat/completions

FieldTypeDescription
modelstringRequired. Provider-prefixed model ID, e.g. openai/gpt-5.6-sol.
messagesarrayRequired. Standard role / content message objects.
max_tokensintegerUpper bound on generated tokens.
temperaturenumberPassed through to the upstream provider.
streambooleanReturn server-sent events instead of one JSON body.
toolsarrayTool/function definitions, OpenAI schema.
fallbacksarrayEURU extension. Model IDs to try, in order, if the primary fails.
metadataobjectEURU extension. Free-form tags echoed into your usage export.

Streaming

Set stream: true to receive incremental chunks as server-sent events. The stream terminates with data: [DONE].

stream.py
stream = client.chat.completions.create(
    model="google/gemini-3.8-flash",
    messages=[{"role": "user", "content": "Write a haiku about Vilnius."}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content or ""
    print(delta, end="", flush=True)

Tool calling

Tool definitions use the OpenAI schema and are translated for providers with a different native format, so the same request body works across Anthropic, OpenAI, Google and open-weight models that support tools.

tools.json
{
  "model": "openai/gpt-5.6-sol",
  "messages": [{ "role": "user", "content": "What is the balance of account 4471?" }],
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_balance",
      "description": "Look up an account balance",
      "parameters": {
        "type": "object",
        "properties": { "account_id": { "type": "string" } },
        "required": ["account_id"]
      }
    }
  }]
}

Fallback routing

The fallbacks array is tried in order when the primary model returns a retryable error — rate limit, upstream timeout, capacity or 5xx. You are billed only for the attempt that produced output, and the response tells you which model answered.

fallbacks.json
{
  "model": "anthropic/claude-sonnet-5",
  "fallbacks": ["openai/gpt-5.6-sol", "mistralai/mistral-large-2512"],
  "messages": [{ "role": "user", "content": "Classify this ticket." }]
}

// the response records what actually served the request
{ "model": "openai/gpt-5.6-sol", "euru": { "routed_from": "anthropic/claude-sonnet-5" } }

Usage & cost

Token counts and the exact charge are returned in the response body and mirrored on response headers, so you can attribute cost without a second API call.

response
"usage": {
  "prompt_tokens": 812,
  "completion_tokens": 240,
  "total_tokens": 1052,
  "cost_usd": "0.006036"
}

# headers
X-EURU-Request-Id:   req_7f3c9a12e4
X-EURU-Model:        anthropic/claude-sonnet-5
X-EURU-Cost-USD:     0.006036
X-EURU-Balance-USD:  418.22

Errors

Errors use standard HTTP status codes with a JSON body. Upstream provider errors are passed through with their original message under error.upstream.

StatusCodeMeaning
400invalid_requestMalformed body, unknown parameter or unsupported combination for that model.
401invalid_keyMissing, malformed or revoked API key.
402insufficient_balanceBalance too low for the estimated cost of the request.
403model_not_allowedThe key's allow-list does not include the requested model.
404model_not_foundUnknown model ID.
409spend_limit_reachedThe key hit its configured daily or total ceiling.
429rate_limitedAccount or upstream rate limit. Retry after Retry-After.
502upstream_errorThe provider failed and no fallback succeeded.

Rate limits

Default limits are 600 requests per minute and 2,000,000 tokens per minute per account, shared across keys. Volume and Enterprise accounts get raised limits on request. Every response carries X-RateLimit-Remaining-Requests and X-RateLimit-Remaining-Tokens.

Data controls

  • Zero retention. Set a key to zero-retention and request and response bodies are never written to disk — only metered token counts are kept.
  • Regional routing. Restrict a key to upstreams that serve from EU regions.
  • No training. Traffic through EURU is excluded from provider training by contract; we never train on it either.
  • Deletion. Logged request bodies, where enabled, are deleted on a retention window you choose between 0 and 30 days.
Need something documented for a security review? Write to Ben@euru.io and we will send the sub-processor list, DPA and architecture overview.