Developers

Leak Database API

Check email addresses against known data breaches from your own systems — during onboarding, fraud checks, password resets or security reviews. A simple, secure JSON API, included with every package.

Get your API key See packages

1. Overview

The API is a REST interface that returns JSON. Every request must be made over HTTPS and authenticated with an API key that belongs to your Leak Database account.

Base URL
https://www.leakdatabase.com/api/v1
Format
JSON (UTF-8). Send request bodies as application/json.
Authentication
API key in the Authorization header
Allowance
A daily number of searches or API calls (shared with website searches) set by your package, resetting at midnight South African time (UTC+2)
MethodEndpointWhat it doesCounts as a call
POST/emailBreaches an email address appears in, with full detailsYes
GET/breachesEvery documented breach in the databaseYes
GET/usageYour package and today’s usageNo

2. Getting started

  1. Create an account and choose a package — API access is included with every package, including the free one.
  2. Go to My account → API & usage and create an API key. Copy it straight away: it is shown only once.
  3. Make your first request:
Linux / macOS terminal
curl -X POST https://www.leakdatabase.com/api/v1/email \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@example.com"}'

On Windows the quoting is different:

Windows Command Prompt
curl -X POST https://www.leakdatabase.com/api/v1/email -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" -d "{\"email\": \"jane@example.com\"}"
Windows PowerShell
Invoke-RestMethod -Method Post -Uri "https://www.leakdatabase.com/api/v1/email" `
  -Headers @{ Authorization = "Bearer YOUR_API_KEY" } `
  -ContentType "application/json" `
  -Body (@{ email = "jane@example.com" } | ConvertTo-Json)

3. Authentication

Send your key in the Authorization header as a bearer token. (The X-API-Key header is also accepted.)

HTTP header
Authorization: Bearer ldk_3f9a…
  • Keep keys on your server. Never put a key in a website’s JavaScript, a mobile app or a public repository. Browser requests are not supported.
  • Never put a key in a URL. Requests with a key in the query string are refused, because URLs end up in logs.
  • Use a separate key for each system. You can have up to 5 keys and revoke any of them at any time; all keys share your account’s daily allowance.
  • Keys stop working automatically if your package ends.

4. Check an email address

POST /api/v1/email — returns every documented breach the address appears in. The address goes in the request body, so it never appears in URLs or server logs.

Request

POST https://www.leakdatabase.com/api/v1/email
{
  "email": "jane@example.com"
}

Response

200 OK
{
  "email": "jane@example.com",
  "found": true,
  "breach_count": 2,
  "undocumented_breaches": 0,
  "breaches": [
    {
      "id": 1,
      "name": "ExampleShop Online",
      "date": "2025-03-14",
      "date_label": "14 Mar 2025",
      "records": 1250000,
      "data_exposed": ["Email addresses", "Phone numbers", "Physical addresses"],
      "description": "In March 2025, attackers gained access to …",
      "advice": "Change your ExampleShop password and any account where you reused it."
    },
    {
      "id": 2,
      "name": "SampleForum",
      "date": "2024-11",
      "date_label": "November 2024",
      "records": 483210,
      "data_exposed": ["Email addresses", "Usernames", "Password hashes"],
      "description": "…",
      "advice": "…"
    }
  ],
  "checked_at": "2026-10-30T20:51:00+00:00"
}
FieldTypeDescription
emailstringThe address checked, normalised to lower case.
foundbooleantrue if the address appears in at least one breach.
breach_countintegerNumber of breaches, including undocumented ones.
undocumented_breachesintegerBreaches we hold the address for but haven’t documented yet (no details available).
breaches[].datestring | nullWhen the breach happened: YYYY-MM-DD, YYYY-MM or YYYY, depending on how precisely it is known. null if unknown.
breaches[].recordsinteger | nullTotal records in the breach, if known.
breaches[].data_exposedstring[]Types of data exposed in the breach.
breaches[].descriptionstring | nullWhat happened.
breaches[].advicestring | nullWhat affected people should do.

Breaches are ordered newest first. An address that isn’t found returns "found": false and an empty breaches list — not an error.

5. List breaches

GET /api/v1/breaches — every documented breach, newest first, without the long descriptions.

ParameterDefaultDescription
page1Page number.
per_page100Results per page, up to 500.
q—Only breaches whose name contains this text.
200 OK
{
  "page": 1,
  "per_page": 100,
  "total": 17,
  "pages": 1,
  "breaches": [
    { "id": 17, "name": "NewCo Rewards", "date": "2026-10-20", "date_label": "20 Oct 2026",
      "records": 2100000, "data_exposed": ["Email addresses", "Phone numbers", "Passwords"] }
  ]
}

6. Check your usage

GET /api/v1/usage — your package’s limits and what you’ve used today. This call is free: it doesn’t count towards your allowance.

200 OK
{
  "packages": [3],
  "today": {
    "searches_and_api_calls": {
      "limit": 500, "used": 49, "remaining": 451,
      "api_calls": 42, "website_searches": 7
    },
    "pdf_reports": { "limit": 500, "used": 1, "remaining": 499 }
  },
  "monitoring": { "email_addresses": 5, "domains": 5 },
  "resets_at": "2026-10-31T00:00:00+02:00"
}

7. Limits and packages

Each package includes a daily allowance of searches or API calls, shared between the website and the API: every search on the website and every successful /email or /breaches request uses one. Requests that fail validation or authentication don’t. If your account has more than one package, the highest allowance applies.

PackageSearches or API calls / dayPDF reports / day
Leak Database Starter1010
Leak Database Small Business500500
Leak Database Medium Business5,0005,000
Leak Database Large Business50,00050,000

Compare packages and prices →

Every response includes your allowance in its headers:

Response headers
X-RateLimit-Limit: 500        # searches + API calls per day in your package
X-RateLimit-Remaining: 451    # left today (website and API together)
X-RateLimit-Reset: 11340      # seconds until midnight (UTC+2)

To protect the service there is also a short-term limit of ten times your package’s average rate per minute (at least 60 requests a minute). Spread large jobs over the day rather than sending them all at once. When you hit either limit you get 429 with a Retry-After header.

8. Errors

Errors use standard HTTP status codes and a JSON body:

401 Unauthorized
{
  "error": {
    "code": "invalid_api_key",
    "message": "This API key is not valid or has been revoked.",
    "status": 401
  }
}
StatusCodeMeaning
400missing_email, invalid_json, key_in_urlThe request is incomplete or malformed.
401missing_api_key, invalid_api_keyNo key, or the key is wrong or revoked.
403no_api_accessYour account has no active package with API access.
405method_not_allowedWrong HTTP method for this endpoint.
422invalid_emailThe value isn’t a valid email address.
429daily_quota_exceeded, too_many_requestsDaily allowance of searches and API calls used, or too many requests in a short time. Wait for Retry-After seconds.
503unavailableTemporary problem on our side. Retry with back-off.

9. Code examples

PHP

PHP
<?php
$apiKey = getenv('LEAKDATABASE_API_KEY');   // keep the key out of your code

$ch = curl_init('https://www.leakdatabase.com/api/v1/email');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode(['email' => 'jane@example.com']),
]);
$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status === 200) {
    echo $data['found'] ? "Found in {$data['breach_count']} breaches\n" : "Not found\n";
} else {
    echo "Error {$status}: {$data['error']['message']}\n";
}

Python

Python (requests)
import os, requests

resp = requests.post(
    "https://www.leakdatabase.com/api/v1/email",
    headers={"Authorization": f"Bearer {os.environ['LEAKDATABASE_API_KEY']}"},
    json={"email": "jane@example.com"},
    timeout=15,
)
if resp.status_code == 429:
    print("Limit reached, retry after", resp.headers.get("Retry-After"), "seconds")
resp.raise_for_status()
data = resp.json()
for b in data["breaches"]:
    print(b["name"], b["date_label"], ", ".join(b["data_exposed"]))

Node.js

Node.js 18+
const res = await fetch("https://www.leakdatabase.com/api/v1/email", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.LEAKDATABASE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ email: "jane@example.com" }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data.found ? `Found in ${data.breach_count} breaches` : "Not found");
console.log("Calls left today:", res.headers.get("X-RateLimit-Remaining"));

C#

C# (.NET 6+)
using System.Net.Http.Json;

var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new("Bearer", Environment.GetEnvironmentVariable("LEAKDATABASE_API_KEY"));

var res = await http.PostAsJsonAsync("https://www.leakdatabase.com/api/v1/email", new { email = "jane@example.com" });
var json = await res.Content.ReadAsStringAsync();
Console.WriteLine($"{(int)res.StatusCode}: {json}");

10. Responsible use

  • Only check addresses you have a legitimate reason to check — for example your own customers, users or staff — and tell them how you use breach checks, as POPIA requires.
  • Use results to protect people: prompt a password reset, add verification steps, or warn about phishing. Don’t use them to deny someone a service on their own.
  • Don’t store results longer than you need them, and don’t resell or republish them.
  • Cache results for a reasonable time instead of re-checking the same address many times a day.

Using the API means you accept our terms of use. Questions or a need for higher limits? Contact us.