Skip to content

Admin Dashboard

The admin dashboard is a web UI that wraps the full Admin API. It is served from the same Worker as the API, but on the dedicated admin host:

https://admin.bve.me

The public API remains on https://api.bve.me. The dashboard is operator-facing and is not accessible to regular API key holders. All mutations (key creation, suspension, revocation, model blocking) still flow through the Admin API.


  1. Navigate to https://admin.bve.me/login.

  2. Enter your operator email and password. Enable Remember session to prefill the email field on future visits.

  3. On success, the server issues a bve_admin_session cookie. A fresh CSRF token is also returned and stored in the browser’s module scope.

  4. The dashboard redirects you to the overview page at /.

PropertyValue
Cookie namebve_admin_session
FlagsHttpOnly, Secure, SameSite=Lax, Path=/
TTL7 days (sliding — extended every 15 minutes on activity)
Token storageSHA-256 hash stored in D1; raw token only in the cookie
LogoutSoft-revokes the session and records an admin.logout audit event

Sessions are visible on the Settings → Operator Profile page. You can revoke individual sessions or sign out all other devices from there.

The login endpoint enforces a limit of 5 failures per email address or per IP address within a 15-minute window. A 429 response includes a Retry-After header.

Operators can change their password from Settings → Operator Profile. Changing the password does not invalidate existing sessions.

To reset a forgotten password, an owner must run the CLI command:

Terminal window
bun run admin:reset-password --remote

Three roles control what operators can do.

CapabilityOwnerAdminViewer
Create / rotate / revoke API keys
Suspend / unsuspend API keys
Block / enable models (allowlist)
Purge in-memory caches
Send webhook test events
View request logs, usage, audit logs
View analytics charts
Create / delete operators
Change other operators’ roles

Viewers have read-only access and see no mutating action buttons.


The sidebar is divided into four sections.

Core Dashboard

  • Overview — KPI cards and traffic chart
  • Usage Analytics — analytics charts and model breakdowns

Access Control

  • API Keys — key management and quota inspection
  • Operators — operator accounts and roles

System Metrics (visible but unrelated endpoints may be hidden for viewers)

  • Request Logs — sampled request log explorer
  • Model Catalog — live Fuelix catalog and global policy controls
  • Diagnostics — public API health, drift checks, and local audit runbooks

Configuration

  • Audit Journal — operator and key lifecycle events
  • Settings & Security — profile, team, gateway config, danger zone
ShortcutAction
Cmd K / Ctrl KOpen command palette
EscClose any open panel or dialog

The command palette provides quick navigation to every page and a Purge Cache action.

  • Search — type to filter sidebar links instantly
  • Pinned views — hover any link and click the star to pin it at the top of the sidebar; pins persist to localStorage
  • History — the sidebar shows your last three visited pages below the nav groups

The overview (/) gives a live health summary.

CardDetails
Active API KeysCount of active keys; expired keys shown separately as an alert
Monthly RequestsProgress bar against the hard cap; shows month-over-month delta
Monthly TokensTotal token volume vs. prior month
D1 DatabaseHealth check for the D1 database

Stacked bar chart showing successful requests (teal) and errors (rose) per hour over the last 24 hours. Shows total requests, error rate, and peak hour. Refreshes every 60 seconds; a manual refresh button is available.

The dashboard derives alerts from live data. No configuration is required. Alerts fire for:

  • DB health failure
  • Suspended keys
  • Expired keys (status active but past expires_at)
  • Keys expiring within 7 days
  • Monthly hard cap reached (503 enforcing)
  • Soft cap exceeded or at ≥ 80%
  • Monthly request quota warning (key at 80–99% of monthly_limit)
  • Monthly request quota exceeded (key at ≥ 100% of monthly_limit)
  • Monthly token quota warning (key at 80–99% of monthly_token_limit)
  • Monthly token quota exceeded (key at ≥ 100% of monthly_token_limit)

The bell icon in the top header shows an unread count (capped at 9+). Notifications cover:

Notification typeTrigger
key_expiring_soonKey expires within the next 7 days
key_expiredKey has passed its expires_at (status still active)
key_quota_warningKey has used 80–99% of its monthly request limit
key_quota_exceededKey has used ≥ 100% of its monthly request limit
key_token_quota_warningKey has used 80–99% of its monthly token limit
key_token_quota_exceededKey has used ≥ 100% of its monthly token limit

Notifications are generated by the daily cron handler (0 3 * * *) and delivered via the EVENTS_QUEUE. They appear in the bell on the next dashboard load after the cron fires.

Clicking any notification deep-links to the API Keys page with that key’s Quota Inspector pre-opened.


