Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions docs/api/procuring-api-keys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,23 @@ Use an API key as the bearer token when calling these endpoints from scripts, ba

## Management API Endpoints

Use this bearer token for management API endpoints that configure or inspect Bifrost. The following endpoint patterns use management API authentication in the OpenAPI spec:
Use this bearer token for management API endpoints that configure or inspect Bifrost. The following endpoint patterns use management API authentication in the OpenAPI spec.

Governance resources moved under the `/api/governance` namespace. Both generations are listed: the canonical route is the one to build against, and the rows marked *deprecated aliases* are the pre-move paths, which still answer today and are scheduled for removal in the following major release. See the [governance API namespace migration guide](/migration-guides/governance-api-namespace) for the full mapping.

| Area | Endpoint patterns |
| --- | --- |
| Configuration | `/api/config`, `/api/proxy-config`, `/api/pricing/force-sync` |
| Providers and models | `/api/providers`, `/api/providers/{provider}`, `/api/providers/{provider}/keys`, `/api/providers/{provider}/keys/{key_id}`, `/api/keys`, `/api/models`, `/api/models/base`, `/api/models/details`, `/api/models/parameters` |
| Plugins | `/api/plugins`, `/api/plugins/builtins`, `/api/plugins/{name}` |
| Governance | `/api/governance/virtual-keys`, `/api/governance/virtual-keys/{vk_id}`, `/api/governance/teams`, `/api/governance/teams/{team_id}`, `/api/governance/customers`, `/api/governance/customers/{customer_id}`, `/api/governance/budgets`, `/api/governance/rate-limits`, `/api/governance/routing-rules`, `/api/governance/routing-rules/{rule_id}`, `/api/governance/model-configs`, `/api/governance/model-configs/{mc_id}`, `/api/governance/providers`, `/api/governance/providers/{provider_name}`, `/api/governance/pricing-overrides`, `/api/governance/pricing-overrides/{id}` |
| Business units | `/api/governance/business-units`, `/api/governance/business-units/{id}`, `/api/governance/business-units/{id}/teams`, `/api/governance/business-units/{id}/teams/{team_id}`, `/api/governance/business-units/{id}/governance` |
| RBAC | `/api/roles`, `/api/roles/{id}`, `/api/roles/{id}/permissions`, `/api/resources`, `/api/operations`, `/api/permissions` |
| Users and teams | `/api/users`, `/api/users/{id}`, `/api/users/{id}/role`, `/api/users/{id}/teams`, `/api/users/me/permissions`, `/api/users/email/{email}/virtual-keys`, `/api/teams`, `/api/teams/{id}`, `/api/teams/{id}/members`, `/api/teams/{id}/members/{userId}` |
| Access profiles | `/api/access-profiles`, `/api/access-profiles/{id}`, `/api/access-profiles/{id}/activate`, `/api/access-profiles/{id}/deactivate`, `/api/access-profiles/{id}/clone`, `/api/access-profiles/{id}/propagate`, `/api/access-profiles/{id}/roles`, `/api/access-profiles/{id}/roles/{role_id}`, `/api/access-profiles/{id}/versions`, `/api/access-profiles/{id}/versions/{version}`, `/api/access-profiles/{id}/audit-logs`, `/api/access-profiles/audit-logs`, `/api/users/{target_user_id}/access-profiles`, `/api/users/{target_user_id}/access-profiles/{profile_id}`, `/api/users/{target_user_id}/access-profiles/{profile_id}/virtual-keys`, `/api/users/{target_user_id}/access-profiles/virtual-keys/{vk_id}` |
| Business units | `/api/governance/business-units`, `/api/governance/business-units/{business_unit_id}`, `/api/governance/business-units/{business_unit_id}/teams`, `/api/governance/business-units/{business_unit_id}/teams/{team_id}`, `/api/governance/business-units/{business_unit_id}/customers`, `/api/governance/business-units/{business_unit_id}/customers/{customer_id}`, `/api/governance/business-units/{business_unit_id}/governance`, `/api/governance/customers/{customer_id}/business-units` |
| RBAC | `/api/governance/rbac/roles`, `/api/governance/rbac/roles/{role_id}`, `/api/governance/rbac/roles/{role_id}/permissions`, `/api/governance/rbac/resources`, `/api/governance/rbac/operations`, `/api/governance/rbac/permissions` |
| RBAC (deprecated aliases) | `/api/roles`, `/api/roles/{role_id}`, `/api/roles/{role_id}/permissions`, `/api/resources`, `/api/operations`, `/api/permissions` |
| Users and teams | `/api/governance/users`, `/api/governance/users/{user_id}`, `/api/governance/users/{user_id}/role`, `/api/governance/users/{user_id}/teams`, `/api/governance/users/me/permissions`, `/api/governance/users/email/{email}`, `/api/governance/users/email/{email}/virtual-keys`, `/api/governance/users/{user_id}/virtual-keys`, `/api/governance/teams`, `/api/governance/teams/{team_id}`, `/api/governance/teams/{team_id}/members`, `/api/governance/teams/{team_id}/members/{user_id}` |
| Users and teams (deprecated aliases) | `/api/users`, `/api/users/{user_id}`, `/api/users/{user_id}/role`, `/api/users/{user_id}/teams`, `/api/users/me/permissions`, `/api/users/email/{email}`, `/api/users/email/{email}/virtual-keys`, `/api/users/{user_id}/virtual-keys`, `/api/teams`, `/api/teams/{team_id}`, `/api/teams/{team_id}/members`, `/api/teams/{team_id}/members/{user_id}` |
| Access profiles | `/api/governance/access-profiles`, `/api/governance/access-profiles/{profile_id}`, `/api/governance/access-profiles/{profile_id}/activate`, `/api/governance/access-profiles/{profile_id}/deactivate`, `/api/governance/access-profiles/{profile_id}/clone`, `/api/governance/access-profiles/{profile_id}/propagate`, `/api/governance/access-profiles/{profile_id}/roles`, `/api/governance/access-profiles/{profile_id}/roles/{role_id}`, `/api/governance/access-profiles/{profile_id}/versions`, `/api/governance/access-profiles/{profile_id}/versions/{version}`, `/api/governance/access-profiles/{profile_id}/audit-logs`, `/api/governance/access-profiles/audit-logs`, `/api/governance/users/{user_id}/access-profiles`, `/api/governance/users/{user_id}/access-profiles/{profile_id}`, `/api/governance/users/{user_id}/access-profiles/{profile_id}/virtual-keys`, `/api/governance/users/{user_id}/access-profiles/virtual-keys/{vk_id}`, `/api/governance/users/{user_id}/access-profiles/{profile_id}/budgets/{budget_id}/override` |
| Access profiles (deprecated aliases) | `/api/access-profiles`, `/api/access-profiles/{profile_id}`, `/api/access-profiles/{profile_id}/activate`, `/api/access-profiles/{profile_id}/deactivate`, `/api/access-profiles/{profile_id}/clone`, `/api/access-profiles/{profile_id}/propagate`, `/api/access-profiles/{profile_id}/roles`, `/api/access-profiles/{profile_id}/roles/{role_id}`, `/api/access-profiles/{profile_id}/versions`, `/api/access-profiles/{profile_id}/versions/{version}`, `/api/access-profiles/{profile_id}/audit-logs`, `/api/access-profiles/audit-logs`, `/api/users/{user_id}/access-profiles`, `/api/users/{user_id}/access-profiles/{profile_id}`, `/api/users/{user_id}/access-profiles/{profile_id}/virtual-keys`, `/api/users/{user_id}/access-profiles/virtual-keys/{vk_id}`, `/api/users/{user_id}/access-profiles/{profile_id}/budgets/{budget_id}/override` |
| Logs and analytics | `/api/logs`, `/api/logs/{id}`, `/api/logs/sessions/{session_id}`, `/api/logs/sessions/{session_id}/summary`, `/api/logs/stats`, `/api/logs/filterdata`, `/api/logs/dashboard`, `/api/logs/dropped`, `/api/logs/rankings`, `/api/logs/recalculate-cost`, `/api/logs/recalculate-cost/status`, `/api/logs/histogram`, `/api/logs/histogram/cost`, `/api/logs/histogram/tokens`, `/api/logs/histogram/models`, `/api/logs/histogram/latency`, `/api/logs/histogram/cost/by-provider`, `/api/logs/histogram/tokens/by-provider`, `/api/logs/histogram/latency/by-provider`, `/api/logs/histogram/cost/by-dimension`, `/api/logs/histogram/tokens/by-dimension`, `/api/logs/histogram/latency/by-dimension` |
| MCP logs | `/api/mcp-logs`, `/api/mcp-logs/{id}`, `/api/mcp-logs/stats`, `/api/mcp-logs/filterdata`, `/api/mcp-logs/histogram`, `/api/mcp-logs/histogram/cost`, `/api/mcp-logs/histogram/top-tools` |
| MCP clients and sessions | `/api/mcp/clients`, `/api/mcp/client`, `/api/mcp/client/{id}`, `/api/mcp/client/{id}/reconnect`, `/api/mcp/client/{id}/complete-oauth`, `/api/mcp/sessions`, `/api/mcp/sessions/{id}`, `/api/mcp/sessions/{id}/reauth`, `/api/mcp/per-user-headers/flows/{id}`, `/api/mcp/per-user-headers/credential/{id}` |
Expand All @@ -46,6 +51,7 @@ Use this bearer token for management API endpoints that configure or inspect Bif
| Skills | `/api/skills`, `/api/skills/{id}`, `/api/skills/{id}/versions`, `/api/skills/{id}/shift-version`, `/api/skills/all/version`, `/api/skills/files/upload`, `/api/skills/files/orphans` |
| Cache | `/api/cache/clear/{cacheId}`, `/api/cache/clear-by-key/{cacheKey}` |
| Circuit breaker | `/api/circuit-breaker/policies`, `/api/circuit-breaker/policies/{name}`, `/api/circuit-breaker/state` |
| Audit logs | `/api/audit-logs`, `/api/audit-logs/{id}`, `/api/audit-logs/filterdata`, `/api/audit-logs/export`, `/api/audit-logs/{id}/verify` |
| Audit logs | `/api/governance/audit-logs`, `/api/governance/audit-logs/{id}`, `/api/governance/audit-logs/filterdata`, `/api/governance/audit-logs/export`, `/api/governance/audit-logs/{id}/verify` |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| Audit logs (deprecated aliases) | `/api/audit-logs`, `/api/audit-logs/{id}`, `/api/audit-logs/filterdata`, `/api/audit-logs/export`, `/api/audit-logs/{id}/verify` |
| Webhooks | `/api/webhooks`, `/api/webhooks/{id}`, `/api/webhooks/{id}/deliveries`, `/api/webhooks/{id}/rotate-secret`, `/api/webhooks/{id}/test`, `/api/webhooks/deliveries/{id}/redeliver` |
| Session and vault operations | `/api/session/logout`, `/api/session/ws-ticket`, `/api/vault/flush-cache` |
124 changes: 124 additions & 0 deletions docs/api/procuring-api-keys_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Checks the endpoint table in procuring-api-keys.mdx against the OpenAPI spec.

