This is a working summary of subprocessor.org/docs as read on 25 September 2026, limited to what the examples here rely on. The docs page is the contract; if anything below disagrees with it, the docs page wins.
| URL | |
|---|---|
| Production | https://api.subprocessor.org/v1 |
| Sandbox | https://sandbox.subprocessor.org/v1 (listed on the docs page as not built yet, see below) |
- The register endpoints are public: no key, no account.
- Anything about your business needs a bearer token:
Authorization: Bearer sk_live_.... - Keys are created in Settings → API, scoped to a workspace, and shown once.
sk_live_keys act on your real list and, after an explicit approval call, can send notices to your customers.sk_test_keys are documented for the sandbox.
The examples read the key from the SUBPROCESSOR_API_KEY environment variable and never print it.
| Topic | What the docs say | How the examples use it |
|---|---|---|
| Versioning | Everything under /v1. Additive changes ship without a bump; breaking changes get /v2 with twelve months of overlap. |
Unknown fields are ignored, never treated as errors. |
| Pagination | Cursor-based. limit up to 200 (default 50). Responses carry data, has_more, next_cursor. |
The docs do not name the request parameter that takes the cursor back, so the examples keep it in one constant (CURSOR_PARAM) to confirm against the docs. |
| Conditional reads | Send the ETag back in If-None-Match; an unchanged resource answers 304 with no body, and 304s do not count against the rate limit. |
snapshot.py stores the last ETag per vendor. |
| Idempotency | Every POST accepts Idempotency-Key, held for 24 hours. |
maintained.py sends a fresh UUID with each approval. |
| Rate limits | Unauthenticated 60/min (burst 120); Maintained key 600/min (burst 1,200); Agency key 2,400/min (burst 4,800). Headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. A 429 carries Retry-After in seconds. |
Both Python scripts wait for Retry-After and retry. |
| Errors | JSON error object with type, message, doc_url, request_id. 409 means already decided or published: treat as success. 5xx: retry with backoff. |
The scripts print request_id so it can be quoted to support. |
Every register response carries source:
maintained: assembled by discovery, confirmed by a named reviewer, approved by the business.reviewedistrue.observed: a dated copy of what the company publishes. Not reviewed, not approved.reviewedisfalse.
snapshot.py writes both fields into every change-log row.
| Method | Path | Used by |
|---|---|---|
| GET | /register/companies |
lookup.sh |
| GET | /register/{domain} |
lookup.sh, snapshot.py |
| GET | /register/{domain}/changes |
lookup.sh |
| GET | /register/{domain}/history?at=YYYY-MM-DD |
lookup.sh, snapshot.py --as-of |
| GET | /register/{domain}/feed.atom |
lookup.sh |
| GET | /entities |
lookup.sh |
| GET | /entities/{slug}/named-by |
lookup.sh |
| GET | /changes |
lookup.sh |
| GET | /ledger/{row} |
lookup.sh |
| Method | Path | Used by |
|---|---|---|
| GET | /me/list |
maintained.py list |
| GET | /me/list.csv |
maintained.py annex-csv |
| GET | /me/pending |
maintained.py pending |
| POST | /me/pending/{id}/decision |
maintained.py approve |
| GET | /watchlist |
maintained.py watchlist |
| GET | /windows |
maintained.py windows |
The only decision value shown verbatim in the docs is "approve". The docs also describe holding a change or sending it back to a reviewer with a question, but do not print the values for those, so maintained.py only sends approve. Use the UI for the others until the values are documented.
- Signed with HMAC-SHA256; header
X-Subprocessor-Signature: t=<unix>,v1=<hex>. - The signed string is
t + "." + raw_body, keyed with your webhook secret. - Reject anything older than five minutes.
- Five retries with exponential backoff; any event replayable for thirty days from the dashboard.
| Event | Fires when |
|---|---|
discovery.completed |
A discovery finishes and is ready for you |
change.detected |
A list moved and a reviewer confirmed it is real |
change.quarantined |
Something moved but is held: a rebuild, or a mass removal |
change.published |
You approved it and it went live |
window.opening |
An objection window has started |
window.closing |
Seven days left, then one |
notice.received |
A vendor notice landed in your forwarding inbox |
The payload body fields are not published on the docs page, so the receivers in examples/webhooks/ verify the signature and store the verified raw body without routing on fields that are not documented.
The docs page lists these as not built at the time of reading. The examples here do not call any of them:
POST /v1/watchlistandDELETE /v1/watchlist/{domain}(manage the watchlist in the UI)POST /v1/evidence/packs(generate packs in the UI and download them there)POST /v1/discovery,/v1/me/subscribers,/v1/me/entities- Agency workspace endpoints
- SDKs, the sandbox and the CI action
The docs invite requests for any of these; see subprocessor.org/docs.