batchwatch

batchwatch › batchwatch API reference

batchwatch API reference

Base URL: https://batchwatch.dev (https://api.batchwatch.dev and https://www.batchwatch.dev are the same worker — no redirect).

Base URL: https://batchwatch.dev (https://api.batchwatch.dev and https://www.batchwatch.dev are the same worker — no redirect).

Everything below was written by reading src/index.js and then calling the routes. Every example response in these documents is a real captured response, and each one says where it came from. Nothing here is invented.

Provenance of the examples

Two sources, and they are always labelled:

LabelWhat it means
Captured from https://batchwatch.devA real call against production on 2026-08-25. The numbers are whatever the live dataset held at that moment.
Captured from a local wrangler dev instanceThe same code, run locally against a local D1 seeded with synthetic measurements. Used only for routes that cannot be reached from an unauthenticated caller, or for response shapes production could not produce at the time. The numbers in those examples are not real queue times.

Why some examples are local: creating an API key from the machine used for this work returned 429 too many keys from this address today, so the key-authenticated routes could not be exercised against production. Writing measurements into production to demonstrate the ingest routes was deliberately not done — the dataset is the product, and it must not contain data that was made up to illustrate documentation.

Routes

MethodPathKey requiredDelayed by tier
GET/healthnon/a
GET/v1/statusnono
GET/v1/probenono
GET/v1/outagesnono, never
GET/v1/outages.atomnono, never
GET/v1/coveragenono
GET/v1/waitnoyes
GET/v1/curvenoyes
GET/v1/should-i-batchcontribution or free trialyes
GET/v1/b2b/should-i-batchcontribution or free trialyes
GET/v1/estimate-batchtimecontribution or free trialyes
GET/v1/conditionscontribution or free trialyes
GET/v1/distributioncontribution or free trialyes
POST/v1/subscriptionsyesn/a
GET/v1/subscriptionsyesn/a
DELETE/v1/subscriptions/{id}yesn/a
POST/v1/keysnon/a
GET/v1/keys/currentyesn/a
DELETE/v1/keys/currentyesn/a
POST/v1/callsno (optional)n/a
PATCH/v1/calls/{id}no (optional)n/a
POST/v1/calls/completeyesn/a
GET/v1/calls/mineyesn/a
DELETE/v1/calls/mineyesn/a
POST/v1/rollup/refreshyesn/a
OPTIONSanynon/a

Plus the non-API pages served by the same worker: / and /index.html (dashboard, HTML), /m/{provider}/{model} and /p/{provider} (public model and provider pages, HTML), /robots.txt, /sitemap.xml. See ops.md.

Routes that do not exist, despite being named in docs/KRAVSPEC.md: /v1/batches, /v1/batches/complete, /v1/forecast, /v1/tradeoff, /v1/anomalies, /v1/status.atom. All six return 404 (verified against production on 2026-08-25; /v1/coverage returned 200 in the same run as a positive control). KRAVSPEC is out of date; this reference describes the code.

Authentication

Authorization: Bearer bw_...

The token is hashed with SHA-256 and compared against api_key.token_hash. A revoked key (revoked_at set) does not authenticate.

An absent or unrecognised header is not an error on most routes — the caller is simply treated as anonymous. Only the routes marked "key required" above return 401.

Get a key with POST /v1/keys. No email, no confirmation.

Path and method handling

CORS

All JSON responses carry access-control-allow-origin: * and cache-control: no-store. OPTIONS on any path returns 204 with:

access-control-allow-origin: *
access-control-allow-methods: GET,POST,PATCH,OPTIONS
access-control-allow-headers: authorization,content-type

Note that DELETE is not in access-control-allow-methods, although DELETE /v1/calls/mine and DELETE /v1/keys/current exist. Browser preflight for those two routes will therefore fail; call them server-side. (Verified against production 2026-08-25 — see the discrepancy list at the bottom of ops.md.)

The rest of this reference

Quickstart

# open, no key, delayed 15 minutes
curl 'https://batchwatch.dev/v1/wait?provider=openai&model=gpt-5-nano'

# get a key
curl -X POST https://batchwatch.dev/v1/keys \
     -H 'content-type: application/json' \
     -d '{"label":"prod-pipeline"}'

# contribute: start a measurement when your batch job starts
curl -X POST https://batchwatch.dev/v1/calls \
     -H "authorization: Bearer $BW_KEY" -H 'content-type: application/json' \
     -d '{"provider":"openai","model":"gpt-5-nano","mode":"batch","requests":1200,"input_tokens":840000}'

# ...and close it when the job finishes
curl -X PATCH https://batchwatch.dev/v1/calls/c_xxxxxxxxxxxxxxxxxxxx \
     -H "authorization: Bearer $BW_KEY" -H 'content-type: application/json' \
     -d '{"status":"completed","output_tokens":250000}'

Five completed measurements in the last seven days makes you a contributor, which unlocks the four gated routes and cuts the delay from 900s to 300s.

Reference

Measurement routes

Seven routes answer questions about queue time. Three are open to anyone (/v1/wait, /v1/curve, /v1/coverage); four are gated behind a contribution or the…

Contributing measurements

There are two ways to get a measurement into the dataset, and they are not equally trusted.

Keys

Self-service, no email, no confirmation, no password. You get a key by asking for one.

Tiers, delay, quota and the free trial

Two things are sold separately, and the split is the whole design:

Error model

Every error is JSON with an error field holding a human-readable message, plus route-specific fields. There is no error code enumeration — the status code…

Reading an answer

Most responses carry the same four disclosure fields: confidence, basis, n and freshness. They exist because the alternative — a single number with no…

Operational and public routes

Health, self-check, prober state, outage feeds, the forced rollup, and the HTML pages the same worker serves.

Outage alerts

Batchwatch watches every model it measures and opens an outage when the queue for one of them degrades against its own baseline. This page is how you get…