BreachSpot API

REST API application for credential search and breach data analysis. It provides comprehensive search capabilities across multiple data sources including Credshed, Twitter, and various breach incident collections. The API supports dual authentication systems for different client types and includes advanced filtering, pagination, and credit-based access control.

  • Credential exposure search across multiple breach collections (email, domain, or username)
  • Monitors (watch lists) that track search terms over time
  • Underground intelligence (threat actor groups, leak-site posts, markets, and attributed incident feeds)

Base URL:

https://api.breachspot.com

Interactive documentation

Swagger UI: https://api.breachspot.com/docs/
OpenAPI JSON: https://api.breachspot.com/docs.json

All examples below use the production base URL. Replace placeholder values such as YOUR_CLIENT_ID with the credentials issued to your organisation.


Before you start

You need:

  1. An Edgewatch API client provisioned for Breachspot (targetApis includes breachspot, or the client is marked generic).
  2. Two values from that client:
    • Client ID — sent as the x-client-id header
    • Client secret — sent as the x-client-secret header
  3. A tool that can send HTTPS requests (curl, Postman, or a short script).

If you do not have credentials yet, contact Edgewatch Support. Do not share your client secret in tickets, chat, or source control.

Recommended workflow

  1. Exchange x-client-id + x-client-secret for a short-lived access token and a refresh token.
  2. Call data endpoints with Authorization: Bearer <access_token>.
  3. When the access token expires, call /auth/refresh with the refresh token. Do not send the client secret on every search request.

Access tokens are typically valid for 1 hour. Refresh tokens are typically valid for 7 days. Both values are issued as JWTs.


Step-by-step authentication

Step 1 — Confirm you have client credentials

Your organisation should have received:

  • x-client-id — unique client identifier (uid)
  • x-client-secret — corresponding secret

Store them as environment variables so they never appear in scripts committed to git:

export BREACHSPOT_BASE_URL="https://api.breachspot.com"
export BREACHSPOT_CLIENT_ID="YOUR_CLIENT_ID"
export BREACHSPOT_CLIENT_SECRET="YOUR_CLIENT_SECRET"

Step 2 — Request an access token

Call GET /auth/token with your client ID and secret. No Bearer token is required for this call.

curl -sS -X GET "$BREACHSPOT_BASE_URL/auth/token" \
  -H "x-client-id: $BREACHSPOT_CLIENT_ID" \
  -H "x-client-secret: $BREACHSPOT_CLIENT_SECRET"

Successful response (HTTP 200)

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Save both values:

export BREACHSPOT_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
export BREACHSPOT_REFRESH_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Common errors

HTTPBodyWhat to do
401{"error":"Invalid credentials"}Check x-client-id and x-client-secret.
401This API key is not authorized to access the Breachspot API.Ask Support to add breachspot to your client’s allowed APIs.
401Your API key has expired, please issue a new one.Request a new API client.

Step 3 — (Optional) Validate the token

Confirm the token is accepted and inspect the client metadata returned by the API:

curl -sS -X GET "$BREACHSPOT_BASE_URL/auth/validate" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Successful response (HTTP 200)

{
  "uid": "YOUR_CLIENT_ID",
  "name": "Acme Security Operations",
  "client": "your-client-guid",
  "isGeneric": false,
  "targetApis": ["breachspot"],
  "createdAt": "2026-01-15T10:00:00.000Z",
  "expiresAt": "2027-01-15T10:00:00.000Z"
}

If this call returns 401, the access token is missing, malformed, or expired. Request a new token (Step 2) or refresh it (Step 5).

Step 4 — Call a protected endpoint with the Bearer token

Every credential-search and monitor request must include:

Authorization: Bearer YOUR_ACCESS_TOKEN

Example — search by email:

curl -sS -G "$BREACHSPOT_BASE_URL/breaches/search" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "[email protected]" \
  --data-urlencode "limit=50"

You do not need to send x-client-id / x-client-secret on data requests once you have an access token.

