Discovery queue open Typical turnaround: one working day The register Method Talk to a reviewer
subprocessor.org
Six sources. One reviewed list. Re-checked daily.See how the work flows →
Same engine, different job to be done.Compare the tiers →
We publish observations and dated records, never legal conclusions.Read the method →
Free to read. No account.Open the register →
documentation

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.

base urlhttps://api.subprocessor.org/v1
sandboxhttps://sandbox.subprocessor.org/v1
statusall endpoints nominal

Your first request

No key, no account, no sign-up. This works right now from your terminal.

register lookup200 OK
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.

authenticated request
curl https://api.subprocessor.org/v1/me/pending \
  -H "Authorization: Bearer sk_live_9f2c…"
PrefixEnvironmentNotes
sk_live_ProductionActs on your real list and can send notices to your customers.
sk_test_SandboxFull 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.

paging
{
  "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.

304 Not Modified
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.

safe retry
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.

CallerRequests / minuteBurst
Unauthenticated60120
Maintained key6001,200
Agency key2,4004,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.

422 Unprocessable
{
  "error": {
    "type": "invalid_domain",
    "message": "northwind..io is not a resolvable domain",
    "doc_url": "https://subprocessor.org/docs#errors",
    "request_id": "req_01J9X2K7Q…"
  }
}
CodeMeansRetry?
400Malformed requestNo — fix and resend
401Missing or invalid keyNo
403Key is not scoped to that workspaceNo
404Not tracked, or not yoursNo
409Already decided, or already publishedNo — treat as success
422Understood but rejectedNo
429Rate limitedYes, after Retry-After
5xxOur fault, and loggedYes, with backoff

The register

Public. No key required, and no plans to change that.

GET/register/companiesSearch and page every business we track
GET/register/{domain}Current list, counts, countries, verification stamp
GET/register/{domain}/changesDated change history
GET/register/{domain}/history?at=2022-05-29The list as it stood on any past date
GET/register/{domain}/feed.atomAtom feed for one business
GET/entitiesEvery named sub-processor, and how many name it
GET/entities/{slug}/named-byReverse lookup, with country and purpose
GET/changesGlobal feed of list movements

Your business

Requires a key. Maintained plan.

GET/me/listYour current published list
GET/me/list.csvThe same list shaped for a DPA annex
GET/me/pendingChanges waiting on your decision, evidence attached
POST/me/pending/{id}/decisionApprove, hold, or send back to a reviewer

Watching vendors

Requires a key. Maintained plan.

GET/watchlistVendors you watch, with last-verified and entity count
GET/windowsOpen objection windows and the clause each came from

Evidence and the ledger

Packs need a key. Ledger verification is public, deliberately.

GET/ledger/{row}Verify any record against the hash chain

Anyone 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.

EventFires when
discovery.completedA discovery finishes and is ready for you
change.detectedA list moved and a reviewer has confirmed it is real
change.quarantinedSomething moved but is held — a rebuild, or a mass removal
change.publishedYou approved it and it went live
window.openingAn objection window has started
window.closingSeven days left, then one
notice.receivedA vendor notice landed in your forwarding inbox
verify the signature — node
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. reviewed is true.
  • observed — a dated copy of what the company publishes on its own site. Nobody reviewed it and the business did not approve it. reviewed is false.

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/watchlist and DELETE — 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.