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
Authorizationheader - 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)
| Method | Endpoint | What it does | Counts as a call |
|---|---|---|---|
POST | /email | Breaches an email address appears in, with full details | Yes |
GET | /breaches | Every documented breach in the database | Yes |
GET | /usage | Your package and today’s usage | No |
2. Getting started
- Create an account and choose a package — API access is included with every package, including the free one.
- Go to My account → API & usage and create an API key. Copy it straight away: it is shown only once.
- Make your first request:
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:
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\"}"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.)
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
{
"email": "jane@example.com"
}Response
{
"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"
}| Field | Type | Description |
|---|---|---|
email | string | The address checked, normalised to lower case. |
found | boolean | true if the address appears in at least one breach. |
breach_count | integer | Number of breaches, including undocumented ones. |
undocumented_breaches | integer | Breaches we hold the address for but haven’t documented yet (no details available). |
breaches[].date | string | null | When the breach happened: YYYY-MM-DD, YYYY-MM or YYYY, depending on how precisely it is known. null if unknown. |
breaches[].records | integer | null | Total records in the breach, if known. |
breaches[].data_exposed | string[] | Types of data exposed in the breach. |
breaches[].description | string | null | What happened. |
breaches[].advice | string | null | What 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.
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number. |
per_page | 100 | Results per page, up to 500. |
q | — | Only breaches whose name contains this text. |
{
"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.
{
"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.
| Package | Searches or API calls / day | PDF reports / day |
|---|---|---|
| Leak Database Starter | 10 | 10 |
| Leak Database Small Business | 500 | 500 |
| Leak Database Medium Business | 5,000 | 5,000 |
| Leak Database Large Business | 50,000 | 50,000 |
Every response includes your allowance in its 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:
{
"error": {
"code": "invalid_api_key",
"message": "This API key is not valid or has been revoked.",
"status": 401
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | missing_email, invalid_json, key_in_url | The request is incomplete or malformed. |
| 401 | missing_api_key, invalid_api_key | No key, or the key is wrong or revoked. |
| 403 | no_api_access | Your account has no active package with API access. |
| 405 | method_not_allowed | Wrong HTTP method for this endpoint. |
| 422 | invalid_email | The value isn’t a valid email address. |
| 429 | daily_quota_exceeded, too_many_requests | Daily allowance of searches and API calls used, or too many requests in a short time. Wait for Retry-After seconds. |
| 503 | unavailable | Temporary problem on our side. Retry with back-off. |
9. Code examples
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
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
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#
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.