Unified Gateway

Quickstart

From clone to your first response, end to end.

This walks through the full path once: start the gateway, register a real provider behind a public model, issue a client key, and make a call. It assumes one OpenAI key, but every step works the same for Anthropic, Google, Azure, or an OpenAI-compatible endpoint — see Creating deployments for the per-provider bodies.

You need two kinds of credentials and they are not the same thing: the master key (MASTER_KEY, operator-only) administers the gateway, and virtual keys (unified-…) are what your clients send. Never hand the master key to a client.

1. Run the gateway

Requirements: Bun 1.3+, Postgres 18+, Redis 8+ (full configuration in Setup).

git clone https://github.com/boelabs/unified-gateway.git && cd unified-gateway
bun install
docker compose -f docker-compose.yml -f compose.local.yaml up -d postgres redis

Create apps/gateway/.env (see apps/gateway/.env.example) with at least:

PORT=4000
NODE_ENV=development
MASTER_KEY=replace-with-a-long-secret
CREDENTIALS_ENCRYPTION_KEY=64_hex_chars_32_bytes   # openssl rand -hex 32
DATABASE_URL=postgres://gateway:gateway@localhost:5432/unifiedgateway
REDIS_URL=redis://localhost:6379

Apply migrations and start it:

bun run --filter @boelabs/unified-gateway db:migrate
bun run --filter @boelabs/unified-gateway dev

Confirm it is up:

curl http://localhost:4000/health/ready   # 200 once Postgres + Redis + extensions are healthy

For the rest of this page:

export BASE=http://localhost:4000
export MASTER_KEY=replace-with-a-long-secret

2. Register a deployment

A deployment binds a public model name to a provider, an upstream model, and credentials. Create one with the master key — the provider API key goes inline and is encrypted at rest:

curl -X POST $BASE/admin/deployments \
  -H "Authorization: Bearer $MASTER_KEY" -H "content-type: application/json" -d '{
    "publicModel": "general",
    "provider": "openai",
    "upstreamModel": "gpt-5.4",
    "credentials": { "apiKey": "sk-..." }
  }'

general is now a public model. Want to preview what the gateway resolved (capabilities, operations, transports) without saving? Send the same body to POST /admin/deployments/resolve.

3. Issue a virtual key

Clients never use the master key. Mint a scoped virtual key instead — allowedModels limits which public models it may call ([] or omitted = all):

curl -X POST $BASE/admin/keys \
  -H "Authorization: Bearer $MASTER_KEY" -H "content-type: application/json" -d '{
    "name": "my-app",
    "allowedModels": ["general"]
  }'

The response contains the plaintext key once — store it now, it is never shown again:

{ "data": { "id": "…", "keyPrefix": "unified-abcd…", "key": "unified-…", "allowedModels": ["general"] } }

See Virtual keys for budgets, RPM/TPM limits, and rotation.

4. Make a call

Use the virtual key exactly as you would an OpenAI key, with model set to your public name:

curl -X POST $BASE/v1/chat/completions \
  -H "Authorization: Bearer unified-..." -H "content-type: application/json" -d '{
    "model": "general",
    "messages": [{ "role": "user", "content": "Say hello in one word." }]
  }'

Or with the official OpenAI SDK — this is the whole integration:

from openai import OpenAI

client = OpenAI(base_url="http://localhost:4000/v1", api_key="unified-...")
resp = client.chat.completions.create(
    model="general",
    messages=[{"role": "user", "content": "Say hello in one word."}],
)
print(resp.choices[0].message.content)

The same key and base URL also serve /v1/responses, /v1/messages (Anthropic shape), /v1/embeddings, /v1/images/*, and /v1/audio/transcriptions. Every response carries an x-request-id; virtual-key requests with limits also return x-ratelimit-* headers.

  • Concepts — the mental model behind public models, pools, adapters, and transports.
  • Creating deployments — every provider and custom-model body.
  • Fallbacks — make a public model survive a provider outage.
  • Errors — what a 400/401/403/429/503 means and how to fix it.

On this page