CleanMyDatabaseFree app for your phone

API

Clean data inside your own platform.

Send us a file from your software, poll until it is done, download the cleaned file. Credits only, no card in the loop, and a clean never starts unless the balance covers it in full.

Getting started

Three calls do everything: send a file, ask whether it is finished, download the result. Everything is ordinary HTTPS and multipart form uploads — there is no SDK to install and nothing unusual to support.

You need two things:

  • An account at cleanmydatabase.com with credits on it. Create one free.
  • An API key, made in Your account → API keys.
Cleans are paid for with credits, never a card. We count exactly how many lookups your file needs, take that many credits before any work starts, and reject the whole job if your balance is short. Nothing runs part-paid, and you are never billed for a file you didn’t authorise.

Base URL for every endpoint:

https://cleanmydatabase.com/api/v1/

Authentication

Send your key as a bearer token on every request.

Authorization: Bearer cmd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

If your HTTP client or hosting strips the Authorization header, send the key as X-API-Key instead — we accept either.

Treat the key like a password: it can spend your credits. Keep it on your server, never in browser or mobile app code. You can revoke a key at any time from your account, and it stops working immediately.

Checks available

Pass these as a comma-separated services value. Combine as many as you like in one file.

ValueCheckNeedsColumns added to your file
tps TPS / CTPS screening A phone column On TPS, On CTPS, On DNC
hlr HLR mobile validation A phone column Mobile Live/Dead, Network, Switched On
email Email validation An email column Email Quality, Email Reason, Free Email Service, Disposable Email, Role Address, Catch-all Domain

Other checks exist but are switched on per account. Ask us if you need one that isn’t listed.

Send a file

POST https://cleanmydatabase.com/api/v1/jobs.php

A multipart/form-data upload. CSV or XLSX, up to 45MB. Every column in your file is kept and returned; we add the result columns alongside.

Fields

FieldRequiredWhat it does
fileYesThe list itself, as a file upload.
servicesYesWhich checks to run, e.g. tps,hlr.
phone_columnNoThe column holding phone numbers — a header name (Mobile) or a number starting at 0. Left out, we work it out from the file.
email_columnNoSame, for email addresses.
has_headerNo1, 0, or auto (the default — we detect it).
countryNoWhere the numbers are from, e.g. GB, US. Only used for numbers that don’t already carry a country code. Defaults to GB.
referenceNoAnything you want handed back on every status response — your own batch id, for example.
notify_emailNoEmail a download link to a person as well. Left out, nobody is emailed and your software just polls.

Example

curl -X POST https://cleanmydatabase.com/api/v1/jobs.php \
  -H "Authorization: Bearer cmd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -F "file=@leads.csv" \
  -F "services=tps,hlr" \
  -F "phone_column=Mobile" \
  -F "reference=batch-4471"

Response 201 Created

{
  "job": {
    "id": "k3Rd8Qx2mVaP7yLsT1wNbZfH",
    "status": "running",
    "reference": "batch-4471",
    "filename": "leads.csv",
    "services": ["tps", "hlr"],
    "rows": 4820,
    "processed": 0,
    "percent": 0,
    "credits_used": { "tps": 4820, "hlr": 4796 },
    "created_at": "2026-09-21T14:02:55+01:00",
    "status_url": "https://cleanmydatabase.com/api/v1/job.php?id=k3Rd8Qx2mVaP7yLsT1wNbZfH",
    "result_url": null
  }
}

credits_used is what was taken from your balance. Note that the two numbers can differ: we only charge for rows that actually have a value in the column being checked, so blank cells cost nothing.

Check progress

GET https://cleanmydatabase.com/api/v1/job.php?id=JOB_ID

Poll this until status is complete. Every 10 seconds is plenty; each call also pushes the clean along, so small files often finish on the second poll. Large files run at roughly 20,000–40,000 lookups an hour.

{
  "job": {
    "id": "k3Rd8Qx2mVaP7yLsT1wNbZfH",
    "status": "complete",
    "reference": "batch-4471",
    "rows": 4820,
    "processed": 4820,
    "percent": 100,
    "summary": {
      "records": 4820,
      "ok_to_contact": 3611,
      "with_errors": 0,
      "tps_or_ctps_registered": 702,
      "dead_mobiles": 486,
      "invalid_mobiles": 21,
      "not_mobiles": 0
    },
    "completed_at": "2026-09-21T14:19:02+01:00",
    "result_url": "https://cleanmydatabase.com/api/v1/result.php?id=k3Rd8Qx2mVaP7yLsT1wNbZfH"
  }
}

To list your recent jobs instead, GET /api/v1/jobs.php (optionally ?limit=50).

Download results

GET https://cleanmydatabase.com/api/v1/result.php?id=JOB_ID

Returns the cleaned file itself as text/csv, not JSON. Your original columns come back untouched and in the same order, followed by a column per check, then Check Errors and Recommendation.

