One JSON API over the register
and your own account.
The register is readable without a key, because a register nobody can query is not a register. Anything about your business needs one. Everything below is versioned, cursor-paginated and returns the same hashes we publish, so you can verify us rather than trust us.
https://api.subprocessor.org/v1https://sandbox.subprocessor.org/v1all endpoints nominalYour first request
No key, no account, no sign-up. This works right now from your terminal.
curl https://api.subprocessor.org/v1/register/atlassian.com { "company": "atlassian.com", "verified_at": "2026-08-17T06:12:04Z", "entity_hash": "427b2945ef", "shape": "table", "count": 29, "history_from": "2019-02-10", "entities": [ … ] }
Every response carries entity_hash. If it has not changed, the list has
not changed — regardless of how much of the vendor’s page was rewritten.
Authentication
A bearer token on every request that touches your account. Keys are created in
Settings → API, scoped to a workspace, and shown once.
curl https://api.subprocessor.org/v1/me/pending \ -H "Authorization: Bearer sk_live_9f2c…"
| Prefix | Environment | Notes |
|---|---|---|
sk_live_ | Production | Acts on your real list and can send notices to your customers. |
sk_test_ | Sandbox | Full API against seeded data. Nothing leaves the sandbox. |
A live key can email your customers. Notices only go out after an explicit
approval call, but treat the key accordingly — scope it, rotate it, and use sk_test_
while you are building.
Versioning
Every path sits under /v1. Additive changes — new fields, new endpoints, new
enum values — ship without a version bump, so parse defensively and ignore what you do not
recognise. Anything breaking gets /v2 and twelve months of overlap.
Pagination
Cursor-based, because the change feed grows while you are reading it and offsets drift. Pass
limit up to 200; the default is 50.
{ "data": [ … ], "has_more": true, "next_cursor": "c_01J9X2K…" }
Conditional reads
Poll as often as you like. Send the ETag back and an unchanged resource answers 304
with no body — and 304s do not count against your rate limit. Polling a list that has not
moved is effectively free.
curl -i https://api.subprocessor.org/v1/register/atlassian.com \ -H 'If-None-Match: "427b2945ef"' HTTP/2 304 etag: "427b2945ef" x-ratelimit-remaining: 587 // unchanged
Idempotency
Every POST accepts Idempotency-Key, held for 24 hours. A retry after a
timeout returns the original result rather than acting twice — which matters most on approvals,
since approving twice could mean emailing your customers twice.
curl -X POST …/v1/me/pending/ch_8e12/decision \ -H "Idempotency-Key: 6f2c1a90-…" \ -d '{"decision":"approve"}'
Rate limits
Returned on every response rather than discovered by being cut off, and enforced — a 429 carries Retry-After in seconds.
| Caller | Requests / minute | Burst |
|---|---|---|
| Unauthenticated | 60 | 120 |
| Maintained key | 600 | 1,200 |
| Agency key | 2,400 | 4,800 |
Headers: X-RateLimit-Limit, X-RateLimit-Remaining,
X-RateLimit-Reset. A 429 includes Retry-After in seconds.
Errors
Consistent shape, with a request_id to quote at us if something looks wrong.
{ "error": { "type": "invalid_domain", "message": "northwind..io is not a resolvable domain", "doc_url": "https://subprocessor.org/docs#errors", "request_id": "req_01J9X2K7Q…" } }
| Code | Means | Retry? |
|---|---|---|
| 400 | Malformed request | No — fix and resend |
| 401 | Missing or invalid key | No |
| 403 | Key is not scoped to that workspace | No |
| 404 | Not tracked, or not yours | No |
| 409 | Already decided, or already published | No — treat as success |
| 422 | Understood but rejected | No |
| 429 | Rate limited | Yes, after Retry-After |
| 5xx | Our fault, and logged | Yes, with backoff |
The register
Public. No key required, and no plans to change that.
/register/companiesSearch and page every business we track/register/{domain}Current list, counts, countries, verification stamp/register/{domain}/changesDated change history/register/{domain}/history?at=2022-05-29The list as it stood on any past date/register/{domain}/feed.atomAtom feed for one business/entitiesEvery named sub-processor, and how many name it/entities/{slug}/named-byReverse lookup, with country and purpose/changesGlobal feed of list movementsYour business
Requires a key. Maintained plan.
/me/listYour current published list/me/list.csvThe same list shaped for a DPA annex/me/pendingChanges waiting on your decision, evidence attached/me/pending/{id}/decisionApprove, hold, or send back to a reviewerWatching vendors
Requires a key. Maintained plan.
/watchlistVendors you watch, with last-verified and entity count/windowsOpen objection windows and the clause each came fromEvidence and the ledger
Packs need a key. Ledger verification is public, deliberately.
/ledger/{row}Verify any record against the hash chainAnyone can verify a record without an account — your auditor, or a customer of yours checking a notice you sent. That is the point of publishing the chain.
Webhooks
Signed with HMAC-SHA256, five retries with exponential backoff, and 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 has 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 |
const sig = req.headers['x-subprocessor-signature']; // t=1786999933,v1=5f3a… const [t, v1] = sig.split(',').map(p => p.split('=')[1]); const expected = crypto .createHmac('sha256', process.env.SUBPROCESSOR_SECRET) .update(t + '.' + rawBody) .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) { return res.status(400).end(); } // reject anything older than five minutes
Maintained and observed
Every register response carries source, and it changes what the record means.
maintained— assembled by discovery, confirmed by a named reviewer, and approved by the business before publication.reviewedistrue.observed— a dated copy of what the company publishes on its own site. Nobody reviewed it and the business did not approve it.reviewedisfalse.
Do not treat them alike. An observed record is evidence of what was published on a date, and nothing more — it is not a claim that the list is complete or correct.
Not built yet
Listed because a documented endpoint that does not answer is worse than an absent one, and this page is the contract.
POST /v1/watchlistandDELETE— add and remove vendors in the UI for now.POST /v1/evidence/packs— generate packs in the UI; the files are downloadable there.POST /v1/discovery,/v1/me/subscribers,/v1/me/entities.- Agency workspace endpoints — the tier itself is not built.
- SDKs, the sandbox and the CI action.
If one of these is what you need, say so and it moves up the list. They are absent because nothing was built on top of them yet, not because they are hard.
Something missing?
The API is built against real use rather than guessed at. If an endpoint you need is not here, tell us what you are trying to do and we will tell you honestly whether it exists yet.