Step 5 — Refresh the access token when it expires

When /auth/validate or a data call returns 401 because the access token expired, rotate both tokens:

curl -sS -X GET "$BREACHSPOT_BASE_URL/auth/refresh" \
  -H "x-refresh-token: $BREACHSPOT_REFRESH_TOKEN"

Successful response (HTTP 200)

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Replace both stored values. The previous refresh token should not be reused after a successful rotation.

HTTPMeaning
400x-refresh-token header is missing.
403Refresh token is invalid or expired. Start again from Step 2 with your client ID and secret.

Authentication headers (quick reference)

HeaderUsed onPurpose
x-client-idGET /auth/tokenAPI client identifier
x-client-secretGET /auth/tokenAPI client secret
Authorization: Bearer <token>Data endpoints and GET /auth/validateAccess token
x-refresh-tokenGET /auth/refreshRefresh token
Content-Type: application/jsonPOST / PUT bodiesJSON payloads

Querying the API

Unless noted otherwise, send Authorization: Bearer $BREACHSPOT_TOKEN.

The API never returns plaintext passwords. Emails in credential items are masked (for example u***@***.com). Password material is returned only as password_sha1 and, when available, password_ntlm.

limit on search endpoints is an integer from 1 to 1000 (default 100).


Looks up an email, domain, or username across breach collections. Provide exactly one of email, domain, or username.

Query parameterRequiredDescription
emailone of threeEmail address to search
domainone of threeDomain to search (for example example.com)
usernameone of threeUsername to search
limitnoMax credential items to return (1–1000, default 100)

Search by email

curl -sS -G "$BREACHSPOT_BASE_URL/breaches/search" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "[email protected]" \
  --data-urlencode "limit=50"

Search by domain

curl -sS -G "$BREACHSPOT_BASE_URL/breaches/search" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "domain=example.com" \
  --data-urlencode "limit=100"

Search by username

curl -sS -G "$BREACHSPOT_BASE_URL/breaches/search" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "username=jdoe" \
  --data-urlencode "limit=100"

Example response

{
  "query": {
    "type": "email",
    "value": "[email protected]"
  },
  "count": 1700,
  "matches": [
    {
      "count": 1500,
      "source": {
        "name": "Anti Public Combo List",
        "description": "A compilation of unique email and password pairs.",
        "breached_data": ["email", "password"],
        "total_records": 3300000000,
        "date": "2016-12-01"
      }
    }
  ],
  "items": [
    {
      "email": "u***@***.com",
      "username": "user123",
      "url": "https://example.com",
      "password_sha1": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
      "password_ntlm": "a4f49c9ee90d0ea3",
      "source": "Anti Public Combo List"
    }
  ]
}
FieldMeaning
queryThe search type and original value you sent
countTotal matching occurrences across sources (independent of limit)
count_cappedPresent and true when count is a lower bound, not an exact total
matchesPer-collection occurrence counts and source metadata
itemsSample credential records (masked email, hashed passwords only)

If none of email, domain, or username is provided, the API returns 400:

{ "message": "At least one of email, domain, or username is required" }

2. Advanced credential search — POST /breaches/search/advanced

Use this when you need date ranges, extra filters, field selection, or pagination. Send a JSON body.

Request body

FieldRequiredDescription
filtersyesObject with at least one filter (see below)
limitnoPage size, 1–1000 (default 100)
pagenoPage number, ≥ 1 (default 1)

filters object

FieldDescription
emailFilter by email
domainFilter by domain
usernameFilter by username
sinceInclusive start date (YYYY-MM-DD)
untilInclusive end date (YYYY-MM-DD)
include_fieldsArray of field names to return
whereArray of { "field", "operator", "value" } conditions

where operators: exists, cidr, like, eq, ne, gt, lt, gte, lte, in, nin.

Example — email plus date range

curl -sS -X POST "$BREACHSPOT_BASE_URL/breaches/search/advanced" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "email": "[email protected]",
      "since": "2024-01-01",
      "until": "2025-12-31"
    },
    "limit": 100,
    "page": 1
  }'

