# Relay API [OI]-compatible AI gateway: one API key, one endpoint, open-weights models, per-token billing. ## Base URL - `https://api.relayrouter.org/v1` - Point any [OI] SDK at it with an `sk-or-v1-...` key. ## Auth Two bearer tokens, both sent as `Authorization: Bearer `: | token | looks like | lifetime | use for | |---|---|---|---| | JWT session | `eyJhbG...` | 24 h (`JWT_TTL_HOURS`) | dashboard, account, keys | | API key | `sk-or-v1-<64 hex>` | until revoked/expired | inference | - `POST /auth/signup` `{"email": "...", "password": "..."}` -> `{token, user_id}`. Only `gmail.com`, `outlook.com`, `hotmail.com` and `icloud.com` may sign up. A Cloudflare Turnstile token is required when captcha is enabled: send `{"turnstile": "", "fingerprint": ""}`. Uniqueness is on a canonical address, so dots and `+tags` do not create extra accounts (`h.ello+2@gmail.com` == `hello@gmail.com`). - `POST /auth/signin` -> `{token, user_id}`. No captcha. After 10 failures for the same IP + email in 15 minutes you get `429` for 15 minutes. - There is **no refresh endpoint**: a `401` means sign in again. ## Keys - `POST /keys` `{"name": "default"}` -> the secret is returned **once**. - Optional fields: `ttl_hours`, `expires_at`, `rpm_limit`, `conc_limit`, `daily_cap_micro`, `monthly_cap_micro`, `allow_fallback`, `allowed_models` (array of model ids; `[]` = inherit the account allowlist), and BYOK (`byok_provider`, `byok_key`, `byok_base_url`). - `GET /keys`, `PATCH /keys/{id}`, `DELETE /keys/{id}` (revoke), `POST /keys/{id}/rotate` (new secret, same id). - Max 20 active keys per account. ## Models - `GET /v1/models` -> `{data: [...]}`. Public, no auth. - `GET /v1/models/{id}` -> full detail for one model. - Ids are plain names, e.g. `deepseek-v4.1-flash-1017`. A `:free` suffix marks a free-tier variant of the same upstream model (`deepseek-v4.1-flash-1017:free`). - Current catalog (`GET /v1/models` is always authoritative): | id | context | max out | $/M in | $/M out | notes | |---|---|---|---|---|---| | `deepseek-v4-flash-0731` | 1048576 | 131072 | 0.143 | 0.429 | cache, 35% off official | | `deepseek-v4-pro-0813` | 1048576 | 384000 | 0.429 | 1.287 | cache, 35% off official | | `deepseek-v4.1-flash-1017` | 1050000 | 393216 | 0.0975 | 0.39 | vision, cache, 35% off official | | `deepseek-v4.1-flash-1017:free` | 1050000 | 393216 | 0 | 0 | free, daily cap 700k in / 300k out, vision, cache | | `glm-4.6v` | 128000 | 16000 | 0.195 | 0.585 | vision, cache, 35% off official | | `glm-5.2` | 1000000 | 128000 | 0.91 | 2.86 | cache, 35% off official | | `glm-5.3` | 1000000 | 128000 | 0.91 | 2.86 | cache, 35% off official | | `glm-5.3-flash` | 1048576 | 131072 | 0.0975 | 0.325 | vision, cache, 35% off official | | `hy3` | 262144 | 131072 | 0.091 | 0.377 | vision, cache, 35% off official | | `hy4-preview` | 1000000 | 65536 | 0.5421 | 1.62565 | vision, cache, 35% off official | | `kimi-k2.5` | 262144 | 65536 | 0.39 | 1.95 | vision, cache, 35% off official | | `kimi-k2.6` | 262144 | 262144 | 0.6175 | 2.6 | vision, cache, 35% off official | | `kimi-k2.7-code` | 262144 | 262144 | 0.6175 | 2.6 | vision, cache, 35% off official | | `kimi-k2.8-preview` | 1000000 | 32000 | 0.6175 | 2.6 | vision, cache, 35% off official | | `kimi-k3` | 1048576 | 1048576 | 1.95 | 9.75 | vision, cache, 35% off official | | `minimax-m2` | 204800 | 131072 | 0.195 | 0.78 | cache, 35% off official | | `minimax-m2.1` | 204800 | 131072 | 0.195 | 0.78 | cache, 35% off official | | `minimax-m2.1-highspeed` | 204800 | 65536 | 0.39 | 1.56 | cache, 35% off official | | `minimax-m2.5` | 204800 | 131072 | 0.195 | 0.78 | cache, 35% off official | | `minimax-m2.5-highspeed` | 204800 | 131100 | 0.39 | 1.56 | cache, 35% off official | | `minimax-m2.7` | 204800 | 131072 | 0.195 | 0.78 | cache, 35% off official | | `minimax-m2.7-highspeed` | 204800 | 131100 | 0.39 | 1.56 | cache, 35% off official | | `minimax-m3` | 1048576 | 512000 | 0.195 | 0.78 | vision, cache, 35% off official | | `minimax-m3.1-flash-preview` | 1000000 | 524288 | 0.195 | 0.78 | vision, cache, 35% off official | | `promo/deepseek-v4.1-flash-1017` | 1048576 | 384000 | 0.06 | 0.24 | 60% off official | | `qwen3.8-2.4t-a95b` | 262144 | 1010000 | 1.3 | 3.9 | cache, 35% off official | | `qwen3.8-27b` | 262144 | 32768 | 0.325 | 1.95 | vision, cache, 35% off official | | `qwen3.8-flash` | 991808 | 131072 | 0.0975 | 0.3055 | vision, cache, 35% off official | | `qwen3.8-max` | 991000 | 65536 | 1.3 | 3.9 | vision, cache, 35% off official | | `qwen3.8-max-0902` | 991808 | 131072 | 1.3 | 3.9 | vision, cache, 35% off official | | `qwen3.8-omni-flash` | 991808 | 131072 | 0.0975 | 0.3055 | vision, cache, 35% off official | - The catalog is open-weights only: DeepSeek, GLM, Kimi, MiniMax, Qwen, Hunyuan and similar. No GPT / Gemini / Grok / Claude. - Fields per model: `id`, `name`, `description`, `modality` (e.g. `text,image`), `supports_cache`, `context_length`, `max_output_tokens`, `is_free`, `discounted`, `discount_pct`, `saved_pct`, `limits`, `pricing`. - `pricing` is USD per 1M tokens: `prompt`, `completion`, `token_multiplier`, `fixed_micro` (per-request network fee, micro-USD), media unit prices, and the official baseline (`official_prompt`, `official_completion`) so `saved_pct` can show the discount. - Limits are enforced: a prompt estimate over `context_length` returns `400`, and `max_tokens` is clamped to `max_output_tokens`. - Some models carry **per-account daily caps** (`limits.daily_input_tokens`, `limits.daily_output_tokens`). They reset at 00:00 UTC and are counted from provider-reported tokens (cached input and reasoning included). Exceeding them returns `403`. ## Chat - `POST /v1/chat/completions` `{"model": "", "messages": [{"role": "user", "content": "..."}], "stream": true|false, "max_tokens": 512, "temperature": 0.7, "tools": [...], "response_format": {"type": "json_object"}, "no_fallback": true, "fallback_models": ["", ""]}` - Streaming is SSE: `data: {...}` chunks, terminated by `data: [DONE]`. The final chunk may carry `usage`. - Failover: when your account setting `auto_fallback` is on (default) and the key allows it, retryable upstream failures (timeout / 429 / 5xx) walk the model's `fallback_to` chain or your `fallback_models` (up to 4 models total). Same-model retries happen even with `no_fallback`. - The model that actually served the request is the one billed. - Failed upstream calls are never billed. - If **you** disconnect mid-stream, the reserved worst-case estimate is billed. - `GET /account/settings` / `PATCH /account/settings` `{"auto_fallback": false}` opts out of failover. ## Embeddings - `POST /v1/embeddings` `{"model": "", "input": "..."}` — billed on input tokens at the prompt price x multiplier. ## Images - `POST /v1/images/generations` `{"model": "", "prompt": "...", "n": 1}` (`n` 1–10) — billed per image + fee. - `POST /v1/images/edits` (multipart: `image`, `mask?`, `prompt`, `model`). - `POST /v1/images/variations` (multipart: `image`, `model`). ## Audio - `POST /v1/audio/speech` `{"model": "", "input": "...", "voice": "..."}` -> audio bytes, billed on input text. - `POST /v1/audio/transcriptions` (multipart: `file`, `model`, `language?`) -> `{"text": "..."}`, billed per audio minute. - `POST /v1/audio/translations` — same shape, output in English. - `POST /v1/transcripts` — alias of transcriptions. ## Video - `POST /v1/videos` `{"model": "", "prompt": "...", "duration_seconds": 5}` -> generation job JSON, billed per second / flat fee. - `GET /v1/videos/{job}?model=` — job status, never billed. ## Moderation - `POST /v1/moderations` `{"input": "...", "model": "..."}` -> passthrough JSON. Free to call; only a per-account network-fee override, if any, is charged. ## Billing - Money is **micro-USD integers**: `1_000_000 micro = $1.00`. `balance_usd = balance_micro / 1e6`. - Per request: `network_fee + token_cost`. `token_cost` = `(prompt × price_in + completion × price_out) / 1e6 × token_multiplier`. - The network fee is the model's `pricing.fixed_micro` (1000 = $0.001). - `GET /auth/me` -> `balance_micro`, `balance_usd`, `email_verified`, `role`. - `GET /account/limits` -> your quota, allowlist, price overrides, and each model's daily caps with today's usage and remaining budget. - `GET /usage?days=7` -> totals, per-day series, per-model breakdown (includes the network fee). - BYOK (your own upstream key on an API key): token cost is $0, the network fee still applies. Platform-assigned upstream keys bill normally. ## Free tier - One free model: `deepseek-v4.1-flash-1017:free` (bills $0). - Requires a verified account and is capped at **2,000,000** free tokens per account, plus the model's daily caps (700,000 input / 300,000 output per account per UTC day). - Verification comes from the Discord flow: join the server, Discord account at least 14 days old, sign in again on the redeem page, solve a captcha. One free plan per person / device / network. - `GET /free-status` -> `{per_user_cap, global_cap, global_used_today, locked, verified_required}`. ## Deposits - `POST /deposits {"amount_usd": 10}` -> `{payment_url, track_id, status}`. Bounds $0.50–$100,000. Pay at `payment_url`. - Credit lands from the payment webhook; `POST /deposits/{id}/sync` is the missed-callback fallback and is safe to poll every 5 s. - `GET /deposits` lists your invoices, `GET /deposits/currencies` lists accepted coins. - `GET /billing/receipts`, `GET /billing/receipts/{id}` — completed top-ups only. - A completed deposit also verifies the account and emails a receipt. ## Errors Two shapes: - `{"error": "message"}` for request / account problems. - `{"error": {"message": "", "type": "upstream_error", "code": ""}}` for provider failures. Only the stable code is exposed; upstream detail is never returned to callers. | status | meaning | |---|---| | 400 | invalid body, over the context window, or a policy rejection | | 401 | bad/expired JWT, bad/revoked/expired API key | | 402 | out of credit, or the balance cannot cover the request | | 403 | allowlist, token quota, key cap, disabled model, daily cap, free access disabled | | 404 | unknown model or resource | | 409 | email already registered | | 413 | body over 10 MB | | 429 | rate limited (see `Retry-After`) | | 500 | internal error | | 503 | upstream/provider failure — retry | Upstream codes: `no_api_key`, `provider_auth_error`, `model_unavailable`, `invalid_request`, `rate_limited`, `upstream_timeout`, `upstream_unreachable`, `empty_response`, `upstream_error`. ## Limits - Per IP: 1000 req/s sustained (4000 burst); `/v1/models` 200/400; auth endpoints 2/30; media 100/200. - Per IP: at most 96 concurrent in-flight requests, then `429` with `Retry-After: 2`. - Per key: `rpm_limit`, `conc_limit`, `daily_cap_micro`, `monthly_cap_micro`. - Body limit 10 MB. Request timeout 300 s (45 s auth, 30 s redeem). - Response headers: `x-ratelimit-limit`, `x-ratelimit-remaining`, and `retry-after` on `429`. ## Notes - Signup is restricted to gmail.com / outlook.com / hotmail.com / icloud.com. - CORS is permissive; auth is bearer-token only (no cookies). - Model ids and prices can change; read `GET /v1/models` rather than hardcoding.