Spend Report Dashboard — Setup Runbook¶
The /landscape page has a Spend (30d) tab that aggregates real cost
data from every paid (and free-tier) service in the stack. This doc walks
through enabling each data source.
Live URL: https://services.curaway.ai/landscape → click "Spend (30d)" tab
Endpoint: GET /landscape/spend.json (returns the cached aggregator output)
Quick reference — environment variables¶
All optional. The dashboard works incrementally — set whichever keys you have and the rest will show as "No credentials" with a setup link.
| Service | Env vars | Notes |
|---|---|---|
| Langfuse | LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY |
Already set on Railway |
| Anthropic (incl. Claude Code) | ANTHROPIC_ADMIN_KEY |
Distinct from ANTHROPIC_API_KEY |
| OpenAI | OPENAI_ADMIN_KEY |
Distinct from project keys |
| Railway | RAILWAY_API_TOKEN |
Personal token |
| Vercel | VERCEL_API_TOKEN |
Personal token |
| Cloudflare R2 | CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID |
Token needs R2:Read |
| Upstash | UPSTASH_MGMT_API_KEY + UPSTASH_MGMT_EMAIL |
Management API |
After adding any of these to Railway:
1. Langfuse (already configured)¶
No action needed. LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are
already set on Railway. Langfuse is the authoritative source for LLM
spend because every Claude/OpenAI call in the stack is auto-traced via
the LangChain callback handler. The other LLM providers below are
cross-checks.
If for some reason you need to verify or rotate them: cloud.langfuse.com → Settings → API Keys.
2. Anthropic Admin Key (includes Claude Code)¶
The standard ANTHROPIC_API_KEY (which the backend uses to call Claude
Haiku/Sonnet) does not have access to the org cost endpoints. You need
a separate Admin Key.
- Visit https://console.anthropic.com/settings/admin-keys
- Click Create Admin Key
- Name it
curaway-spend-report(or similar) - Copy the key (starts with
sk-ant-admin-...) - Add to Railway:
What this unlocks:
- 30-day daily cost breakdown for the Anthropic workspace
- Includes Claude Code spend if Claude Code bills against the same
workspace (it usually does — check console.anthropic.com → Billing →
Usage to confirm)
- API source: GET /v1/organizations/cost_report
3. OpenAI Admin Key¶
OpenAI's project keys can't query org-level cost. You need a service-account admin key.
- Visit https://platform.openai.com/settings/organization/admin-keys
- Click Create new secret key → set type to Admin
- Copy the key (starts with
sk-svcacct-...orsk-admin-...) - Add to Railway:
What this unlocks:
- 30-day daily cost breakdown for the OpenAI org
- API source: GET /v1/organization/costs
Note: Curaway uses very little OpenAI in production (it's the fallback for Claude failures). This number should usually be near zero.
4. Railway API Token¶
- Visit https://railway.app/account/tokens
- Click Create Token, name it
curaway-spend-report - Copy the token
- Add to Railway:
What this unlocks:
- Recent invoice totals via the Railway GraphQL API
- The Railway Pro plan is $20/mo flat — this number should be ~$20
- API source: https://backboard.railway.app/graphql/v2
Limitation: Railway exposes invoice totals, not per-day breakdowns. The chart will show monthly buckets only.
5. Vercel API Token¶
- Visit https://vercel.com/account/tokens
- Click Create Token, scope it to the personal account, name it
curaway-spend-report - Copy the token
- Add to Railway:
What this unlocks:
- Hobby tier billing (should be $0 since the app + docs site are both on
the free Hobby plan)
- API source: GET /v1/usage/billing
6. Cloudflare R2¶
Need a Cloudflare API token with Workers R2 Storage:Read permission, plus
the account ID (which is already in R2_ACCOUNT_ID on Railway).
- Visit https://dash.cloudflare.com/profile/api-tokens
- Click Create Token → Custom token
- Permissions:
Account→Workers R2 Storage→Read - Account resources: include the Curaway account
- Copy the token
- Add to Railway:
What this unlocks:
- Real R2 storage used (vs the 10 GB free tier limit)
- The dashboard shows free-tier-used percentage so you know when to worry
- API source: GET /accounts/{id}/r2/usage
7. Upstash Management API¶
Used for both Redis (cache) and QStash (async messages).
- Visit https://console.upstash.com/account/api
- Click Create API Key
- Copy both the key and the account email it's bound to
- Add to Railway:
What this unlocks:
- Daily Redis command count vs the 10K/day free tier
- Same for QStash messages (500/day free)
- API source: https://api.upstash.com/v2/redis/databases
Free-tier services (dashboard spot-check only)¶
These services have no programmatic usage API at the free tier. The Spend tab shows them as a table with direct links to each provider's dashboard so you can manually verify usage:
- Neo4j Aura — 200K nodes free
- Qdrant Cloud — 1 GB free
- Clerk — 10K MAU free
- Flagsmith — 50K req/mo free
- Resend — 3K emails/mo free
- Voyage AI — 50M tokens/mo free
- PostHog — 1M events/mo free
- GitHub — free
If usage on any of these crosses the threshold, you'll get an email from the provider — no need to monitor proactively.
How the dashboard works¶
- Frontend tab (
/landscape→ "Spend (30d)" button) makes a singleGET /landscape/spend.jsoncall when the tab is first clicked. - Backend endpoint checks Redis cache (
landscape:spend:30key, 1h TTL). - On miss, calls
app.services.spend_report_service.collect_spend_report(days=30)which fans out to all 7 fetchers in parallel viaasyncio.gather. - Each fetcher returns a normalized dict with
status∈{ok, no_credentials, error, free_tier}. - The aggregator combines daily series, computes totals, and returns the payload.
- Cached for 1 hour to avoid hammering the external APIs.
The dashboard renders three things: - KPI tiles: 30-day total, biggest line item, paid services reporting, free-tier service count, generated-at timestamp - Daily spend stacked bar chart (last 30 days, one stack per provider) - Provider donut chart (share of total spend by provider) - Paid services table with per-service status badges + setup links for any unconfigured providers - Free tier services table with dashboard links
Force-refresh¶
The cache TTL is 1 hour. To force a fresh pull:
# Either delete the Redis key directly...
redis-cli -u $UPSTASH_REDIS_URL DEL landscape:spend:30
# ...or wait for the TTL to expire.
Troubleshooting¶
"No credentials" for a service I configured: Railway env var changes
require a redeploy. Check railway logs for the latest deployment status.
"Error: HTTP 401": The key is invalid or expired. Regenerate from the provider console.
"Error: HTTP 403": The key is valid but lacks permission. For Anthropic and OpenAI, make sure you generated an Admin key, not a project/secret key.
Spend tab is blank: Check the browser console for /landscape/spend.json
errors. The endpoint returns the aggregator output even when individual
fetchers fail — a fully blank response means the cache or aggregator itself
crashed.
"Connection timeout": External APIs occasionally hang. Each fetcher has
a 15s timeout. The aggregator is wrapped in asyncio.gather(..., return_exceptions=True)
so a single hang doesn't kill the others.