Example — domain with extra conditions

curl -sS -X POST "$BREACHSPOT_BASE_URL/breaches/search/advanced" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "domain": "example.com",
      "include_fields": ["email", "username", "ip", "password_hash"],
      "where": [
        { "field": "password_hash", "operator": "exists" },
        { "field": "ip", "operator": "cidr", "value": "203.0.113.0/24" }
      ]
    },
    "limit": 50,
    "page": 1
  }'

Example response

{
  "items": [
    {
      "email": "u***@***.com",
      "username": "user123",
      "url": "https://example.com",
      "password_sha1": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
      "source": "Anti Public Combo List"
    }
  ],
  "total": 1500,
  "total_unlocked": 1200,
  "page": 1,
  "page_size": 100
}

Walk pages by incrementing page until items is empty or page * page_size >= total.


3. Collection statistics — GET /stats

Returns metadata for each breach collection (friendly name, record counts, date, tags).

curl -sS "$BREACHSPOT_BASE_URL/stats" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Example response

[
  {
    "name": "Anti Public Combo List",
    "slug": "anti_public_combo_list",
    "description": "A massive compilation of unique email and password pairs.",
    "total_records": 3300000000,
    "date": "2016-12-01",
    "tags": ["breach", "credential stuffing"],
    "breached_data": ["email", "password"]
  }
]

4. Monitors (watch lists)

A monitor tracks search terms for your API client. One monitor is allowed per client. Search terms support types: email, phone, business_name, name, username, ip, domain. Each term has type, value, and optional is_regex.

The monitor is bound to the authenticated client automatically. You do not send client_guid in the body.

Create a monitor — POST /monitors

curl -sS -X POST "$BREACHSPOT_BASE_URL/monitors" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "is_active": true,
    "search_terms": [
      { "type": "email", "value": "[email protected]", "is_regex": false },
      { "type": "domain", "value": "example.com", "is_regex": false }
    ]
  }'

HTTP 201 returns the created monitor, including its uuid. HTTP 405 means this client already has a monitor — use GET /monitors/client instead.

Get your monitor — GET /monitors/client

curl -sS "$BREACHSPOT_BASE_URL/monitors/client" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

The response includes uuid, search_terms, accumulated results, events, and is_active.

Get results for one search term — GET /monitors/{uuid}/search-terms/{searchtermId}/results

Use the monitor uuid and the search term identifier from GET /monitors/client.

curl -sS "$BREACHSPOT_BASE_URL/monitors/MONITOR_UUID/search-terms/SEARCH_TERM_ID/results" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Example response

{
  "results": [],
  "ransomResults": []
}

results are credential matches for that term. ransomResults are related underground / ransomware matches when available.


5. Underground intelligence

These endpoints cover threat actor groups, leak-site posts, markets, and attributed incident feeds. The feed accepts unauthenticated calls with a small page size; send your Bearer token for higher limits, pagination, and filters.

Incident feed — GET /underground/feed

Query parameterAuth requiredDescription
limitnoUnauthenticated: 1–10 (default 10). Authenticated: 1–500 (default 50).
offsetyesSkip this many items (load-more pagination).
detailedyestrue returns full post + target objects.
date_from / date_toyesYYYY-MM-DD range
periodyesday, week, month, or year (used when dates are omitted)
countryyesCountry filter
cityyesCity filter
companyyesCompany / target name
industryyesIndustry / sector
typeyesIncident type (for example extortion)
groupyesGroup slug
curl -sS -G "$BREACHSPOT_BASE_URL/underground/feed" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "limit=25" \
  --data-urlencode "period=week" \
  --data-urlencode "country=US"

Unauthenticated example (limited):

curl -sS -G "$BREACHSPOT_BASE_URL/underground/feed" \
  --data-urlencode "limit=10"
curl -sS -G "$BREACHSPOT_BASE_URL/underground/search" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "q=lockbit"

