Let's get the awkward bit out of the way first. There isn't an official TPS register API. The DMA, which operates the TPS and CTPS on behalf of the ICO, does not publish a public REST endpoint you can hit with a phone number to ask "is this on the list?". If a vendor tells you they plug straight into a TPS API, they're being loose with their words.
What the DMA actually provides
The DMA licenses a daily file called the TPSL. It's a flat list of the numbers currently registered on the TPS, with an equivalent file for the CTPS (the corporate register). Licensed list cleaners download it on a rolling basis, typically every 24 hours, and the contents refresh on roughly a 28-day cycle as new registrations come in and lapsed ones drop out.
To get hold of the TPSL you need a list-cleaner licence from the DMA. That's a contractual relationship, not a sign-up form. The licence sets out how you may store the data, how often you must refresh it, and the audit obligations that go with it. You can read the policy background on the DMA's site.
Why direct access isn't open
Two reasons. The first is licensing. The TPS file is a commercial dataset with rules attached, and an open API would make those rules unenforceable. The second is anti-scraping. A public lookup endpoint, even rate-limited, would let anyone reconstruct the register one number at a time. That's the opposite of what a do-not-call register is meant to be.
So if you want to screen numbers programmatically, you have two real options. Become a licensed list cleaner yourself, which is a non-trivial commitment, or call a licensed list cleaner's API and let them carry the licence and the audit burden.
How list cleaners actually work under the hood
The mechanics are unglamorous and that's a feature, not a bug. A compliant list cleaner does roughly this:
- Pulls the TPSL and CTPSL files once a day from the DMA.
- Ingests them into a fast lookup structure, usually a hashed set or an indexed table keyed by E.164 number.
- On each request, normalises the inbound number (strip spaces, force E.164, validate country code) and checks both sets.
- Returns a verdict, plus the timestamp the underlying file was refreshed, so the caller can prove the screen was current.
- Writes an audit row: which number, what verdict, which file version, which API key, which timestamp.
The lookup is local because the source file is local. There's no live round-trip to the DMA per number, and there couldn't be.
What a sensible developer API on top looks like
TPSClear sits in that second category. We hold the licence, we refresh the file daily, and we expose a thin HTTPS API on top so you can screen from your own backend, your CRM, or whatever pipeline you've got. The design goals are boring on purpose: predictable shapes, idempotent calls, an audit row per request.
Auth
API keys for server-to-server use, scoped tokens for narrower contexts (a single workflow, a single source system). Keys are revocable from the dashboard and every call carries the key's identifier into the audit log so you can trace who screened what.
Endpoints (illustrative)
A single-number or small-batch screen looks like this:
POST /v1/screen
Authorization: Bearer sk_live_...
Content-Type: application/json
{
"phoneNumbers": ["+447700900123", "+442071838750"]
}Response (illustrative):
{
"results": [
{
"phoneNumber": "+447700900123",
"tpsListed": true,
"ctpsListed": false,
"checkedAt": "2026-05-08T09:14:22Z",
"fileVersion": "tpsl-2026-05-08"
},
{
"phoneNumber": "+442071838750",
"tpsListed": false,
"ctpsListed": true,
"checkedAt": "2026-05-08T09:14:22Z",
"fileVersion": "tpsl-2026-05-08"
}
]
}For larger jobs there's a bulk endpoint that accepts a list and either streams results back or writes them to a job you can poll. The shape of each result is identical to the single-screen response, which keeps client code simple.
POST /v1/screen/bulk
{ "phoneNumbers": [ /* up to 50,000 per job */ ] }
-> { "jobId": "scr_01HYZ...", "status": "queued" }
GET /v1/screen/bulk/scr_01HYZ...
-> { "status": "complete", "results": [ ... ] }Re-screening is the part most homegrown systems get wrong. Numbers registered yesterday are listed today. A webhook callback handles this without you having to re-poll every record:
POST https://your-app.example.com/webhooks/tpsclear
{
"event": "rescreen.changed",
"phoneNumber": "+447700900123",
"previous": { "tpsListed": false, "ctpsListed": false },
"current": { "tpsListed": true, "ctpsListed": false },
"checkedAt": "2026-05-08T09:14:22Z"
}Rate limits and fair use
Per-key rate limits, with bulk jobs metered separately so a long overnight cleanse doesn't starve real-time calls. If you need sustained throughput beyond the default, that's a conversation, not a paywall.
Audit trail
Every screen, single or bulk, writes a row you can export: phone number, verdict, file version, API key, timestamp, request ID. That export is what you hand the ICO if a complaint ever reaches them. Saying "we use a list cleaner" is not a defence on its own. Showing the row that proves the number was screened against the current file before you dialled is.
Integrating without a CRM
Most of our sign-ups come through native CRM integrations, but plenty of teams don't have a CRM in the loop. Predictive diallers, in-house lead platforms, custom outbound tooling. The raw API is built for those. You screen on import, you re-screen before each campaign, you respect the webhook when a number flips. That's the whole job.
If you're weighing up the alternative of building this yourself, I've written separately about the trade-offs in build vs buy for TPS checks. Short version: the DMA licence is the hard part, not the code.
If you'd like keys and the full reference, see /developers, or get in touch via /contact and I'll walk you through fit.