APIRelay docs

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

  1. 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.
  2. 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.
  3. Make a key. Under API keys, create one per app. It starts with sk-relay- and is shown once.
  4. 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 URLhttps://apirelay.anri.ai/v1
API keyan APIRelay key, sk-relay-...
Modelauto, 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:

  1. The URL path. Use https://apirelay.anri.ai/a/notes/v1 as the base URL. Best for apps where you can only change the URL and the key.
  2. A header: X-Relay-App: notes.
  3. The body: "relay": {"app": "notes"} or "metadata": {"app": "notes"}. These fields are removed before the request goes upstream.
  4. The key's default app, set when you create the key.
  5. 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 creditChatGPT usage limit, 402, insufficient_quota. Rested until the time the provider gives for the reset (an hour when it gives none).
gets throttled429. Rested for the provider's retry-after, at most 15 minutes.
signs outThe saved sign-in stops working. Marked "sign in again" in the dashboard until you reconnect.
errors5xx or a network failure. Rested for 20 seconds or so.
crosses its hold-back lineChatGPT 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 busyAlready 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/completionsStreaming 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/responsesStreaming and non-streaming. Stateless: send the full input each time; previous_response_id is refused.
POST /v1/embeddingsCopilot and OpenAI-compatible accounts only.
GET /v1/modelsEvery 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-idThe id shown in the request log.
x-relay-providerchatgpt, copilot or compat.
x-relay-accountName of the account that answered (URL-encoded).
x-relay-modelThe model that answered. With auto or a fallback it can differ from what you asked for.
x-relay-fallbacksHow many accounts failed first.
x-relay-appThe 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_keyUnknown, revoked or expired key.
403 model_not_allowedThe key's model allow-list doesn't include that model.
404 model_not_foundNo connected account serves that model.
429 insufficient_quotaEvery account is out of credit. retry-after says when the first one frees up.
429 rate_limit_exceededKey per-minute limit, or every account is cooling down.
429 key_daily_tokens and friendsThe key hit one of its caps.
502 upstream_errorEvery account failed for other reasons. The message carries the last one.
503 no_accounts / empty_chainNothing 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.