Bifrost

Quickstart

Start a local gateway, connect a provider, and make an authenticated request.

This guide creates a local gateway with an OpenAI deployment named general and a virtual key for your application. If the gateway is already running, start with Your first deployment.

Before you start

Install Bun 1.4+ and Docker Compose. You also need an OpenAI API key with access to the model used below. The Compose commands start Postgres 18 and Redis 8 locally.

Keep operator credentials private

The master key administers Bifrost. A provider API key authenticates Bifrost with the upstream provider. Your application receives a separate virtual key; do not give it either operator credential.

Run the gateway

git clone https://github.com/boelabs/bifrost.git && cd bifrost
bun install
docker compose -f docker/compose.yaml -f docker/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-at-least-32-random-characters
ENCRYPTION_KEYRING={"primary":"64_hex_chars_32_bytes"}   # value: openssl rand -hex 32
ACTIVE_ENCRYPTION_KEY_ID=primary
DATABASE_URL=postgres://gateway:gateway@localhost:5432/bifrost
REDIS_URL=redis://localhost:6379

Apply migrations and start it:

bun run --filter @boelabs/bifrost db:migrate
bun run --filter @boelabs/bifrost 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-at-least-32-random-characters

Register a deployment

A deployment binds a public model name to an adapter, 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",
    "adapterKey": "openai",
    "upstreamModel": "gpt-5.5",
    "credentials": { "apiKey": "sk-..." }
  }'

general is now a public model. To inspect the resolved operations and transports before saving, send the same body to POST /admin/deployments/resolve.

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": "sk-abcdefg", "key": "sk-…", "allowedModels": ["general"] } }

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

Make a call

Set model to the public name and use the virtual key returned by the previous step.

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

A successful response contains an assistant message in choices[0].message.content. If the request fails, check the model name, virtual key scope, and provider credentials.

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.

Next steps

  • Concepts — the mental model behind public models, pools, adapters, and transports.
  • Architecture — the same request, traced through every layer in precise order.
  • Creating deployments — every provider and custom-model body.
  • Fallbacks — make a public model survive a provider outage.
  • Troubleshooting — what a 400/401/403/429/503 means and how to fix it.

On this page