APIRelay guide
APIRelay turns the subscriptions you already pay for (ChatGPT, GitHub Copilot) into one OpenAI-style API with keys, limits and per-app reports. Anything that can talk to OpenAI can talk to it. You change two settings: the base URL and the key.
Quick start
- Connect an account. In the dashboard, choose Sign in with ChatGPT. You get a one-time code and type it on OpenAI's own page. Your password never passes through APIRelay.
- Check the order. Under Routing, the first account in the chain answers first. Add a second ChatGPT account below it and it takes over when the first runs out.
- Make a key. Under API keys, create one per app. It starts with
sk-relay-and is shown once. - Send a request.
curl https://apirelay.anri.ai/v1/chat/completions \
-H "Authorization: Bearer sk-relay-..." \
-H "Content-Type: application/json" \
-d '{"model": "auto", "messages": [{"role": "user", "content": "Say hi"}]}'
auto means "whatever my chain says". You can also ask for a model by id; GET /v1/models lists every model your accounts offer.
Using it in other apps
Look for a setting called base URL, API base, endpoint or "OpenAI-compatible provider". Give it:
| Base URL | https://apirelay.anri.ai/v1 |
|---|---|
| API key | an APIRelay key, sk-relay-... |
| Model | auto, or any id from /v1/models |
Python
from openai import OpenAI
client = OpenAI(base_url="https://apirelay.anri.ai/v1", api_key="sk-relay-...")
stream = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Three facts about telephone exchanges"}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
Node
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://apirelay.anri.ai/v1", apiKey: process.env.RELAY_KEY });
const r = await client.chat.completions.create({
model: "auto",
messages: [{ role: "user", content: "Name a switchboard part" }],
});
console.log(r.choices[0].message.content);
Environment variables
Many tools read the standard variables, so this is often all it takes:
export OPENAI_BASE_URL=https://apirelay.anri.ai/v1
export OPENAI_API_KEY=sk-relay-...
Apps like Open WebUI, LibreChat, n8n, Continue, Obsidian plugins and LangChain all have an "OpenAI-compatible" or "custom base URL" option. Some older tools want the URL without /v1; if you get 404s, try it both ways.
Telling apps apart
Every request is filed under an app name so the usage page can split it. APIRelay takes the first one it finds:
- The URL path. Use
https://apirelay.anri.ai/a/notes/v1as the base URL. Best for apps where you can only change the URL and the key. - A header:
X-Relay-App: notes. - The body:
"relay": {"app": "notes"}or"metadata": {"app": "notes"}. These fields are removed before the request goes upstream. - The key's default app, set when you create the key.
- Otherwise
default.
The simplest setup is one key per app with the app name on the key. For your own software, the header also lets you name the end user: X-Relay-User: user-42 (or the standard OpenAI user field). Usage then splits by end user too.
client = OpenAI(
base_url="https://apirelay.anri.ai/v1",
api_key="sk-relay-...",
default_headers={"X-Relay-App": "support-bot"},
)
Models and fallback
The chain on the Routing page is an ordered list of account and model pairs. For each request, APIRelay builds a list of candidates and tries them in order until one starts answering:
model: "auto"goes down the chain from the top.- A specific model goes first to every connected account that has it (chain order), then down the rest of the chain. Turn off "fall back to other models" on a key if an app must only ever get the model it asked for.
- Embeddings only go to accounts that list that embeddings model.
An account is skipped, and the next one tried, when it:
| runs out of credit | ChatGPT usage limit, 402, insufficient_quota. Rested until the time the provider gives for the reset (an hour when it gives none). |
|---|---|
| gets throttled | 429. Rested for the provider's retry-after, at most 15 minutes. |
| signs out | The saved sign-in stops working. Marked "sign in again" in the dashboard until you reconnect. |
| errors | 5xx or a network failure. Rested for 20 seconds or so. |
| crosses its hold-back line | ChatGPT only: you can tell the relay to stop at, say, 80% of the 5-hour or weekly window and leave the rest for yourself. |
| is busy | Already running its limit of parallel requests (3 by default). |
Fallback only happens before the first token is sent. Once an answer is streaming, it's committed to that account. A request that the upstream rejects as invalid (a bad tool schema, say) comes straight back to you with the upstream's message and is not retried elsewhere.
Key limits
Each key can carry any of: requests per minute, requests per day, tokens per day, tokens per month, an allow-list of models, and an expiry date. Days and months run on UTC. Tokens are counted when a request finishes, so a burst of long requests can go a little past a token cap.
A key over its limit gets a 429 in OpenAI's format, with code set to rate_limit_exceeded, key_daily_requests, key_daily_tokens or key_monthly_tokens. Per-minute limits also send retry-after and x-ratelimit-remaining-requests.
What's supported
POST /v1/chat/completions | Streaming and non-streaming, stream_options.include_usage, tools and tool calls (including parallel), tool_choice, images in user messages, response_format with JSON mode or JSON schema, reasoning_effort. |
|---|---|
POST /v1/responses | Streaming and non-streaming. Stateless: send the full input each time; previous_response_id is refused. |
POST /v1/embeddings | Copilot and OpenAI-compatible accounts only. |
GET /v1/models | Every model your accounts offer, plus auto. |
ChatGPT accounts ignore sampling settings. The subscription backend takes no temperature, top_p, max_tokens, stop or seed. They're accepted and dropped so clients that always send them keep working. Copilot and OpenAI-compatible accounts pass them through.
Not available: n greater than 1, audio in or out, image generation, the Files, Batch, Assistants and fine-tuning APIs, and legacy /v1/completions.
Reasoning. Turn on "stream reasoning summaries" in Settings and ChatGPT's reasoning summary arrives as reasoning_content in each delta, which several chat front-ends display as thinking.
Response headers
x-request-id | The id shown in the request log. |
|---|---|
x-relay-provider | chatgpt, copilot or compat. |
x-relay-account | Name of the account that answered (URL-encoded). |
x-relay-model | The model that answered. With auto or a fallback it can differ from what you asked for. |
x-relay-fallbacks | How many accounts failed first. |
x-relay-app | The app name the request was filed under. |
Errors
Every error has OpenAI's shape, so SDKs raise their usual exceptions:
{"error": {"message": "Every connected account is out of credit...", "type": "insufficient_quota", "param": null, "code": "insufficient_quota"}}
401 invalid_api_key | Unknown, revoked or expired key. |
|---|---|
403 model_not_allowed | The key's model allow-list doesn't include that model. |
404 model_not_found | No connected account serves that model. |
429 insufficient_quota | Every account is out of credit. retry-after says when the first one frees up. |
429 rate_limit_exceeded | Key per-minute limit, or every account is cooling down. |
429 key_daily_tokens and friends | The key hit one of its caps. |
502 upstream_error | Every account failed for other reasons. The message carries the last one. |
503 no_accounts / empty_chain | Nothing connected, or the chain has no steps. |
If an answer breaks after streaming started, the stream ends with a data: {"error": ...} line before [DONE].
Accounts and your data
ChatGPT signs in with the same device-code flow the Codex CLI uses, and works with Plus, Pro, Business and Enterprise plans. The dashboard shows how much of the 5-hour and weekly windows are used, straight from the numbers ChatGPT returns. GitHub Copilot uses GitHub's device flow and needs an active Copilot plan. OpenAI-compatible takes any https base URL and API key, handy as the last step once the subscriptions run dry.
Sign-ins and keys are encrypted at rest, tied to your account, and only ever sent to the provider they came from. The request log keeps counts and routing; message text is only stored if you turn on previews in Settings, and logs are deleted after the retention you choose.
About the terms. A subscription is sold for use in the vendor's own apps. Relaying it to other software can go against those terms and can get an account limited. Keep APIRelay to your own apps and your own use.