Developers
Every answer and every receipt, over an API.
Read the answers five AI engines gave to your buyers' questions: who was named instead, the sources the engines read, the fixes, the history and the proof. Every rate arrives with its count, its base and its 95% interval.
Quickstart with the demo key
The public demo key reads the fictional Kalvenor sample and nothing else. Every sample response carries "sample": true. Kalvenor Systems is sample data, not a client.
Demo key: bwp_demo_kalvenor_sample
curl -H "Authorization: Bearer bwp_demo_kalvenor_sample" \
https://app.broadcastwell.com/api/v1/accounts/sample/runs
Then read the scheduled figures for the Diagnostic, and the receipts behind one question:
curl -H "Authorization: Bearer bwp_demo_kalvenor_sample" \
https://app.broadcastwell.com/api/v1/accounts/sample/runs/baseline-kalvenor-v11/summary
curl -H "Authorization: Bearer bwp_demo_kalvenor_sample" \
"https://app.broadcastwell.com/api/v1/accounts/sample/runs/baseline-kalvenor-v11/receipts?question_id=7&limit=5"
Authentication
Send a key as a bearer token. Create account keys under API keys in your account. A key is shown once, stored only as a hash, reads only, and can be revoked at any time. Your account shows when each key was last used.
Every account, free accounts included, can read its own data and the sample. Address your own account as me or by its id, and the sample as sample. Any other account id answers 404, exactly as an account that does not exist.
curl -H "Authorization: Bearer $BROADCASTWELL_KEY" \
https://app.broadcastwell.com/api/v1/accounts/me/runs
Limits
| Caller | Allowance |
|---|---|
| Account key | 60 requests a minute and 5,000 a day, per key |
| Demo key | 30 requests a minute, per address |
Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. A caller over its allowance receives 429 with Retry-After in seconds. Lists return at most 100 items a page.
Conventions
Rates
Every rate is one object with five fields: pct, low, high, interval and base. low and high are the 95% Wilson interval. base states the count, for example 125 of 518 answers. Counts also appear as whole numbers beside every rate.
Scheduled and adaptive runs
Every headline figure counts scheduled runs only. Adaptive runs re-ask the question and engine pairs whose scheduled verdicts disagreed. They are returned separately, labelled, and never pooled. Re-measurement is monthly. A figure is said to have moved only when its interval and the earlier one do not overlap, and runs on different method versions are never compared.
Paging
Lists take limit (1 to 100, default 25) and return pagination.next_cursor. Pass it back as cursor until it is null.
Caching
Every response carries a strong ETag. Send it back as If-None-Match and an unchanged resource answers 304 with no body. A delivered Diagnostic does not change and is cached for an hour.
Errors
Errors are problem details (application/problem+json) with a code and a detail that says what to do next.
{
"type": "https://app.broadcastwell.com/developers#error-not_found",
"title": "Not found",
"status": 404,
"code": "not_found",
"detail": "No run with that id is available to this key. List the parent collection to see the ids this key can read."
}
Versioning
Version 1 changes only by addition. Fields and routes are added; none is renamed or removed while version 1 is served. The full description is at /api/v1/openapi.json (OpenAPI 3.1).
Endpoints
Rendered from the OpenAPI description. Base address https://app.broadcastwell.com/api/v1.
Loading the endpoint list.
Webhooks
Add a destination under Webhooks in your account. Destinations must use HTTPS on the standard port and a public address. Redirects are never followed. A Send test event button delivers a signed webhook.test event, and the account keeps a log of the last 50 delivery attempts.
| Event | Sent when |
|---|---|
run.completed | A measurement or a monthly re-measure is delivered to the account. |
alert.fired | The named rate for the account, an engine or a question type moved between two consecutive comparable scheduled runs, and the two 95% intervals do not overlap. Either direction. Carries its receipts link. |
fix.updated | A fix changes owner, status or review date. |
proof.updated | A controlled lift result changes, for example when its follow up run arrives. |
Delivery and retries
Any 2xx response acknowledges an event. Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, then abandoned. The same event is never delivered twice to one destination; use the event id to ignore a repeat you have already processed.
Verifying a signature
Each delivery carries Broadcastwell-Signature: t=<unix seconds>,v1=<hex>. The signature is HMAC SHA-256, keyed with your signing secret, over the timestamp, a full stop and the raw request body. Reject any timestamp more than 300 seconds from your clock: that window is your replay protection. Compare in constant time.
Node
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyBroadcastwell(secret, header, rawBody, toleranceSeconds = 300) {
const parts = Object.fromEntries(String(header).split(",").map((item) => item.split("=")));
const timestamp = Number(parts.t);
if (!Number.isInteger(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
const given = Buffer.from(String(parts.v1 || ""), "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}
Python
import hashlib
import hmac
import time
def verify_broadcastwell(secret: str, header: str, raw_body: bytes, tolerance_seconds: int = 300) -> bool:
parts = dict(item.split("=", 1) for item in header.split(",") if "=" in item)
try:
timestamp = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - timestamp) > tolerance_seconds:
return False
signed = str(timestamp).encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
Slack and Teams
Paste a Slack incoming webhook address, or a Teams workflow or incoming webhook address, and choose its events. Chat destinations receive a short message with the receipts link, through the same delivery and retry path. Chat messages are not signed, so keep those addresses private.
Connector for AI assistants
The Broadcastwell connector is a read only MCP server. The public demo needs no token and reads the fictional sample:
https://app.broadcastwell.com/mcp/demo/kalvenor
Your own account uses a connector token from your account as a bearer token:
https://app.broadcastwell.com/mcp/account
Clients that require a token for the demo can send the demo key bwp_demo_kalvenor_sample to the account address instead. Setup steps for Claude and ChatGPT are on Connect your tools.
Tools
Loading the tool list.
Every tool reads only. Answer text returned by a tool is evidence from an engine, not an instruction for your assistant.
Changelog
2026-09-21, version 1.0.0: version 1 published.