The API Keys page (/api-keys) is the primary key management interface.

  • Status tabs — All / Active / Suspended / Revoked / Expired, each with a live count badge.
  • Debounced search — filters by key name (server-side, 500 ms debounce).
  • CSV export — exports the current filtered set as a CSV file.
  • Pagination — 50 rows per page.
ColumnDetails
Key Name & IDFriendly name and UUID
StatusAnimated badge (active / suspended / revoked / expired)
Last UsedRelative timestamp
LimitsRPM / RPD caps
Monthly CapsMini progress bars for requests and tokens; amber at 80%, rose at 95%
Allowed ModelsBadge list, or a gateway-policy label explaining that the key follows the gateway’s live/global model policy when no per-key allowlist is set
CreatedCreation date
ExpiresUTC-aware status badge and detail line; exact expiry timestamps stay exact, while UTC end-of-day expiries render as a UTC date

Check one or more non-revoked keys to reveal a floating toolbar:

  • Suspend N — calls POST /admin/api-keys/bulk-suspend
  • Unsuspend N — calls POST /admin/api-keys/bulk-unsuspend
  • Revoke N — shows a confirmation dialog, then calls POST /admin/api-keys/bulk-revoke

Select-all picks every non-revoked key on the current page.

Right-click the actions dropdown on any row (owner / admin only):

ActionDescription
Inspect Live QuotaOpens the real-time Quota Inspector modal
View Request LogsNavigates to /transactions?keyId=<id>
Duplicate KeyClones the config and generates a new raw key
Adjust ConfigurationOpens the edit dialog
Suspend / UnsuspendToggles based on current status
Reset Usage WindowOpens a dialog to reset minute / day / month / all quota counters
Rotate SecretReplaces the key material; the new raw key is shown once
Revoke PermanentlyConfirmation dialog, then irrevocable

Click Create API Key and fill in:

  • Name — descriptive label (required)
  • RPM limit — requests per minute cap (null = no limit)
  • RPD limit — requests per day cap (null = no limit)
  • Monthly request cap — monthly request ceiling (null = no limit)
  • Monthly token cap — monthly token ceiling (null = no limit)
  • Allowed models — multi-select from the global catalog (null = allow all)
  • Expiry date — ISO 8601 date, clearable (null = never expires)

On success, the raw sk-bve-… key is displayed exactly once in a reveal dialog with a copy button. It cannot be recovered later — only rotated.

Open via Inspect Live Quota or by visiting /api-keys?keyId=<uuid>. The modal polls every 5 seconds.

It displays:

  • RPM gauge (used / limit, remaining slots, next reset)
  • RPD gauge
  • Monthly request cap gauge (if set)
  • Monthly token cap gauge (if set)
  • 7-day request history bar chart
  • 24-hour activity stacked bar chart
  • Performance Analytics with date tabs (7d / 30d / 90d / All): request count, error rate, 4xx / 5xx counts, avg / p50 / p95 / max latency, total tokens, top models bar chart

The Request Logs page (/transactions) is a sampled request log explorer. The log is a 1% sample of all gateway requests — it is not a full audit trail.

  • Total Logged — request count in the filtered window
  • Avg Latency — with p50 / p95 sub-text
  • Token Volume — total prompt + completion tokens
  • Error Rate — 4xx / 5xx breakdown; highlighted red if > 5%
FilterOptions
StatusAll / 2xx / 4xx / 5xx
Date RangeFrom / to date pickers
ModelFree-text, Apply button
KeyDropdown grouped as Active/Suspended vs. Revoked
Request IDUUID input, Go button (exact lookup)
Error CodeQuick-select buttons for common codes + free-text
Latency RangeMin / max ms
Upstream Latency RangeMin / max ms upstream (Fuelix) time

Deep-links are supported: /transactions?model=gpt-4o, /transactions?keyId=<uuid>, /transactions?requestId=<uuid>.

Click any row to open a slide-in detail panel:

  • Endpoint path, HTTP status, and error code
  • Latency, tokens, model, and API key name
  • Upstream vs. gateway overhead progress bar
  • Token breakdown (prompt / completion / total)
  • Request ID with a copy button
  • cURL Replay — auto-generated curl command for the endpoint and model, with a copy button

Press Esc or click the close button to dismiss.

Exports all rows matching the current filters, not just the current page.


The Usage page (/usage) shows aggregate usage trends.

  • Key filter — aggregate all keys or isolate one
  • Compare to prior period checkbox — overlays a dashed line for the previous period with a delta % badge
  • Date range tabs — 7d / 30d / 90d
