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_tokens

Streaming

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],
).data

Models

GET /v1/models returns everything your key can reach, with its current multiplier. The catalogue today:

IDProviderContext
gpt-5.1OpenAI400K
gpt-5-miniOpenAI400K
claude-opus-5Anthropic200K
claude-sonnet-5Anthropic1M
gemini-3-proGoogle1M
text-embedding-4OpenAI8K

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.2s

Retry 429 with exponential backoff and jitter. The official SDKs do this by default.