API Keys
API keys are how external software identifies itself to WALDO. You generate a key in the dashboard, hand it to your integrator (or paste it into Claude / ChatGPT), and from that point forward they can read your analyses, trigger new ones, and manage your webhooks on your behalf — with no further input from you. Each key is yours alone, scoped to the permissions you choose, and revocable at any time.
TL;DR for CRE professionals: API keys turn WALDO into a back-end service for whatever tools you already use. Want a custom dashboard pulling live numbers from your latest analysis? Want Claude to "summarize the location quotient on my Cuyahoga County analysis"? Want to trigger a fresh analysis from a Slack slash command? You generate a key in Settings → API Keys, hand it to the integrator, and that's it.
What you get from this
- Plug WALDO into anything. Any tool that can make an HTTPS request — n8n, Zapier, Make, Pipedream, your custom CRM, Cursor, Claude Desktop, ChatGPT, a Slack app, a Retool dashboard — can read and write to WALDO with an API key.
- Granular permissions. A key issued to "the n8n flow that just emails reports" doesn't need permission to delete webhooks or run new analyses. You pick scopes per key.
- Audit trail. Every API call is logged with the key that made it, the route, the response, and the time. If something looks off, you can see exactly which integration is responsible.
- Revocable in one click. If a laptop walks off, if an integrator leaves, if Claude got a little too autonomous — revoke the key, and that channel is shut down immediately. The rest of your integrations keep working.
Common use cases
| What you want to do | Which scopes the key needs |
|---|---|
| Pull analysis results into a Google Sheet on a schedule | text |
| Have Claude / ChatGPT read your latest analysis and summarize it | text text |
| Trigger a new analysis from an external system (Slack command, n8n flow, custom dashboard) | text |
| Build a custom dashboard that lists your analyses and shows status | text |
| Wire up webhooks programmatically from Terraform / CI | text text |
| Connect Claude Desktop / Cursor to WALDO via MCP | text |
Step-by-step: creating your first API key
- Sign in to WALDO as a Professional or Enterprise user. (API access isn't included on Basic — upgrade in Settings → Billing if needed.)
- Go to Settings → API Keys in the left sidebar.
- Click "New key." Give it a recognizable name (e.g. n8n production, Cursor MCP, Retool dashboard) so you'll remember what each key does.
- Choose the scopes the key needs. Grant the minimum. A read-only key for a dashboard should not have .text
write:analyses - Click "Create key." The full key () will be revealed — copy it now, it won't be shown again. (If you lose it, revoke it and create a new one.)text
waldo_live_... - Paste the key into your integrator's environment variables (e.g. intext
WALDO_API_KEY, or directly into the secrets section of your automation tool). Never paste it into a public chat, GitHub, or a Loom video.text.env
Available scopes
| Scope | What it allows |
|---|---|
text | List the caller's analyses; fetch any phase result (economic base, employment / shift-share, market metrics, supply); export a finished analysis as PDF / Excel / PowerPoint; search the markets directory. |
text | Trigger a new analysis. Returns a job ID immediately; the analysis runs in the background and fires text |
text | List your webhook endpoints; view recent delivery history. |
text | Create, update, rotate, and delete webhook endpoints; replay deliveries. |
text | Allow MCP clients (Claude Desktop, Cursor, custom MCP integrations) to call WALDO tools on your behalf. |
You can combine multiple scopes on one key. Example: a single "n8n production" key with
read:analyseswrite:analysesread:webhooksKey hygiene — the short version
- Treat keys like passwords. They grant access to your data and can run analyses on your behalf (which costs money under your plan).
- One key per integration. When you offboard an integration, you revoke just that one key. If five tools share one key and the laptop walks off, you have to rotate everything.
- Don't paste keys into chat / Slack / GitHub. Use environment variables, secrets managers (1Password, Doppler, Vault), or your platform's secrets UI.
- Rotate periodically. For long-lived integrations, plan to rotate the key every 6–12 months: create a new key, update the integration, revoke the old one.
- Watch the audit trail. If you see calls from a key you don't recognize, revoke it immediately.
Rate limits and quotas
WALDO enforces two layers of rate limiting:
- Short-window (per user, per minute) — protects against runaway loops and abuse. If your integrator gets a , slow down.text
429 Too Many Requests - Monthly cost-units (per user, per billing period) — different endpoints cost different amounts (read endpoints = 1 unit, write endpoints = 50 units, MCP calls = 5 units). Quota varies by tier.
Check your current usage in Settings → Billing. If you're hitting quota, the upgrade prompt there shows the next tier up.
When something goes wrong
The most common API key issues:
- — The key is missing, mistyped, or revoked. Confirm thetext
401 Unauthorizedheader is present and matches a non-revoked key.textAuthorization: Bearer waldo_live_... - — The key is valid but lacks the scope for the operation. Either add the scope to that key (you'll need to revoke + recreate — scopes aren't editable on existing keys) or use a different key.text
403 Forbidden - on a resource you own — The resource ID is wrong, or it belongs to a different user. WALDO returns 404 instead of 403 for cross-user access to avoid leaking ownership.text
404 Not Found - — You've hit the rate limit. Back off and retry; the response includes atext
429 Too Many Requestsheader.textRetry-After
Every API response includes an
X-Request-IDFor your integrator (or AI agent)
Everything below is the technical reference. Paste a link to this page into Claude / ChatGPT / Cursor and ask the AI to wire up the integration — it has all the information it needs.
Authentication
All calls to
/api/v1/*textAuthorization: Bearer waldo_live_<32-base64url-bytes>
Keys are looked up by SHA-256 hash. The full key value is shown to the user once at creation and never stored in plaintext on our side.
Server-to-server only. The public API does not set CORS headers — calling it from a browser would expose the key in client JavaScript. Keys belong on a server (or in an environment variable consumed by a server-side tool).
Key shape
textwaldo_live_<32 base64url bytes, no padding>
Example:
waldo_live_3xY7pQ-cN8mZv4_HhKfWAa1bRsTu2EgD0iLoP9qVwXyThe dashboard displays
waldo_live_X…3xYzScopes (technical)
Scope strings (storage layer):
read:analyseswrite:analysesread:webhookswrite:webhooksmcp:invokeStored on
api_keys.scopes TEXT[]withApiAuth({ requireScopes: [...] })/api/v1/*withApiAuth({ requireScopes: ['read:analyses'] })read:analysesTier gating (technical)
API access is paid-tier only. After key + scope check,
withApiAuthsubscription_tierjson{ "type": "https://api.waldocre.io/errors/forbidden", "title": "API access requires a paid plan", "status": 403, "detail": "Upgrade to Professional or Enterprise to use the API.", "instance": "/api/v1/...", "request_id": "..." }
Rate limits (technical)
Two layers:
- Sliding-window per (Upstash Redis): default 100 requests / 60 seconds. Tier-overridable.text
user_id - Monthly cost-units per (counted ontext
user_id): per-tier cap. Read = 1, write = 50, MCP = 5 per call.textapi_audit_log.cost_units
Rate-limit responses are RFC 9457 problem-details with
429Retry-AfterPer-call audit log
Every authenticated call writes one row to
api_audit_logtextrequest_id, key_id, user_id, method, route, status, latency_ms, cost_units, ts
You can correlate any error response back to its row using the
X-Request-IDEndpoint reference
All under
https://<your-waldo-host>/api/v1/Analyses
| Method | Path | Scope | Cost | Description |
|---|---|---|---|---|
| GET | text | text | 1 | List caller's analyses (cursor-paginated). |
| GET | text | text | 1 | Summary: status, market, completion, top-line metrics. |
| GET | text | text | 1 | Full economic-base phase result. |
| GET | text | text | 1 | Full shift-share decomposition. |
| GET | text | text | 1 | Full market-metrics phase result. |
| GET | text | text | 1 | Full supply phase result. |
| GET | text | text | 1 | Binary export. |
| GET | text | text | 1 | Async-job status (current_phase, completion_percentage). |
| POST | text | text | 50 | Trigger a new analysis. Requires text text text |
Markets
| Method | Path | Scope | Cost | Description |
|---|---|---|---|---|
| GET | text | (none) | 1 | Search the markets directory. |
Webhooks
| Method | Path | Scope | Cost |
|---|---|---|---|
| GET | text | text | 1 |
| POST | text | text | 5 |
| GET | text | text | 1 |
| PATCH | text | text | 5 |
| DELETE | text | text | 5 |
| POST | text | text | 5 |
| GET | text | text | 1 |
| POST | text | text | 5 |
See the Webhooks docs for the full payload contract.
Idempotency
Write endpoints (currently
POST /v1/analysesIdempotency-KeytextPOST /api/v1/analyses Authorization: Bearer waldo_live_... Idempotency-Key: 3f9c-batch-march-cuyahoga Content-Type: application/json { "market_code": "39035", "market_type": "county", ... }
Pagination
List endpoints use opaque cursor pagination:
textGET /api/v1/analyses?limit=50&cursor=eyJ1cGRhdGVkX2F0IjoiMjAyNi0w...
Cursors encode
(updated_at, id)limitnext_cursornullError format (RFC 9457)
json{ "type": "https://api.waldocre.io/errors/invalid-request", "title": "Invalid request", "status": 400, "detail": "events must be a non-empty array", "instance": "/api/v1/webhooks", "request_id": "..." }
request_idX-Request-IDMCP (Claude / Cursor / agents)
The
mcp:invoke/api/mcpQuickstart — Node.js
jsconst WALDO = 'https://app.waldocre.io/api/v1' async function listAnalyses() { const res = await fetch(`${WALDO}/analyses?limit=20`, { headers: { Authorization: `Bearer ${process.env.WALDO_API_KEY}` }, }) if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) return res.json() }
Quickstart — Python
pythonimport os, requests WALDO = "https://app.waldocre.io/api/v1" def list_analyses(): r = requests.get( f"{WALDO}/analyses", params={"limit": 20}, headers={"Authorization": f"Bearer {os.environ['WALDO_API_KEY']}"}, ) r.raise_for_status() return r.json()
Quickstart — curl
bashcurl -H "Authorization: Bearer $WALDO_API_KEY" \ https://app.waldocre.io/api/v1/analyses?limit=20