Documentation
BestAGI API reference
The gateway implements the OpenAI REST protocol. If your code already talks to api.openai.com, it talks to BestAGI — change the base URL and the key.
Quickstart
Create a key in the dashboard, export it, and send a request. The base URL is the only thing that differs from calling the provider directly.
export BESTAGI_API_KEY="sk-bag-..."
curl https://api.bestagi.com/v1/chat/completions \
-H "Authorization: Bearer $BESTAGI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.1",
"messages": [{"role": "user", "content": "Hello"}]
}'Authentication
Every request carries a bearer token in the Authorization header. Keys are scoped to a single project, can carry their own budget and rate limit, and are revocable independently.
Authorization: Bearer sk-bag-9f2c...Keys are shown once at creation. Store them in your secret manager — the gateway keeps only a hash and the display prefix.
Chat completions
POST /v1/chat/completions accepts the full OpenAI request body. Anthropic and Google models are addressed by name through the same endpoint; parameter translation happens inside the gateway.
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BESTAGI_API_KEY"],
base_url="https://api.bestagi.com/v1",
)
resp = client.chat.completions.create(
model="claude-opus-5",
messages=[
{"role": "system", "content": "You are a release-notes editor."},
{"role": "user", "content": changelog},
],
temperature=0.2,
max_tokens=1024,
)
print(resp.usage) # -> prompt_tokens, completion_tokens, total_tokensStreaming
Set stream: true for server-sent events. Chunks are forwarded as they arrive from upstream; the gateway adds no buffering.
const stream = await client.chat.completions.create({
model: "gpt-5.1",
messages,
stream: true,
stream_options: { include_usage: true },
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}Tool calling
Function and tool definitions pass through unchanged, including parallel tool calls and JSON-schema structured output.
tools = [{
"type": "function",
"function": {
"name": "get_deploy_status",
"description": "Current status of a deployment",
"parameters": {
"type": "object",
"properties": {"id": {"type": "string"}},
"required": ["id"],
},
},
}]
resp = client.chat.completions.create(
model="claude-sonnet-5",
messages=messages,
tools=tools,
tool_choice="auto",
)Embeddings
POST /v1/embeddings supports batches of up to 2,048 inputs per request.
vectors = client.embeddings.create(
model="text-embedding-4",
input=[doc.text for doc in batch],
).dataModels
GET /v1/models returns everything your key can reach, with its current multiplier. The catalogue today:
| ID | Provider | Context |
|---|---|---|
| gpt-5.1 | OpenAI | 400K |
| gpt-5-mini | OpenAI | 400K |
| claude-opus-5 | Anthropic | 200K |
| claude-sonnet-5 | Anthropic | 1M |
| gemini-3-pro | 1M | |
| text-embedding-4 | OpenAI | 8K |
Errors
Error bodies follow the OpenAI shape. Upstream errors are forwarded verbatim; gateway-generated errors carry a bestagi_ prefix on the code so you can tell them apart.
{
"error": {
"message": "No upstream capacity for model 'gpt-5.1' in this region.",
"type": "service_unavailable",
"code": "bestagi_no_capacity",
"request_id": "req_01J9X4KQ2M"
}
}- 401 — key missing, malformed, or revoked.
- 402 — balance exhausted or key budget reached.
- 429 — rate or concurrency limit hit; retry with backoff.
- 5xx — upstream failure after failover was exhausted.
Rate limits
Limits are per key and expressed in requests per minute and tokens per minute. Current values come back on every response:
x-ratelimit-limit-requests: 10000
x-ratelimit-remaining-requests: 9942
x-ratelimit-limit-tokens: 12000000
x-ratelimit-remaining-tokens: 11840233
x-ratelimit-reset-tokens: 4.2sRetry 429 with exponential backoff and jitter. The official SDKs do this by default.