q is required. The response groups matching groups, markets, leaks, posts, channels, messages, and notes.

Threat actor groups — GET /underground/groups

curl -sS -G "$BREACHSPOT_BASE_URL/underground/groups" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "search=lockbit" \
  --data-urlencode "active=true" \
  --data-urlencode "limit=50"

Optional query parameters: search, active, has_sites, limit, cursor.

Single group profile

curl -sS "$BREACHSPOT_BASE_URL/underground/groups/lockbit" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Group posts

curl -sS -G "$BREACHSPOT_BASE_URL/underground/groups/lockbit/posts" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "since=2026-01-01" \
  --data-urlencode "limit=50"

Group sites / locations (type can be dls, fs, chat, or admin)

curl -sS -G "$BREACHSPOT_BASE_URL/underground/groups/lockbit/sites" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "type=dls"

Legacy aliases still work: GET /underground/group/{name} and GET /underground/market/{name}.

Markets — GET /underground/markets

curl -sS "$BREACHSPOT_BASE_URL/underground/markets" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Posts — GET /underground/posts

curl -sS -G "$BREACHSPOT_BASE_URL/underground/posts" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "q=healthcare" \
  --data-urlencode "country=US" \
  --data-urlencode "since=2026-01-01" \
  --data-urlencode "limit=50"

Optional filters: group, q, country, sector, source_type, since, limit, cursor.

Post by ID

curl -sS "$BREACHSPOT_BASE_URL/underground/posts/POST_ID" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Post by group name and post name

curl -sS "$BREACHSPOT_BASE_URL/underground/post/lockbit/example-post-slug" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Recent posts (number defaults to 100)

curl -sS "$BREACHSPOT_BASE_URL/underground/recent/25" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Posts from the last N days

curl -sS "$BREACHSPOT_BASE_URL/underground/last/7" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Posts in a calendar month

curl -sS "$BREACHSPOT_BASE_URL/underground/posts/2026/09" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Posts in a date period (YYYY-MM-DD)

curl -sS "$BREACHSPOT_BASE_URL/underground/posts/period/2026-09-01/2026-09-21" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Cursor-style list endpoints (/underground/groups, /underground/posts, /underground/sites, …) return { "items": [...], "next_cursor": "..." }. Pass next_cursor as cursor on the next request until it is null.

Sites, screenshots, Telegram sources

# Leak / chat / file-sharing sites
curl -sS -G "$BREACHSPOT_BASE_URL/underground/sites" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "online=true" \
  --data-urlencode "limit=50"

# Screenshot catalogue
curl -sS "$BREACHSPOT_BASE_URL/underground/screenshots" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

# One screenshot
curl -sS "$BREACHSPOT_BASE_URL/underground/screenshots/SCREENSHOT_ID" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

# Telegram sources
curl -sS "$BREACHSPOT_BASE_URL/underground/sources/telegram" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Attributed targets and relationship graphs

curl -sS -G "$BREACHSPOT_BASE_URL/underground/targets" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN" \
  --data-urlencode "group=lockbit" \
  --data-urlencode "limit=50"

curl -sS "$BREACHSPOT_BASE_URL/underground/graph/group/lockbit" \
  -H "Authorization: Bearer $BREACHSPOT_TOKEN"

Complete examples in other languages

Python

import os
import requests

BASE = os.environ["BREACHSPOT_BASE_URL"]  # https://api.breachspot.com
CLIENT_ID = os.environ["BREACHSPOT_CLIENT_ID"]
CLIENT_SECRET = os.environ["BREACHSPOT_CLIENT_SECRET"]

# 1. Authenticate
tokens = requests.get(
    f"{BASE}/auth/token",
    headers={
        "x-client-id": CLIENT_ID,
        "x-client-secret": CLIENT_SECRET,
    },
    timeout=30,
)
tokens.raise_for_status()
access_token = tokens.json()["token"]
refresh_token = tokens.json()["refreshToken"]
auth = {"Authorization": f"Bearer {access_token}"}

