Read your site's data over HTTP
Every Web Buddy site has a read-only REST API for its details, page-view analytics, and team. Authenticate with a site API key and call the endpoints below.
Base URL
All endpoints live under a single versioned base URL:
https://webbuddy.radioworkflow.com/api/v1
The API is read-only today. Content writes (articles and the like) happen in your site's own admin, not here. What v1 guarantees, and what would cost a new version segment, is on the versioning page.
Authentication
Create a key on your site's API tab in the console (owner only). A key looks like wb_live_… and is shown in full exactly once, at creation — we store only a hash, so copy it somewhere safe. Each key is scoped to the one site it was created for, and there is no way to ask for another: the key is the site selector.
The site API key as a bearer token: Bearer wb_live_…. It is the only credential — there are no cookies, no session and no CSRF token in this API.
curl 'https://webbuddy.radioworkflow.com/api/v1/site' \ -H "Authorization: Bearer wb_live_your_key_here"
Scopes
A key carries only what you tick when you create it, and the choice is permanent: scopes cannot be added to an existing key. Call an endpoint whose scope your key does not carry and it answers insufficient_scope— every time, for the life of that key. Give a key the least it needs; a public analytics widget has no business reading your team's email addresses.
| Scope | What it unlocks | Endpoints |
|---|---|---|
| site:read Site | Read the site's identity, serving URLs and custom domains. | |
| analytics:read Analytics | Read page-view counts and top paths. | |
| members:read Team | Read the team list, including every member's email address. |
A key minted before scopes existed carries none and grants everything; the console labels it Full access · legacyon the site's API tab. Replace one when you see it — it is the only key shape that cannot be narrowed.
Endpoints
4 endpoints, grouped by what they read. Each page carries its parameters, response fields, error codes and ready-to-run samples — and a Try it form that sends the real request with your own key and shows you the real answer. Try get the site now.
The form runs in your browser and calls the API on this same origin: the key you paste goes into the Authorization header of that one request and nowhere else — not to the documentation, not into storage on your device. It is a real call, so it counts against the same rate limitand shows up in your site's API log.
Site
Analytics
Team
What every response carries
Set by the wrapper every endpoint shares, on success and on failure alike:
| Header | Value | Why |
|---|---|---|
| cache-control | no-store | Every response is per-key and live, so nothing in this API is cacheable by an intermediary. Cache on your side if you poll, keyed by whatever freshness your integration actually needs. |
| X-API-Version | v1 | Which API version answered. The base path already implies it; the header makes it visible in a log or a request trace, where a proxy sitting in front of something else would otherwise be invisible. |
| X-RateLimit-Limit | 120 | Requests allowed per key per 60-second window, on the instance that answered. See the rate-limit note: this is a per-instance brake, not a global budget. |
| X-RateLimit-Remaining | 119 | Requests left in the current window after this one. Absent on a 401, which cannot be attributed to a key. |
| X-RateLimit-Reset | 1787896102 | When the window rolls over, as UNIX SECONDS — not a duration and not milliseconds. A 429 additionally carries Retry-After in whole seconds, floored at 1. |
Errors
Failures answer a non-2xx status and a JSON envelope with a stable machine-readable code and a human-readable message. Branch on the code — the message is prose for a person and may be reworded at any time.
{
"error": {
"code": "unauthorized",
"message": "Provide a valid API key as an 'Authorization: Bearer wb_live_...' header.",
"docs_url": "https://webbuddy.radioworkflow.com/docs/errors#error-unauthorized"
}
}These can arrive from any endpoint:
- 401
unauthorized— No usable API key on the request. TheAuthorizationheader was missing or malformed, the token did not match any key, the key has been revoked, or the key's site is no longer active. - 403
insufficient_scope— The API key is valid, but does not carry the scope this endpoint requires. Scopes are chosen when a key is created and cannot be changed afterwards. - 429
rate_limited— The key exceeded 120 requests in the current 60-second window. The same code also answers a caller that has presented too many tokens which do not resolve — that brake is counted per network address rather than per key, because a request nobody can attribute to a key has no key bucket to charge, and looking a bad token up still costs a database call. - 500
internal_error— The request failed inside the server. The cause is never disclosed to the caller — the wrapper swallows the exception so a stack trace cannot reach an API consumer.
The full catalogue, with what to do about each one, is on the errors page.
Rate limits
Each API key is limited to 120 requests per minute — counted per key, so two keys on the same site do not share a budget. Beyond that the API answers rate_limited until the window resets.
Counted per API key in a fixed 60-second window, per serving instance. Treat it as an abuse brake rather than a guaranteed budget: cache what you poll, and spread bulk reads out.
Health check
Answers 200 ok while the server process is running. It does NOT touch the database — a 200 here means the process is up, not that the console is fully functional. It lives at /api/health rather than /healthz because Google's front end reserves that path on *.run.app and answers it before the request reaches us.
https://webbuddy.radioworkflow.com/api/healthNo key requiredIt sits outside /api/v1 and outside every promise on the versioning page — it belongs to the infrastructure, not to the product API, and its shape can change without that being a breaking change. Poll it for uptime; do not build on it.
Machine-readable contract
The whole surface is published as an OpenAPI 3.1 document — generate a client from it, import it into Postman or Bruno, or keep a copy and diff it after a release to see whether anything you depend on moved.
https://webbuddy.radioworkflow.com/api/v1/openapi.jsonNo key requiredcurl -s https://webbuddy.radioworkflow.com/api/v1/openapi.json > api-$(date +%F).json diff <(jq -S . api-2026-01-01.json) <(jq -S . api-$(date +%F).json)
It is generated from the same catalogue this page is, so it cannot describe an endpoint the docs do not. The document's own shape follows the OpenAPI specification rather than our versioning policy — the promises on that page are about the API, not about this file.
Keeping keys safe
- Treat a key like a password. Anyone who has it can read everything these endpoints expose for your site.
- Tick the least it needs. A key for a traffic dashboard needs
analytics:readand nothing else — leavingmembers:readticked puts every member's email address behind a token that is going to live in a config file somewhere. See Scopes. - Store keys in a secret manager or an environment variable — never commit them to source control or paste them into client-side code. The samples on each endpoint page show a placeholder for exactly this reason.
- The full key is shown only once. If you lose it, create a new one rather than trying to recover the old.
- Rotate by creating a new key, switching your integration to it, then revoking the old one. Revoking takes effect immediately.
- If a key is ever exposed, revoke it right away from the site's API tab.
Manage keys on your site's API tab in the console — create, see when each was last used, and revoke. Only site owners can create or revoke keys; editors can view the list.