Developer reference

JSON API reference

The Breachtide JSON API exposes the same scan state your dashboard renders, scoped to the monitored email addresses you own. It is read-only, JSON over HTTPS, and authenticated with a per-account Bearer token issued from your settings page.

Machine-readable spec: /openapi.json. Drop it into any OpenAPI 3.1 client to generate types or a SDK.

Download Postman collectionDownload OpenAPI spec

Base URL

ProductionHTTPS
https://breachtide.com/api/v1

Authentication

Issue a token from /dashboard/settings. Send it on every request as a Bearer token in the Authorization header. The token is shown once at issue time; only its hash is stored. Rotate to invalidate the old token and issue a new one; revoke to disable API access entirely.

Authorization headerrequired on every request
Authorization: Bearer bt_ak_<your-token>

Requests with a missing or unrecognised token return 401 unauthorized.

Rate limit

60 requests per minute per API key, in a fixed one-minute window. Every response carries three headers so a client can pace itself without polling for limits:

X-RateLimit-Limitinteger
Total requests allowed in the current window.
X-RateLimit-Remaininginteger
Requests left in the window.
X-RateLimit-Resetinteger (unix seconds)
When the window resets and the budget refills.

Exceeding the limit returns 429 rate_limited. Wait until the reset and retry.

GET /me

Identity check. Returns the authenticated user, the email on file, and the active plan. Useful as a connectivity probe and for surfacing plan-gated features in your client.

GET /api/v1/me200 application/json
{
  "id": "u_01HW8N5XYZABCDEF",
  "email": "[email protected]",
  "plan": "pro"
}

The plan field is one of free, personal, pro, or business. Users with no active subscription are reported as free.

GET /breaches

Returns the per-source scan state for one monitored email address you own. The address must already be added and verified on your account; unverified or unknown addresses are rejected before any data is returned.

GET /api/v1/[email protected]query parameter required
{
  "email": "[email protected]",
  "sources": [
    {
      "source": "archive",
      "breach_count": 3,
      "source_dbs": [
        "LinkedIn2016",
        "Dropbox2012",
        "MyFitnessPal2018"
      ],
      "fields_exposed": [
        "password",
        "username"
      ],
      "has_plaintext_password": true,
      "last_scanned_at": "2026-05-08T11:42:18.000Z",
      "last_changed_at": "2026-04-30T03:11:02.000Z"
    },
    {
      "source": "enriched",
      "breach_count": 1,
      "source_dbs": [
        "combolist.2026-04"
      ],
      "fields_exposed": [
        "password"
      ],
      "has_plaintext_password": true,
      "last_scanned_at": "2026-05-08T11:42:18.000Z",
      "last_changed_at": "2026-05-02T17:08:44.000Z"
    }
  ]
}

Each entry in sources is the latest sweep result from one of the four feeds: archive (historical breach corpora), combolist (live credential combolists), index (continuously refreshed public breach index), and enriched (field-level breach detail on confirmed hits). Only sources that have been scanned at least once appear in the array.

Field reference

sourceenum
Which of the four feeds produced this state. One value per feed in the response.archivecombolistindexenriched
breach_countinteger
Number of distinct source databases that referenced the address in the most recent sweep.
source_dbsstring[]
The named breaches or combolists where the address appeared, deduplicated and sorted.
fields_exposedenum[]
Which categories of value were found alongside the address.passwordhashusernamenamedobaddressphoneip
has_plaintext_passwordboolean
True when at least one source returned a plaintext password, not just a hash. The highest-severity signal in the pipeline.
last_scanned_atstring (date-time)
When the source was last queried, regardless of whether anything changed.
last_changed_atstring (date-time)
When the sorted result hash last differed from the previous sweep. Equal to last_scanned_at on the first scan; otherwise reflects the most recent material change.

Errors

400
missing_email
The email query parameter was not provided.
403
not_verified
The address is on this account but the verification email has not been confirmed. Resend from the email detail page.
404
not_monitored
The address is not on this account. Add it from the emails page first.

Examples

curl

shellposix
curl -sS \
  -H "Authorization: Bearer $BREACHTIDE_API_KEY" \
  "https://breachtide.com/api/v1/[email protected]"

Node.js

node-fetch.jsNode 20+
const token = process.env.BREACHTIDE_API_KEY;
const url = new URL('https://breachtide.com/api/v1/breaches');
url.searchParams.set('email', '[email protected]');

const res = await fetch(url, {
  headers: { Authorization: `Bearer ${token}` },
});

if (res.status === 429) {
  const reset = Number(res.headers.get('x-ratelimit-reset')) * 1_000;
  await new Promise((r) => setTimeout(r, reset - Date.now()));
}

if (!res.ok) {
  throw new Error(`breachtide ${res.status}: ${await res.text()}`);
}

const body = await res.json();
console.log(body.sources);

Python

breachtide_client.pyPython 3.10+
import os
import time
import httpx

token = os.environ["BREACHTIDE_API_KEY"]
client = httpx.Client(
    base_url="https://breachtide.com/api/v1",
    headers={"Authorization": f"Bearer {token}"},
)

resp = client.get("/breaches", params={"email": "[email protected]"})

if resp.status_code == 429:
    reset = int(resp.headers["X-RateLimit-Reset"])
    time.sleep(max(0, reset - int(time.time())))
    resp = client.get("/breaches", params={"email": "[email protected]"})

resp.raise_for_status()
print(resp.json()["sources"])

Operational notes

Ready to plug in?

Issue a key from your settings page and run the curl example above.

Go to settings