# 2. Simple search
search = requests.get(
    f"{BASE}/breaches/search",
    headers=auth,
    params={"email": "[email protected]", "limit": 50},
    timeout=60,
)
search.raise_for_status()
print(search.json()["count"], "occurrences")

# 3. Advanced search
advanced = requests.post(
    f"{BASE}/breaches/search/advanced",
    headers={**auth, "Content-Type": "application/json"},
    json={
        "filters": {
            "domain": "example.com",
            "since": "2024-01-01",
        },
        "limit": 100,
        "page": 1,
    },
    timeout=60,
)
advanced.raise_for_status()
print("page size", advanced.json()["page_size"])

# 4. Refresh when the access token expires
refreshed = requests.get(
    f"{BASE}/auth/refresh",
    headers={"x-refresh-token": refresh_token},
    timeout=30,
)
refreshed.raise_for_status()
access_token = refreshed.json()["token"]
refresh_token = refreshed.json()["refreshToken"]

Node.js (fetch)

const BASE = process.env.BREACHSPOT_BASE_URL; // https://api.breachspot.com

async function getTokens() {
  const res = await fetch(`${BASE}/auth/token`, {
    headers: {
      "x-client-id": process.env.BREACHSPOT_CLIENT_ID,
      "x-client-secret": process.env.BREACHSPOT_CLIENT_SECRET,
    },
  });
  if (!res.ok) throw new Error(`auth failed: ${res.status} ${await res.text()}`);
  return res.json(); // { token, refreshToken }
}

async function searchByEmail(token, email) {
  const url = new URL("/breaches/search", BASE);
  url.searchParams.set("email", email);
  url.searchParams.set("limit", "50");
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) throw new Error(`search failed: ${res.status} ${await res.text()}`);
  return res.json();
}

const { token, refreshToken } = await getTokens();
const result = await searchByEmail(token, "[email protected]");
console.log(result.count, result.matches?.length);

HTTP status codes

CodeTypical meaning
200Success
201Monitor created
400Missing or invalid parameters (for example no search term)
401Missing, invalid, or expired credentials / token
403Refresh token rejected, or detailed=true on the feed without auth
404Resource not found (monitor, post, screenshot, group)
405Client already has a monitor
429Rate limit exceeded
500Unexpected server error

Rate limits and usage metering

External clients are rate-limited. Default window is 100 requests per 15 minutes per client IP (your contract may differ).

Successful responses include:

  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset

When you exceed the limit you receive 429:

{
  "error": "Too many requests",
  "message": "Rate limit exceeded. Maximum 100 requests per 900 seconds. Please try again later.",
  "retryAfter": 900
}

Honour the Retry-After header (seconds) before retrying. Back off rather than tight-looping.

Search and monitor calls are also metered against your credit wallet. If searches stop returning full results or authentication succeeds but data looks restricted, check your remaining credits with Support.


Security and data-handling notes

  • Treat x-client-secret, access tokens, and refresh tokens as passwords.
  • Prefer environment variables or a secret manager; never commit secrets.
  • Rotate tokens with /auth/refresh instead of embedding the client secret in every worker request.
  • The API returns masked emails and password hashes only. Do not expect plaintext credentials.
  • Search only identifiers you are authorised to investigate (your organisation, customers under contract, or other lawful purpose). See the terms of use.
  • Law-enforcement-only routes (/le/artifacts/{post_id}, /le/dumps/{post_id}) require a dedicated LE API client. Contact Support if your agency needs that access; all LE calls are audit-logged.

Interactive documentation

The live OpenAPI explorer is at https://api.breachspot.com/docs/.

  1. Open the docs site.
  2. Click Authorize.
  3. For token issue: enter x-client-id and x-client-secret, then try GET /auth/token.
  4. Paste the returned token into the BearerAuth field.
  5. Try GET /breaches/search or any other operation.

Authorization persists in the browser tab (persistAuthorization is enabled).

Was this article helpful?

Related Articles