Run directly: `python3 procuring-api-keys_test.py`. No test framework needed.

The table is hand-maintained prose that enumerates the management surface, so it drifts
silently whenever a route moves. Two things must hold: every path it lists is a real
documented path, and for each governance area it shows BOTH the canonical
`/api/governance/...` route and the legacy alias that still answers, so a reader on either
generation of the API finds what they are calling.
"""

from __future__ import annotations

import re
import sys
from pathlib import Path

import yaml

HERE = Path(__file__).resolve().parent
DOC = HERE / "procuring-api-keys.mdx"
SPEC = HERE.parent / "openapi" / "openapi.yaml"

spec_paths = set((yaml.safe_load(SPEC.read_text(encoding="utf-8")).get("paths") or {}))

# Every `/api/...` in a backtick span inside the table, keyed by the Area cell.
rows: dict[str, set[str]] = {}
for line in DOC.read_text(encoding="utf-8").splitlines():
if not line.startswith("|") or line.startswith("| ---"):
continue
cells = [cell.strip() for cell in line.strip("|").split("|")]
if len(cells) < 2 or cells[0] in ("Area",):
continue
found = set(re.findall(r"`(/api/[^`]+)`", cells[1]))
if found:
rows.setdefault(cells[0], set()).update(found)

listed = {path for paths in rows.values() for path in paths}

# Governance areas that were renamed under /api/governance. Each legacy alias must appear
# in the table alongside its canonical successor.
LEGACY_TO_CANONICAL = {
"/api/roles": "/api/governance/rbac/roles",
"/api/roles/{role_id}": "/api/governance/rbac/roles/{role_id}",
"/api/roles/{role_id}/permissions": "/api/governance/rbac/roles/{role_id}/permissions",
"/api/resources": "/api/governance/rbac/resources",
"/api/operations": "/api/governance/rbac/operations",
"/api/permissions": "/api/governance/rbac/permissions",
"/api/users": "/api/governance/users",
"/api/users/{user_id}": "/api/governance/users/{user_id}",
"/api/users/me/permissions": "/api/governance/users/me/permissions",
"/api/users/{user_id}/role": "/api/governance/users/{user_id}/role",
"/api/users/{user_id}/teams": "/api/governance/users/{user_id}/teams",
"/api/users/email/{email}/virtual-keys": "/api/governance/users/email/{email}/virtual-keys",
"/api/teams": "/api/governance/teams",
"/api/teams/{team_id}": "/api/governance/teams/{team_id}",
"/api/teams/{team_id}/members": "/api/governance/teams/{team_id}/members",
"/api/teams/{team_id}/members/{user_id}": "/api/governance/teams/{team_id}/members/{user_id}",
"/api/access-profiles": "/api/governance/access-profiles",
"/api/access-profiles/{profile_id}": "/api/governance/access-profiles/{profile_id}",
"/api/users/{user_id}/access-profiles": "/api/governance/users/{user_id}/access-profiles",
"/api/audit-logs": "/api/governance/audit-logs",
}
Comment thread
impoiler marked this conversation as resolved.

passed = 0
failed = 0


def check(name, fn):
global passed, failed
try:
fn()
passed += 1
print(f" ok - {name}")
except AssertionError as exc:
failed += 1
print(f" FAIL - {name}\n {exc}")


def test_table_is_not_empty():
assert len(listed) > 100, f"only parsed {len(listed)} endpoints from the table"


def test_every_listed_path_is_documented():
unknown = sorted(path for path in listed if path not in spec_paths)
assert not unknown, f"table lists paths absent from openapi.yaml: {unknown}"


def test_canonical_governance_routes_are_listed():
missing = sorted(
canonical
for canonical in set(LEGACY_TO_CANONICAL.values())
if canonical not in listed
)
assert not missing, f"canonical governance routes missing from the table: {missing}"


def test_legacy_aliases_are_still_listed():
missing = sorted(legacy for legacy in LEGACY_TO_CANONICAL if legacy not in listed)
assert not missing, f"legacy aliases dropped from the table: {missing}"


def test_legacy_rows_are_labelled_as_deprecated():
"""A reader must be able to tell which of the two generations they are looking at."""
unlabelled = set()
for area, paths in rows.items():
legacy_here = paths & set(LEGACY_TO_CANONICAL)
if legacy_here and "deprecated" not in area.lower():
unlabelled.add(area)
assert not unlabelled, (
"areas list legacy aliases without saying so in the Area cell: "
f"{sorted(unlabelled)}"
)


check("the endpoint table parses", test_table_is_not_empty)
check("every path in the table exists in openapi.yaml", test_every_listed_path_is_documented)
check("canonical /api/governance routes are listed", test_canonical_governance_routes_are_listed)
check("legacy aliases are still listed", test_legacy_aliases_are_still_listed)
check("legacy aliases are labelled as deprecated", test_legacy_rows_are_labelled_as_deprecated)

print(f"\n{passed} passed, {failed} failed")
sys.exit(0 if failed == 0 else 1)
Loading
Loading