API Dashboard
Centralized dashboard for monitoring external API services with interactive API explorer, provider visibility management, enriched provider cards, and encrypted key management
API Dashboard
The API Dashboard (v3.0.0) provides a comprehensive view of every external API service integrated into Arcturus-Prime. It shows configuration status, validates keys, pulls live usage data, provides an interactive API explorer for executing endpoints, and lets you hide unused providers.
Located at /admin/api-dashboard. Admin-only.
Features
Instructions Banner
A brief explanation at the top of the page describes how the dashboard works. Below it, a color-coded legend explains the four status dot states: green (valid key), yellow (checking), red (invalid/expired), grey (not configured). This is always visible so users do not need external documentation to interpret the page.
Summary Bar
Four counters across the top of the page:
| Counter | Meaning |
|---|---|
| Total | Number of providers defined in config |
| Configured | Providers with an env var set |
| Free | Providers on a free tier |
| Paid | Providers on a paid or pay-as-you-go tier |
Provider Cards
Each provider renders as a card grouped by category. Cards display:
- Status indicator (colored dot)
- Provider name, icon, and description
- Pricing tier badge (free / paid / payg) and endpoint count badge
- API base URL in monospace
- Feature tags (e.g. “90+ models”, “Streaming”, “Tool use”)
- Rate limit info
- Free tier details or free model list
- Live usage data (if the provider supports it)
- Direct links to provider website, API docs, dashboard, and billing page
Card Actions
Each card has action buttons:
| Button | Icon | Action |
|---|---|---|
| Explore | Terminal | Opens the inline API Explorer panel |
| Docs | Book | Opens the provider’s API documentation (external link) |
| Hide | Eye-slash | Hides the provider from the dashboard |
| Configure | Gear | Opens the API key management modal |
Auto-Fetch and Refresh
Usage data is fetched automatically when the page loads. A manual refresh button in the header allows re-fetching at any time. All provider checks run in parallel with an 8-second timeout per provider. Providers without usage APIs receive a key validity check instead.
Hide / Show Providers
Click the eye-slash icon on any card to hide it from the dashboard. Hidden providers are stored in Cloudflare KV (data:api-dashboard:hidden-providers) and filtered at SSR time.
A visibility dropdown in the page header (eye-slash icon with badge count) lists all hidden providers. Click “Show” next to any provider to restore it.
API Explorer
Click “Explore” on any provider card to open an inline explorer panel below the card’s section. The explorer shows all available API endpoints for that provider, grouped by category:
| Category | Description |
|---|---|
| Account | Account info, balances, subscription details |
| Models | List available models |
| Usage | Usage records, credit balance, generation history |
| Content | Search, domains, zones, calls, emails |
| Health | Service health checks |
Each endpoint button shows:
- HTTP method badge (GET = green, POST = blue)
- Endpoint name and description
- Documentation link (external)
Click any endpoint to execute it through the secure server-side proxy. The response panel shows:
- HTTP method and URL (with any API keys stripped)
- Status code (color-coded: green = success, red = error)
- JSON response with syntax highlighting (keys = purple, strings = green, numbers = amber, booleans = blue, null = gray)
- Request timing and response size
- Copy button for the raw JSON
Provider Categories
Twelve providers ship in the default configuration across five categories:
| Category | Provider | Env Var | Tier | Free Tier Info | Live Usage |
|---|---|---|---|---|---|
| AI / LLM | OpenRouter | OPENROUTER_API_KEY | payg | DeepSeek R1, Llama 4, Qwen3, Nemotron free models | Yes |
| Google Gemini | GEMINI_API_KEY | free | Gemini 2.0 Flash (1500 RPD), 1M context | No | |
| Groq | GROQ_API_KEY | free | Llama 3.3 70B, Whisper Large v3 (30 RPM) | No | |
| NVIDIA NIM | NVIDIA_NIM_API_KEY | free | 1000 free API calls on signup | No | |
| Anthropic | ANTHROPIC_API_KEY | paid | — | No | |
| OpenAI | OPENAI_API_KEY | paid | — | No | |
| Voice | ElevenLabs | ELEVENLABS_API_KEY | free | 10K chars/mo | Yes |
| Search | Brave Search | BRAVE_API_KEY | free | 2K queries/mo | No |
| Perplexity | PERPLEXITY_API_KEY | paid | — | No | |
| Communication | Twilio | TWILIO_ACCOUNT_SID | payg | — | Yes |
| Resend | RESEND_API_KEY | free | 3K emails/mo, 100/day | No | |
| Infrastructure | Cloudflare | CF_API_TOKEN | free | Workers 100K req/day, Pages 500 builds/mo | No |
Twilio requires three env vars: TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_FROM_NUMBER. Cloudflare also uses CF_ACCOUNT_ID.
Live Usage Data
Three providers return real-time usage information from their APIs. All others perform a key validity check only (HTTP request to the provider’s models or verify endpoint).
OpenRouter
Calls two endpoints in parallel:
GET /api/v1/auth/key— returns limit, usage, rate limits, free tier statusGET /api/v1/credits— returns total_credits and total_usage (requires management key)
| Field | Source | Description |
|---|---|---|
credits | /credits | Account balance (totalCredits - totalUsage) |
totalCredits | /credits | Total credits purchased |
totalUsage | /credits | Total credits spent |
limitRemaining | /auth/key | Spending cap remainder (limit - usage) |
usage | /auth/key | Usage against spending limit |
limit | /auth/key | Spending cap |
isFreeTier | /auth/key | Whether the key is on the free tier |
rateLimitRequests | /auth/key | Requests per interval |
rateLimitInterval | /auth/key | Rate limit window |
Note: credits (balance) and limitRemaining are different values. Credits is your actual deposited balance from /api/v1/credits. Limit remaining is how much of your spending cap is left from /api/v1/auth/key. The /api/v1/credits endpoint requires a management API key — if a regular key is used, the balance fields will be unavailable.
ElevenLabs
Calls GET /v1/user/subscription with the xi-api-key header.
| Field | Description |
|---|---|
tier | Subscription tier name |
characterCount | Characters used this period |
characterLimit | Character quota for the period |
voiceCount | Voice slot limit |
nextResetUnix | Unix timestamp of next quota reset |
The dashboard renders a progress bar for character usage against the limit.
Twilio
Calls the Account and Balance REST APIs with Basic auth.
| Field | Description |
|---|---|
status | Account status (active, suspended, closed) |
type | Account type |
friendlyName | Account display name |
balance | Current balance amount |
currency | Balance currency code |
Custom Providers
Admins can add custom API providers directly from the dashboard UI without editing config files or redeploying.
Adding a Custom Provider
Click the + button in the dashboard header. Fill in the modal form:
| Field | Required | Description |
|---|---|---|
| Provider Name | Yes | Display name (e.g. “Replicate”) |
| Env Var Name | Yes | Environment variable holding the API key |
| Category | Yes | Which section the card appears in |
| Tier | No | Free, Paid, or Pay-as-you-go |
| Description | No | Short description |
| Pricing Note | No | Pricing info shown on the card |
| Dashboard URL | No | Link to provider’s management console |
| Billing URL | No | Link to provider’s billing page |
| Icon | No | Font Awesome class (defaults to fa-plug) |
| Validation URL | No | URL to check key validity (hit with the key as auth) |
| Auth Type | No | Bearer Token or X-API-Key header |
Storage
Custom providers are stored in Cloudflare KV at key data:api-dashboard:custom-providers. The page fetches them at SSR time and merges with the static config. The API usage endpoint also checks custom providers alongside built-ins.
Editing and Deleting
Custom provider cards show edit (pencil) and delete (trash) buttons. Click edit to modify any field. Delete requires confirmation. Built-in providers cannot be edited or deleted from the UI — modify config.local.json instead.
Limitations
Custom providers only get generic key validity checks (hit a URL, check for HTTP 200). They do not get live usage data like OpenRouter credits or ElevenLabs character counts. Provider IDs cannot collide with built-in provider IDs.
Configuration
Provider definitions live in JSON config files in the module repo root.
config.default.json
Ships with the module. Contains all 12 default providers with their metadata: name, description, icon, category, tier, env var(s), free tier info, pricing notes, and dashboard/billing URLs. Do not edit this file — it gets overwritten on module updates.
config.local.json
Created automatically by install.sh as a copy of config.default.json. This file is gitignored. Edit it to:
- Remove providers you do not use
- Override descriptions, icons, or pricing notes
- Change category assignments
The dashboard reads config.local.json if it exists, falling back to config.default.json. The install script copies the chosen config into Arcturus-Prime as a JSON file that gets bundled by Vite at build time. For adding new providers at runtime, use the Custom Providers feature instead.
Installation
The API Dashboard lives in a standalone module repo (Arcturus-Prime-module-api-dashboard), separate from the main Arcturus-Prime codebase. The installer copies files into Arcturus-Prime because Astro/Vite does not follow symlinks for relative imports.
Install
cd ~/Development/Arcturus-Prime-module-api-dashboard
./install.sh
This copies five files into Arcturus-Prime:
| Source | Destination | Purpose |
|---|---|---|
src/pages/admin/api-dashboard.astro | Same path in Arcturus-Prime | Dashboard page |
src/pages/admin/_api-dashboard-config.json | Same path in Arcturus-Prime | Provider config (Vite JSON import) |
src/config/modules/api-dashboard.ts | Same path in Arcturus-Prime | Module registration |
src/pages/api/admin/api-usage.ts | Same path in Arcturus-Prime | Status check endpoint |
src/pages/api/admin/api-dashboard-providers.ts | Same path in Arcturus-Prime | Custom provider CRUD endpoint |
The config file is copied from config.local.json (if it exists) or config.default.json. The underscore prefix ensures Astro does not create a route for it. The .astro page imports it as a standard JSON module, which Vite bundles at build time — this ensures it works on Cloudflare Workers where there is no filesystem at runtime.
The install script also creates config.local.json from config.default.json if it does not already exist. Run npm run blueprint afterward to update the site blueprint.
Uninstall
cd ~/Development/Arcturus-Prime-module-api-dashboard
./uninstall.sh
Removes the five copied files from Arcturus-Prime. Run npm run blueprint afterward.
Updating
Re-run install.sh after editing module source files or config. It overwrites the copies in Arcturus-Prime.
Status Indicators
Each provider card shows a colored status dot:
| Color | Meaning |
|---|---|
| Green | Key is configured and validated successfully |
| Yellow | Key is configured but has not been checked (or check was inconclusive) |
| Red | Key is configured but validation failed (invalid key or provider error) |
| Grey | No env var set — provider is not configured |
Security
The API endpoint requires Cloudflare Access admin authentication via validateAdmin() — the same pattern used by all other admin API routes. Unauthenticated requests receive a 401 response. No API keys or secrets are ever included in the response — only configuration status, key validity booleans, and usage metrics.
The dashboard page itself is served under /admin/ which is blocked by middleware for non-Arcturus-Prime.com hosts, and protected by Cloudflare Access in production.
API Endpoint
GET /api/admin/api-usage
Returns the status of all configured providers. Requires Cloudflare Access admin authentication (validateAdmin). Returns 401 if not authenticated.
Response:
{
"providers": [
{
"id": "openrouter",
"configured": true,
"valid": true,
"usage": {
"credits": 9.25,
"totalCredits": 12.50,
"totalUsage": 3.25,
"limitRemaining": 46.75,
"usage": 3.25,
"limit": 50,
"isFreeTier": false,
"rateLimitRequests": 200,
"rateLimitInterval": "10s"
}
},
{
"id": "gemini",
"configured": true,
"valid": true
},
{
"id": "anthropic",
"configured": false,
"valid": null
}
],
"fetchedAt": "2026-02-25T18:30:00.000Z"
}
Each provider object contains:
| Field | Type | Description |
|---|---|---|
id | string | Provider identifier (matches config key) |
configured | boolean | Whether the env var is set |
valid | boolean or null | Key validity (null if unchecked or errored) |
usage | object (optional) | Live usage data (only for providers with usage APIs) |
error | string (optional) | Error message if the check failed |
All provider checks run via Promise.allSettled — one failing provider does not block the others. Each check has an 8-second timeout. Custom providers from KV are checked alongside built-in providers.
GET /api/admin/api-dashboard-providers
Returns all custom providers stored in KV. Requires admin auth.
POST /api/admin/api-dashboard-providers
Add or update a custom provider. Body must be JSON with at minimum id, name, envVar, and category. Returns { ok: true, id } on success. Returns 400 if validation fails (e.g. id collides with a built-in provider).
DELETE /api/admin/api-dashboard-providers?id=xxx
Remove a custom provider by id. Returns { ok: true } on success, 404 if not found.
API Key Management
API keys can be configured directly from the dashboard UI without editing Worker Secrets or redeploying.
How It Works
Each provider card has a Configure button (gear icon). Clicking it opens a key configuration modal showing:
- Current key status (configured via KV, Worker Secret, or not configured)
- Masked current value (prefix + last 4 chars)
- Password input for entering a new key value
- Save button that encrypts and stores the key, then validates it against the provider API
Multi-Key Providers
Some providers require multiple credentials:
| Provider | Keys |
|---|---|
| Twilio | TWILIO_ACCOUNT_SID + TWILIO_AUTH_TOKEN + TWILIO_FROM_NUMBER |
| Cloudflare | CF_API_TOKEN + CF_ACCOUNT_ID |
The modal renders a separate input field for each required key.
Encryption
Keys are encrypted at rest using AES-256-GCM via the Web Crypto API.
| Detail | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key source | API_KEYS_ENCRYPTION_KEY env var (64-char hex = 32 bytes) |
| IV | 12-byte random per encryption |
| Storage format | base64(iv || ciphertext) |
| KV key | data:api-keys |
| Generate key | openssl rand -hex 32 |
The encryption key itself must be set as a Worker Secret — it cannot be stored in KV (circular dependency).
Key Resolution (getEnv Fallback Chain)
When any part of Arcturus-Prime calls getEnv("SOME_API_KEY"), the resolution order is:
- KV-stored keys (user-managed via admin UI) — highest priority
- Cloudflare Worker Secrets (set via
wrangler secret put) - process.env (dev mode, loaded from
.envby Vite)
UI-managed keys take precedence over Worker Secrets. This allows overriding secrets without redeployment.
The KV keys are decrypted once per Worker isolate via loadKvApiKeys() (called from middleware) and cached in memory for the isolate’s lifetime.
Validation on Save
When a key is saved, the API endpoint validates it against the provider’s API before responding. Validators exist for:
| Provider | Validation Endpoint |
|---|---|
| OpenRouter | GET /api/v1/auth/key |
| Groq | GET /openai/v1/models |
| NVIDIA NIM | GET /v1/models |
| Anthropic | GET /v1/models |
| OpenAI | GET /v1/models |
| Brave Search | GET /res/v1/web/search |
| Resend | GET /domains |
Each validation has an 8-second timeout. Providers without a validator return valid: null.
Setup Guide
A setup guide panel appears on the dashboard when API_KEYS_ENCRYPTION_KEY is not configured. It warns that key management requires the encryption key and provides the openssl rand -hex 32 command.
API Endpoints (Key Management)
GET /api/admin/api-keys
Returns masked key status for all known env vars. Requires admin auth.
Response:
{
"keys": {
"OPENROUTER_API_KEY": { "masked": "sk-or-...x1y2", "source": "kv" },
"GEMINI_API_KEY": { "masked": "AIza...abcd", "source": "env" },
"ANTHROPIC_API_KEY": { "masked": null, "source": null }
},
"encryptionConfigured": true
}
| Field | Description |
|---|---|
masked | Prefix + last 4 chars, or null if not configured |
source | "kv" (UI-managed), "env" (Worker Secret), or null |
encryptionConfigured | Whether API_KEYS_ENCRYPTION_KEY is set |
POST /api/admin/api-keys
Save a key (encrypted in KV). Validates against provider API after saving.
Request body: { "envVar": "OPENROUTER_API_KEY", "value": "sk-or-..." }
Response: { "ok": true, "valid": true, "masked": "sk-or-...x1y2" }
| Status | Condition |
|---|---|
| 200 | Key saved (check valid field for validation result) |
| 400 | Missing fields or encryption key not configured |
| 503 | KV unavailable |
DELETE /api/admin/api-keys?envVar=OPENROUTER_API_KEY
Remove a key from KV storage. Falls back to Worker Secret if one exists.
| Status | Condition |
|---|---|
| 200 | { ok: true } |
| 400 | Missing envVar or encryption key not configured |
| 404 | Key not found in KV |
Source Files (Key Management)
| File | Purpose |
|---|---|
src/lib/key-encryption.ts | AES-256-GCM encrypt/decrypt via Web Crypto API |
src/pages/api/admin/api-keys.ts | GET status, POST save, DELETE remove |
src/lib/runtime-env.ts | getEnv() fallback chain, loadKvApiKeys(), getEnvDirect() |
src/middleware.ts | Calls loadKvApiKeys() after setRuntimeEnv() on every request |
src/pages/admin/_api-dashboard-config.json | Provider definitions with signupUrl, instructions per provider |
API Explorer
How It Works
The API Explorer lets you execute any available API endpoint for a provider directly from the dashboard. Click “Explore” on a card to open an inline explorer panel.
Requests are proxied through the server (/api/admin/api-proxy) so credentials are never exposed to the browser. Only endpoints defined in the static config’s endpointMap can be executed (whitelist enforcement).
Endpoint Map
28 endpoints are defined across 12 providers in the config’s endpointMap:
| Provider | Endpoints | Categories |
|---|---|---|
| OpenRouter | List Models, Key Info, Credit Balance | models, account, usage |
| Gemini | List Models | models |
| Groq | List Models | models |
| NVIDIA NIM | List Models, Health Check | models, health |
| Anthropic | List Models | models |
| OpenAI | List Models | models |
| ElevenLabs | Subscription Info, List Voices, Generation History | account, content, usage |
| Brave | Web Search Test | content |
| Perplexity | (none) | |
| Twilio | Account Info, Balance, Usage Records, Recent Calls | account, usage, content |
| Resend | List Domains, Recent Emails | account, content |
| Cloudflare | Verify Token, List Zones, List Accounts | account, content |
Each endpoint definition includes: id, method, path, baseUrl, name, category, description, docsUrl, and optional pathParams and bodyTemplate.
Auth Patterns
The proxy dispatches authentication based on the provider’s authPatterns config entry:
| Auth Type | How It Works | Providers |
|---|---|---|
bearer | Authorization: Bearer <key> | OpenRouter, Groq, NVIDIA, OpenAI, Perplexity, Resend, Cloudflare |
x-api-key | Custom header name | Anthropic (x-api-key + anthropic-version: 2023-06-01) |
custom-header | Arbitrary header name | ElevenLabs (xi-api-key), Brave (X-Subscription-Token) |
query-param | Appends ?key=<value> to URL | Gemini |
basic | HTTP Basic auth (user:password) | Twilio (SID:AuthToken) |
Path Parameters
Some endpoints have dynamic path segments (e.g. Twilio’s /2010-04-01/Accounts/{AccountSid}/Balance.json). Path parameters use two sources:
env:VAR_NAME— auto-substituted from server environment (e.g.env:TWILIO_ACCOUNT_SID)input:key— would prompt the user (not yet implemented in the UI)
POST /api/admin/api-proxy
Server-side API proxy. POST-only, requires admin auth.
Request body:
{
"endpointId": "openrouter-models",
"pathValues": {},
"requestBody": null
}
Response:
{
"ok": true,
"status": 200,
"statusText": "OK",
"data": { "...parsed JSON response..." },
"truncated": false,
"endpoint": {
"id": "openrouter-models",
"method": "GET",
"url": "https://openrouter.ai/api/v1/models"
},
"fetchedAt": "2026-02-27T23:00:00.000Z"
}
| Field | Description |
|---|---|
ok | Whether the upstream API returned a 2xx status |
status | HTTP status code from upstream |
data | Parsed JSON response (null if not parseable) |
raw | Raw text response (only if JSON parsing failed) |
truncated | Whether the response was truncated at 512KB |
endpoint | Echo of endpoint ID, method, and sanitized URL |
Security
- Only whitelisted endpoints from the static config can be proxied
- Credentials are injected server-side and never sent to the browser
- API keys are stripped from URLs in the response (
sanitizeUrl()) - POST-only to prevent CSRF via GET requests
- 512KB response cap prevents memory issues
- 15-second timeout per request
- Requires
validateAdmin()authentication
Provider Visibility
GET /api/admin/api-hidden-providers
Returns the list of hidden provider IDs. Requires admin auth.
Response: { "hidden": ["perplexity", "anthropic"] }
POST /api/admin/api-hidden-providers
Toggle a provider’s visibility. Requires admin auth.
Request body: { "id": "perplexity", "hidden": true }
Response: { "ok": true, "hidden": ["perplexity", "anthropic"] }
Hidden providers are stored in KV at data:api-dashboard:hidden-providers (JSON array). They are filtered out at SSR time. The visibility dropdown in the page header lists hidden providers and allows restoring them.
All Source Files
| File | Purpose |
|---|---|
src/pages/admin/api-dashboard.astro | Main dashboard page |
src/pages/admin/_api-dashboard-config.json | Provider definitions, endpoint map, auth patterns |
src/pages/api/admin/api-proxy.ts | Server-side API proxy (whitelist, multi-auth) |
src/pages/api/admin/api-hidden-providers.ts | KV-backed hide/unhide toggle |
src/pages/api/admin/api-usage.ts | Provider status and live usage data |
src/pages/api/admin/api-dashboard-providers.ts | Custom provider CRUD |
src/pages/api/admin/api-keys.ts | Encrypted key management |
src/lib/key-encryption.ts | AES-256-GCM encrypt/decrypt |
src/lib/runtime-env.ts | getEnv() fallback chain, loadKvApiKeys() |
src/config/modules/api-dashboard.ts | Module manifest (v3.0.0) |