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 redisCreate 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:6379Apply migrations and start it:
bun run --filter @boelabs/bifrost db:migrate
bun run --filter @boelabs/bifrost devConfirm it is up:
curl http://localhost:4000/health/ready # 200 once Postgres + Redis + extensions are healthyFor the rest of this page:
export BASE=http://localhost:4000
export MASTER_KEY=replace-with-at-least-32-random-charactersRegister 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/503means and how to fix it.