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:
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." }] }'
import os from openai import OpenAI client = OpenAI( base_url="https://api.euru.io/v1", api_key=os.environ["EURU_API_KEY"], ) resp = client.chat.completions.create( model="anthropic/claude-sonnet-5", messages=[{"role": "user", "content": "Say hello in Lithuanian."}], ) print(resp.choices[0].message.content)
import OpenAI from "openai"; const euru = new OpenAI({ baseURL: "https://api.euru.io/v1", apiKey: process.env.EURU_API_KEY, }); const res = await euru.chat.completions.create({ model: "anthropic/claude-sonnet-5", messages: [{ role: "user", content: "Say hello in Lithuanian." }], }); console.log(res.choices[0].message.content);
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.
Authorization: Bearer euru_sk_live_xxxxxxxxxxxxxxxxxxxx Content-Type: application/json
Listing models
Fetch the live catalogue, including current per-token pricing and context length.
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
| Field | Type | Description |
|---|---|---|
| model | string | Required. Provider-prefixed model ID, e.g. openai/gpt-5.6-sol. |
| messages | array | Required. Standard role / content message objects. |
| max_tokens | integer | Upper bound on generated tokens. |
| temperature | number | Passed through to the upstream provider. |
| stream | boolean | Return server-sent events instead of one JSON body. |
| tools | array | Tool/function definitions, OpenAI schema. |
| fallbacks | array | EURU extension. Model IDs to try, in order, if the primary fails. |
| metadata | object | EURU 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 = 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.
{
"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.
{
"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.
"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.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed body, unknown parameter or unsupported combination for that model. |
| 401 | invalid_key | Missing, malformed or revoked API key. |
| 402 | insufficient_balance | Balance too low for the estimated cost of the request. |
| 403 | model_not_allowed | The key's allow-list does not include the requested model. |
| 404 | model_not_found | Unknown model ID. |
| 409 | spend_limit_reached | The key hit its configured daily or total ceiling. |
| 429 | rate_limited | Account or upstream rate limit. Retry after Retry-After. |
| 502 | upstream_error | The 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.