Endpoints
- Admin UI:
bifrost.augustin.ai— Basic auth withBIFROST_USER/BIFROST_PASSWORD - Inference (VK auth — send
x-bf-vk: sk-bf-...orAuthorization: Bearer sk-bf-...):- OpenAI dialect:
bifrost.augustin.ai/openai/v1/... - Anthropic dialect:
bifrost.augustin.ai/anthropic/v1/... - Native:
bifrost.augustin.ai/v1/...
- OpenAI dialect:
- Health:
bifrost.augustin.ai/health
{provider}/{model_id} (e.g. cecelia/us.anthropic.claude-haiku-4-5-20251001-v1:0). Bare model strings without a provider prefix get resolved by the model catalog at request time, but explicit prefixes are deterministic.
State
| File | Purpose |
|---|---|
bifrost/config.json | Versioned config: store paths, admin auth, routing rules. No providers, no VKs. In git. |
data/config.db | Runtime config: providers, keys, virtual keys. Authoritative for everything not in config.json. |
data/logs.db | Request logs with 365-day retention. |
config.jsonis mounted read-only into the container.config.dbis the SQLite runtime store.- The Web UI and Admin API both read and write
config.db.
config.json against config.db per-entity. Entities only in DB are preserved. Entities only in the file are re-seeded. Entities in both: DB wins unless the file’s hash changed. For this setup, providers and VKs live exclusively in DB so they are never re-seeded — the only things that come from config.json are the three store blocks, the auth config, and the routing rules.
Providers
| Provider | Auth | Region | Cost | Notes |
|---|---|---|---|---|
bedrock | (built-in, no keys) | — | — | Built-in to Bifrost binary, not deletable. Has no keys; any traffic 403s. Stale clients sending bedrock/* get rewritten to cecelia by a routing rule. |
cecelia | rotating STS | us-west-2 | Free, work-funded | Mac launchd job pushes fresh STS creds via PUT /api/providers/cecelia/keys/<uuid> on a 5-minute timer. Streaming requires User-Agent: claude-cli/... set in the provider’s network_config.extra_headers because Cecelia’s IAM role gates streaming on a UA Condition. |
isengard-runtime | long-term bearer | us-east-1 | Personal AWS, paid | 74 models. Anthropic profile IDs + non-Anthropic foundation IDs. Budget exhausted for the current month — minimize traffic. |
isengard-mantle-openai | long-term bearer | us-east-1 | Free, work-funded | 38 OpenAI-shape Mantle models (gpt-oss, qwen, kimi, etc.) at bedrock-mantle.us-east-1.api.aws. |
isengard-mantle-openai-east2 | long-term bearer (same as -openai) | us-east-2 | Free, work-funded | OpenAI’s proprietary GPT models on Bedrock: openai.gpt-5.4, openai.gpt-5.5. Responses API only, at bedrock-mantle.us-east-2.api.aws/openai. Separate provider because base_url/region is provider-level — GPT-5.x is Ohio-only, the -openai provider is us-east-1. Reached only by the client-scoped work-laptop-codex VK (passthrough verified end-to-end 2026-06-06). |
isengard-mantle-anthropic | long-term bearer (in extra_headers) | us-east-1 | Free, work-funded | 2 Anthropic-shape models at bedrock-mantle.us-east-1.api.aws/anthropic. Keyless because env. interpolation only fires on config_keys.value, not on extra_headers. |
kiro | OAuth via aiclient2api | n/a | Free | OpenAI-compatible proxy at http://aiclient2api:3000/claude-kiro-oauth (aiclient2api runs in ns infra, same as Bifrost, so the bare name resolves). 5 Claude variants. Needs a seeded Kiro OAuth token — see cluster/infra/aiclient2api/AGENTS.md. OAuth wedges roughly monthly. |
cecelia and isengard-runtime need us.* or global.* cross-region inference profile IDs. Bare anthropic.* IDs reject. Mantle is the opposite — uses bare IDs only. Other model families accept either.
Routing
Two active global routing rules (both inconfig.json under governance.routing_rules, also live in DB):
bedrock-to-cecelia-redirect(priority 0): rewrites any request withprovider == "bedrock"to cecelia.work-laptop-anthropic-to-cecelia(priority 10): for the work-laptop VK, routes any model starting withus.anthropic.orglobal.anthropic.to cecelia, regardless of whichprovider/prefix the client sent.
provider/ prefix.
Virtual keys
Nine VKs. Six legacy machine-named, flat-scoped across the five usable providers (augustin-default, i3, mastra, n8n-classifier, pentium, work-laptop; the dead built-in bedrock is granted to none). Three client-scoped (the reshape, in progress), each scoped via explicit key_ids in the create request to sidestep the allow_all_keys POST bug:
work-laptop-codex→isengard-mantle-openai-east2only (openai.gpt-5.4/5.5).i3-t3code→ceceliaonly (Claude profile IDs) — for t3code on i3.i3-temporal→ceceliaonly (Claude profile IDs) — for Temporal workers on i3.
~/.claude/bin/api-key-helper.sh is env-aware — if a client exports ANTHROPIC_AUTH_TOKEN, the helper echoes that VK; otherwise it falls back to the machine VK (BIFROST_VK from bifrost/.env, i.e. i3). So multiple Claude clients on one box (native CLI, t3code, a Temporal worker) map to distinct VKs without separate HOMEs — the client just sets ANTHROPIC_AUTH_TOKEN to its VK. Native Claude sets nothing, so it stays on i3.
VK values are bearer tokens; they’re stored plaintext in governance_virtual_keys.value.