curl -L "https://cleanmydatabase.com/api/v1/result.php?id=JOB_ID" \
  -H "Authorization: Bearer cmd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -o leads_cleaned.csv

Recommendation is the column to key your automation off. It reads in plain English — OK to contact, Do not call – TPS, Dead mobile, Do not email — so you can map it straight to a disposition without parsing anything.

Results are kept for 30 days and then deleted automatically. Download them within that window.

Balance & prices

GET https://cleanmydatabase.com/api/v1/account.php

Worth calling before you send a large file, so you can top up rather than have the job rejected.

{
  "account": { "company": "Acme Contact Centre", "email": "ops@acme.co.uk" },
  "checks": {
    "tps": { "name": "TPS / CTPS screening", "needs": "phone", "credits": 128400, "price_per_record": 0.005, "currency": "GBP" },
    "hlr": { "name": "HLR mobile validation", "needs": "phone", "credits": 96120, "price_per_record": 0.008, "currency": "GBP" }
  },
  "limits": { "max_file_bytes": 47185920, "max_running_jobs": 5, "results_kept_days": 30 }
}

Errors

Anything that isn’t a success returns the same shape, with a machine-readable code and a message you can safely show your own users.

{
  "error": {
    "code": "insufficient_credits",
    "message": "Not enough credits for this file. Nothing has been charged and no lookups were made.",
    "details": {
      "required": { "tps": 4820, "hlr": 4796 },
      "short": { "hlr": { "needed": 4796, "balance": 1200, "shortfall": 3596 } }
    }
  }
}
StatusCodeWhat to do
401missing_key / invalid_keyCheck the Authorization header, and that the key hasn’t been revoked.
400missing_columnWe couldn’t find the column to check. Name it with phone_column or email_column — the response lists your file’s headers.
400bad_fileNot a readable CSV/XLSX, or empty.
402insufficient_creditsTop up and send the file again. Nothing was charged and no lookups ran.
409job_not_finishedKeep polling the status URL.
410result_expiredPast the 30-day retention window. The file would need cleaning again.
413file_too_largeSplit the file and send the parts.
429too_many_running_jobsWait for one of your cleans to finish. Usually means a loop at your end sent the same file repeatedly.

A complete example

Send a file, wait for it, save the result. PHP, no libraries:

<?php
$key  = 'cmd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$base = 'https://cleanmydatabase.com/api/v1';
$auth = array('Authorization: Bearer ' . $key);

// 1. Send the file
$ch = curl_init($base . '/jobs.php');
curl_setopt_array($ch, array(
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => $auth,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS     => array(
        'file'         => new CURLFile('leads.csv'),
        'services'     => 'tps,hlr',
        'phone_column' => 'Mobile',
    ),
));
$job = json_decode(curl_exec($ch), true);
curl_close($ch);

if (isset($job['error'])) {
    exit('Could not start: ' . $job['error']['message'] . "\n");
}
$id = $job['job']['id'];

// 2. Wait for it
do {
    sleep(10);
    $ch = curl_init($base . '/job.php?id=' . urlencode($id));
    curl_setopt_array($ch, array(CURLOPT_HTTPHEADER => $auth, CURLOPT_RETURNTRANSFER => true));
    $status = json_decode(curl_exec($ch), true);
    curl_close($ch);
    echo $status['job']['percent'] . "%\n";
} while ($status['job']['status'] !== 'complete');

// 3. Save the cleaned file
$ch = curl_init($base . '/result.php?id=' . urlencode($id));
curl_setopt_array($ch, array(CURLOPT_HTTPHEADER => $auth, CURLOPT_RETURNTRANSFER => true));
file_put_contents('leads_cleaned.csv', curl_exec($ch));
curl_close($ch);

The same in Python:

import time, requests

key  = 'cmd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
base = 'https://cleanmydatabase.com/api/v1'
head = {'Authorization': 'Bearer ' + key}

job = requests.post(base + '/jobs.php', headers=head,
        files={'file': open('leads.csv', 'rb')},
        data={'services': 'tps,hlr', 'phone_column': 'Mobile'}).json()

if 'error' in job:
    raise SystemExit(job['error']['message'])

job_id = job['job']['id']

while True:
    time.sleep(10)
    status = requests.get(base + '/job.php', headers=head, params={'id': job_id}).json()
    print(status['job']['percent'], '%')
    if status['job']['status'] == 'complete':
        break

csv = requests.get(base + '/result.php', headers=head, params={'id': job_id})
open('leads_cleaned.csv', 'wb').write(csv.content)
Running alongside your current supplier? Send the same file to both for a month and compare the results side by side. Most platforms can be pointed at a second cleaner without touching the first. Talk to us and we’ll set up a trial balance.

Plug it in and stop thinking about it.

Create an account, make a key and send your first file today. There is no set-up fee and no contract.