Exposed Credentials
Breach-exposed credentials discovered for a scan's domain — which accounts appeared in which breach corpus, and whether a plaintext or hashed password was recovered. Enough to drive a password-reset campaign; nothing that can be replayed.
This endpoint is heavily redacted by design. The password itself — plaintext or hash — never leaves the database, and neither does breach PII (names, addresses, phone numbers, dates of birth, social profiles). What you get is the account identifier, the breach source, and existence flags.
/v1/my/scans/{scan_id}/credentialsCost: 1 credit per successful call · Scope: api:org
Breach data is the most sensitive dataset on the platform, so the redaction is
enforced in the database, not left to the client: the password and the breach
PII are never part of the response, whoever holds the key. Pair api:org keys
with a per-key IP allowlist to limit the blast
radius of a leaked credential.
Path parameters
scan_idstring (uuid)requiredA scan id from GET /my/scans. The scan's
counts.exposed_credentials tells you how many rows to expect.
Query parameters
limitintegeroptionalPage size, 1–200. Default 50. Out-of-range is a 400, not a silent clamp.
cursorstringoptionalOpaque pagination cursor from a previous page's next_cursor.
Request
curl "https://api.precursorintelligence.com/functions/v1/my/scans/f63285e2-9c78-4020-b2bb-5c8bd2ca51ed/credentials" \
-H "Authorization: Bearer $PRECURSOR_API_KEY"Response
{
"data": {
"count": 2,
"items": [
{
"id": "00e528ef-7eb4-5650-f20d-cd42714a584f",
"email": "j.smith@acmecorp.com",
"username": "jsmith",
"breach_source": "Collection #1",
"has_plaintext_password": true,
"has_hashed_password": false,
"hash_algorithm": null,
"discovered_at": "2026-08-06T06:28:00+00:00",
"last_verified": "2026-08-06T06:28:00+00:00"
},
{
"id": "1c88d2aa-0f4e-4b7d-9a02-8d2b1f7f11a3",
"email": "it-support@acmecorp.com",
"username": null,
"breach_source": "LinkedIn 2021",
"has_plaintext_password": false,
"has_hashed_password": true,
"hash_algorithm": "bcrypt",
"discovered_at": "2026-08-06T06:28:00+00:00",
"last_verified": "2026-08-06T06:28:00+00:00"
}
],
"next_cursor": null,
"has_more": false,
"generated_at": "2026-08-18T09:30:00+00:00"
},
"meta": {
"request_id": "8b16efb8-9a6c-4ace-b817-11cbee626d08",
"api_version": "1.3.0",
"credits": { "charged": 1, "remaining": 9995 }
}
}Response fields
data.items[].emailstring | nulloptionalThe breached account. This plus breach_source is the actionable pair:
which accounts to force-reset, and why.
data.items[].breach_sourcestring | nulloptionalThe breach corpus the credential appeared in.
data.items[].has_plaintext_passwordbooleanoptionalA recoverable plaintext password exists in the breach data. Treat the account as compromised; the password itself is never returned.
data.items[].has_hashed_passwordbooleanoptionalA password hash exists (hash_algorithm says which kind). Weak algorithms
(MD5, SHA-1, unsalted anything) should be treated as close to plaintext.
An unknown scan_id — or another organisation's — returns 200 OK with
data set to null, and the credit is still charged. Take scan ids from
GET /my/scans.
Errors
| Status | error | When |
|---|---|---|
| 400 | invalid_scan_id, invalid_limit, invalid_cursor | A parameter failed validation. Free — rejected before billing. |
| 401 | invalid_authorization | Missing or malformed Authorization header. |
| 401 | unauthorized | Invalid/revoked key, IP not allowlisted, or the key lacks the api:org scope. |
| 402 | insufficient_credits | Organisation credit balance is below 1. |
| 405 | method_not_allowed | Only GET and OPTIONS are accepted. |
| 500 | internal | Unexpected server error (credit auto-refunded). |
See Errors for the full error reference.