SectionDescription
Daily SnapshotsLine chart of requests + tokens per day
Monthly AggregatesGrouped bar chart
Top Models by UsageSortable: requests, tokens, latency, error rate. Clicking a row navigates to /transactions?model=<id>
Response Status DistributionHorizontal bars: 2xx success / 4xx client errors / 5xx server errors
Requests by EndpointBar chart and table per /v1/* path
Top Keys by UsageSortable leaderboard. Clicking navigates to /transactions?keyId=<id>
Recent Sampled RequestsLast N requests with endpoint, model, status, latency, tokens

The Models page (/models) compares the live Fuelix catalog for the current account with the gateway’s persistent D1 policy overrides.

The page is backed by the session-authenticated dashboard endpoint GET /api/models, which returns:

{
"catalogAvailable": true,
"models": [],
"policyModels": []
}
FieldMeaning
catalogAvailabletrue when the live Fuelix /models request succeeded; false when the page is operating in degraded mode
modelsLive Fuelix catalog rows for the current account
policyModelsPreserved D1 allowlist rows that are not represented in models; when catalogAvailable=false, all preserved allowlist rows move here for review

The row-level available flag means effective BVE routability after D1 global block policy is applied, not simply “Fuelix listed this model”. Live Fuelix-listed unclassified rows now remain routable by default, while listed_unavailable, registry_broken, and non-live rows remain blocked.

The dashboard no longer emits a separate snapshotModels array. Local registry rows that are absent from the live Fuelix catalog only surface here when a preserved D1 policy row still exists for the same model, in which case they appear in policyModels with a stale policy availability such as allowlist_only.

The page also performs a separate admin-only public drift probe through GET /api/public-api-catalog-probe when an optional server-side BVE_API_KEY binding is configured for the dashboard Worker. That probe compares the public https://api.bve.me/v1/models response seen by the configured user key with the dashboard’s current live Fuelix-backed catalog, normalised to that same key’s allowed_models restrictions so per-key filtering is not mistaken for deploy drift.

availabilityMeaning
callableFuelix lists the model and the gateway treats it as callable by default on the current account
listed_unavailableFuelix lists the model, but live account checks still fail; the gateway blocks it even if a D1 allow entry exists
registry_brokenThe model is intentionally classified as broken in the local registry
unclassifiedFuelix lists the model, but the local registry has not yet classified it
allowlist_onlyA preserved D1 policy row that does not appear in the latest live Fuelix catalog
allowlist_unverifiedFuelix is currently unavailable, so the preserved D1 policy row cannot yet be confirmed as live or stale

Above the main table, the page shows six summary cards:

  • Fuelix Catalog — live catalog row count, or when Fuelix is unavailable
  • Verified Callable — models with availability: "callable" in the current local snapshot
  • Listed / Failing — live catalog rows that still fail account-level checks
  • Policy Rows — preserved D1 rows that are either stale (allowlist_only) or temporarily unverified (allowlist_unverified)
  • Global Blocks — models with an explicit D1 block (globallyEnabled: false)
  • Active — sampled request volume for the selected stats window

When the dashboard build and the public api.bve.me deploy drift apart, the page shows inline warnings that call out:

  • the stale public deploy tag
  • any /health header mismatch (for example missing Cross-Origin-Opener-Policy: same-origin)
  • public /v1/models catalog drift, including missing live rows and stale deployments that still omit bve_available / bve_availability
  • representative public /v1/models/:id detail-route drift, including stale callable-model detail annotations and blocked-model routes that still return an upstream-style 404 instead of the gateway’s 403 model_not_available

If the optional dashboard BVE_API_KEY binding is absent, the page degrades to health-only public warnings and explicitly says that the public catalog probe is not configured.

The Operator Queue groups the most actionable rows:

  • Live Probe Failures — Fuelix-listed models with captured direct account-probe failures such as authorization problems, upstream-missing rows, or adapter breakage
  • Traffic On Unavailable — sampled traffic landing on models that are not callable by default
  • Registry Exceptions — live Fuelix rows that are still unclassified locally or intentionally marked broken
  • Policy Cleanup Queue or Policy Review Queue — preserved policy rows that need cleanup, or temporary review while Fuelix is down
  • Live Global Blocks — live catalog models that are blocked gateway-wide and still have sampled traffic

All / Chat / Reasoning / Embedding / Image / TTS / Transcription / OCR / Not Default Callable, each with a count badge.

The page also supports free-text search plus provider and availability filters. The Policy Rows availability filter intentionally matches both allowlist_only and allowlist_unverified. There is no standalone snapshot-excluded filter in this surface because non-live local registry rows without preserved policy state are not emitted by GET /api/models.

ColumnDetails
Model IDMonospace, selectable
Categorychat / embedding / image / etc.
CapabilitiesBadge list
Endpoints/v1/chat/completions, /v1/embeddings, etc.
Origin ProviderDerived from model ID prefix
RequestsSortable
Avg LatencySortable
p50 LatencySortable median latency
p95 LatencySortable tail latency
Err%Sortable
Health / PolicyHealth badge (Callable, Listed / Failing, Broken, Unclassified, or policy-row maintenance states) plus policy badge (Policy Open / Global Block)
ActionBlock / Enable button (owner / admin only)

Only live catalog rows from models appear in this table. Preserved D1-only rows from policyModels are rendered in a separate review section below so stale policy entries never pollute the current Fuelix catalog view.

The dashboard session API writes to /api/model-allowlist, which uses the same payload shape as POST /admin/model-allowlist. New writes must reference exact live Fuelix model IDs; stale policy-only rows are cleanup-only and are removed via DELETE instead of being re-posted:

  • Block sends {"model": "<id>", "enabled": false}
  • Enable sends {"model": "<id>", "enabled": true}

Changes take effect within 30 seconds as the in-memory allowlist cache expires.

Rows from policyModels render in a separate section below the live table:

  • When catalogAvailable=true, the section is labeled Policy Cleanup Queue and rows use availability: "allowlist_only". These are stale D1 entries that no longer map to any live Fuelix row.
  • When catalogAvailable=false, the section is labeled Policy Review Queue and rows use availability: "allowlist_unverified". These rows stay visible so operators can review prior overrides, but they cannot yet be treated as stale cleanup entries.

Each policy row can be removed entirely. The underlying Admin API mutation is DELETE /admin/model-allowlist/:model.

When Fuelix is unavailable, the main live catalog table is empty until the upstream catalog recovers; only the preserved policy review queue remains visible.

If a stale policy row originally came from a local snapshot_excluded model, that context is still preserved in the row’s default availability metadata even though the dashboard no longer renders a separate snapshot-only collection. The policy queue and inspector surface that preserved default-availability context so operators can tell whether the stale row last looked callable, broken, snapshot-excluded, listed-but-failing, or unknown.

Click any row to open a detail panel with:

  • Category, provider label, full model ID, effective routing state, and policy state
  • Capabilities and supported BVE endpoints
  • Usage stats (requests, tokens, latency percentiles, error rate, token breakdown)
  • Endpoint distribution with relative progress bars
  • Block / Enable toggle for live catalog rows (owner / admin only)
  • Remove policy entry action for policyModels rows
  • “View Request Logs for this model” deep-link

The Audit Logs page (/audit-logs) shows operator and key lifecycle events recorded by the gateway.

All Logs / Sessions (auth) / API Keys / Models / Operators

ColumnDetails
TimestampUTC, relative label
ActionColor-coded badge: create=emerald, suspend=amber, revoke=rose, login=indigo, logout=purple
Actor Typeadmin or system
Target ResourceUUID or model ID (monospace)
ActorEmail + IP address (from metadata)
Raw EntryJSON metadata preview, truncated to 120 chars

Export as CSV respects the active filters.


The Operators page (/users) is visible to all roles but mutating actions are owner-only.

  • Change role (viewer → admin → owner)
  • Disable / activate the account
  • Delete the account permanently

Opens a dialog with fields: name, email, password (min 8 chars, toggleable visibility), and role (viewer / admin / owner).


  • Current operator info (name, email, role, status)
  • Session cookie details
  • Change Password form (does not invalidate existing sessions)
  • Active Sessions table — shows device, IP, sign-in time, last active, and expiry for each session. Per-session Revoke and bulk “Sign out other devices” buttons.
  • Live operator accounts table from D1
  • Role Permissions Matrix — full table of all capabilities vs. owner / admin / viewer
  • API base URL and an OpenAI SDK snippet pre-populated with the configured URL
  • Webhook integration status (WEBHOOK_URL and WEBHOOK_SECRET) and a Send Test Event button
  • Global Rate Limits (soft cap and hard cap from wrangler.jsonc vars)
  • Purge Gateway Caches — type PURGE CACHES exactly to enable the button. Clears in-memory caches (model allowlist cache, monthly token totals cache). Keys, quotas, and logs are unaffected.

MechanismImplementation
TransportHTTPS only (api.bve.me is TLS-terminated at Cloudflare)
CSRF protectionx-csrf-token header required on all mutating API endpoints; compared with constantTimeEqual
Session storageSHA-256 hashed tokens in D1; raw token only in HttpOnly cookie
Password hashingPBKDF2 with a per-password salt; iteration count within Workers Web Crypto limits
Timing attack defenceSentinel hash always computed even when email is not found
Login rate limit5 failures per email/IP within 15 minutes
IP extractionCF-Connecting-IP preferred over X-Forwarded-For
Audit trailEvery login, logout, key change, and model change is recorded in the audit log

See Security Notes for the full security architecture of the gateway.