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:
- An Edgewatch API client provisioned for Breachspot (
targetApisincludesbreachspot, or the client is marked generic). - Two values from that client:
- Client ID — sent as the
x-client-idheader - Client secret — sent as the
x-client-secretheader
- Client ID — sent as the
- 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
- Exchange
x-client-id+x-client-secretfor a short-lived access token and a refresh token. - Call data endpoints with
Authorization: Bearer <access_token>. - When the access token expires, call
/auth/refreshwith 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
| HTTP | Body | What to do |
|---|---|---|
| 401 | {"error":"Invalid credentials"} | Check x-client-id and x-client-secret. |
| 401 | This API key is not authorized to access the Breachspot API. | Ask Support to add breachspot to your client’s allowed APIs. |
| 401 | Your 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.
| HTTP | Meaning |
|---|---|
| 400 | x-refresh-token header is missing. |
| 403 | Refresh token is invalid or expired. Start again from Step 2 with your client ID and secret. |
Authentication headers (quick reference)
| Header | Used on | Purpose |
|---|---|---|
x-client-id | GET /auth/token | API client identifier |
x-client-secret | GET /auth/token | API client secret |
Authorization: Bearer <token> | Data endpoints and GET /auth/validate | Access token |
x-refresh-token | GET /auth/refresh | Refresh token |
Content-Type: application/json | POST / PUT bodies | JSON 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).
1. Simple credential search — GET /breaches/search
Looks up an email, domain, or username across breach collections. Provide exactly one of email, domain, or username.
| Query parameter | Required | Description |
|---|---|---|
email | one of three | Email address to search |
domain | one of three | Domain to search (for example example.com) |
username | one of three | Username to search |
limit | no | Max 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"
}
]
}
| Field | Meaning |
|---|---|
query | The search type and original value you sent |
count | Total matching occurrences across sources (independent of limit) |
count_capped | Present and true when count is a lower bound, not an exact total |
matches | Per-collection occurrence counts and source metadata |
items | Sample 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
| Field | Required | Description |
|---|---|---|
filters | yes | Object with at least one filter (see below) |
limit | no | Page size, 1–1000 (default 100) |
page | no | Page number, ≥ 1 (default 1) |
filters object
| Field | Description |
|---|---|
email | Filter by email |
domain | Filter by domain |
username | Filter by username |
since | Inclusive start date (YYYY-MM-DD) |
until | Inclusive end date (YYYY-MM-DD) |
include_fields | Array of field names to return |
where | Array 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 parameter | Auth required | Description |
|---|---|---|
limit | no | Unauthenticated: 1–10 (default 10). Authenticated: 1–500 (default 50). |
offset | yes | Skip this many items (load-more pagination). |
detailed | yes | true returns full post + target objects. |
date_from / date_to | yes | YYYY-MM-DD range |
period | yes | day, week, month, or year (used when dates are omitted) |
country | yes | Country filter |
city | yes | City filter |
company | yes | Company / target name |
industry | yes | Industry / sector |
type | yes | Incident type (for example extortion) |
group | yes | Group 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"
Search across underground data — GET /underground/search
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
| Code | Typical meaning |
|---|---|
| 200 | Success |
| 201 | Monitor created |
| 400 | Missing or invalid parameters (for example no search term) |
| 401 | Missing, invalid, or expired credentials / token |
| 403 | Refresh token rejected, or detailed=true on the feed without auth |
| 404 | Resource not found (monitor, post, screenshot, group) |
| 405 | Client already has a monitor |
| 429 | Rate limit exceeded |
| 500 | Unexpected 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-LimitX-RateLimit-RemainingX-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/refreshinstead 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/.
- Open the docs site.
- Click Authorize.
- For token issue: enter
x-client-idandx-client-secret, then tryGET /auth/token. - Paste the returned
tokeninto the BearerAuth field. - Try
GET /breaches/searchor any other operation.
Authorization persists in the browser tab (persistAuthorization is enabled).