POST with a JSON body. There is no job to start and nothing to poll. Each call answers one question:
- Has this password appeared in a known leak, under any login?
- Has this exact email and password pair leaked together?
- Has this pair leaked for one website, such as yours?
- Has this email address appeared in a known leak?
2xx, and counts toward your organization’s per-second rate limit. Every call appears in your API call usage.
Pick a check for each moment
The checks nest, so each narrower check only adds information when the broader one found something:
- A pair match is always a password match. The pair check flags only this user’s own leaked pair, not every password someone else leaked.
- A site match is always a pair match. Run the site check only after the pair check returns
found: true. - An email match says the address leaked. It does not say the user’s current password did. Use it for a notice, and let the pair check decide whether to prompt a change.
alice@acme.local, use the password check.
Check a new password
Callcredential_check_password_compromised when a user picks a password, before you hash and store it. Send the password as password, exactly as the user typed it. Do not hash or trim it.
At sign-up the address is not verified yet, so check the password alone. A pair or email result shown to someone who has not proved they own the address tells them whether that address leaked. At a password change the user is signed in. At a reset, the reset link has proven they control the address. In both cases the pair check with their email and the new password is safe too, if you only want to refuse their own leaked pairs.
found:truewhen at least one leak record holds this password. This is the value your flow acts on.leakCount: the number of leak records that hold this password, under any login. It counts records, not distinct leaks, and stops at 10,000. A value of10000means 10,000 or more.
- 1 to 128 characters. An empty string or a longer value is refused with
400. A password longer than 128 characters cannot be checked. Skip the check for it. - Whitespace is kept.
" hunter2 "and"hunter2"are two different passwords. A single space is a valid password. - Matching is case-sensitive. The value is matched as sent, with no wildcards.
Check the pair after a successful login
Login is the one moment your backend holds an existing user’s password in plain text. Your database holds only hashes. That makes login the moment to run the pair check for accounts that already exist.1
Verify the password as usual
Run your own password verification first. On a failed attempt, call nothing.
2
Run the pair check
Send the
email and password the user typed. On found: false, finish the sign-in.3
Act on a match
Finish the sign-in and prompt a password change. To require the change when the leak names your site, run the site check next.
4
Let the user through if a check cannot answer
On an error or a timeout, finish the sign-in without the prompt. See Let the user through when a check cannot answer.
credential_check_credentials_compromised with both fields. A request missing either one is refused with 400.
leakCount stops at 10,000, like the password check. found: true means this user’s current password for this address sits in a leak, next to the address.
Check a credential for your own site
The password, pair and email checks take no website. The pair check matches the email and password on any site. To ask whether the pair leaked for your site, callcredential_check_leak_search with email, password and website in the body.
totalElements, page, size and results, in the shape shown under What comes back. Read totalElements. A value greater than 0 means the credential leaked for that site. All three filters must match the same record. A record with no site recorded never matches, so a pair can be found by the pair check and still return 0 here. totalElements stops at 10,000.
- Send each host your users sign in on, such as
acme.comorlogin.acme.com.websitetakes one value of up to 255 characters. It is matched, without regard to case and with no wildcards, against the site recorded on each record. Run one search per host. - Confirm the value matches before you rely on it. Outside the sign-in path, search once with
websitealone and read thewebsitevalues that come back. A value in a form your records do not use answers0, with no error. - A
0does not clear the user. The pair check already matched, so keep the prompt to change the password. - Leave out
passwordto ask whether any credential for this address leaked for your site. Every field you send narrows the match. - Send no query string. The default page of 10 records is the smallest one. A
sizebelow10is refused with400. - Keep
resultson your server. It carries up to 10 matching records, with the leaked passwords in plain text. Your flow needs onlytotalElements. Never log or store the response.
Check an email address
Callcredential_check_email_compromised for a signed-in user’s verified address, for example on an account or security settings page. Send the address as email.
found:truewhen at least one leak record holds this address.leakCount: the number of matching leak records. It stops at 10,000.firstSeen/lastSeen: when the oldest and the newest matching records were recorded, asYYYY-MM-DD HH:MM:SS. These are recording dates, not breach dates. Both arenullwhenfoundisfalse.
Alice@Acme.com and alice@acme.com get the same answer. Surrounding whitespace is trimmed. The field must be a valid email address. A bare domain or a partial address is refused with 400. An address on a reserved domain such as .local, .test or localhost is refused with 422. An address with an IP-address domain is refused with 400 or 422. AD-style logins such as alice@acme.local fall into this case. Skip the check for those addresses.
A found address does not mean your user’s password leaked. Read it next to the pair check: if the email is found but the pair is not, the address leaked, but never with this password.
Act on the answer
- Act on
found. Anytrueis enough to refuse a new password or ask for a change.leakCounttells you how widely a value circulates. It is context, not a threshold. - Show results only to the account owner. Run the pair and site checks after a successful login. Show an email result only to the signed-in owner of a verified address. Otherwise your form tells strangers which addresses and passwords are leaked.
- Store the outcome, not the input. Keep a flag such as “password change required” on the account. Never store the password you checked or the records that came back.
found: false is a complete answer. If the leak index cannot answer from all of its data, the call fails with 502. You never get a smaller count, or found: false, in its place. An error means unknown, never clean.Wire the checks into your backend
Call Credential Check from your server, inside the request that handles the form. Your API key authorizes calls as your organization. Anyone who reads it can make the same calls, so it never goes to a browser or a mobile app. Your form posts to your backend as it does today, and your backend calls the API. Keep the API key server-side. Read it from your secret store and send it in theX-API-Key header on every check. Authentication covers API keys. A 401 means the key is invalid, expired or revoked. Alert your team, and do not retry with the same key.
1
Send the credential in the JSON body
The checks read
password from the request body, never from the query string. Send JSON and build it with a JSON encoder, not string concatenation. Quotes, backslashes and non-ASCII characters in a password then reach the API intact.2
Set your own timeout
The API waits up to 30 seconds by default for the lookup service before it answers
504. A slow lookup usually ends as a 502 before that. Either way, it is longer than a sign-up or login form should hang. Give the call the wait your form can afford, and treat a timeout as no answer.3
Fail open
Decide up front what happens when a check gives no answer. At sign-up, login and password change, let the user continue. An outage on the check then never blocks a sign-up or a sign-in.
4
Log the status, never the body
Log the status code of a failed check so you can alert on it. Keep the request body and any leak search response out of your HTTP client logs, error trackers and traces. Both hold credentials.
5
Send the browser only the outcome
The browser needs to know whether to show the warning. It does not need the count or the records.
"site", require a change before the session continues. On "pair", finish the sign-in and prompt a change. On "none", finish the sign-in. On no answer, finish the sign-in without the prompt. The answer is unknown, not clean.
The helpers log the endpoint and the status only. They never log the body, which holds the password.
Let the user through when a check cannot answer
A leak check adds a warning to your flow. It should never be the reason a user cannot sign up, sign in or change a password. When a check fails, finish the flow without the warning and log the failure for your team.Plan for your volume
Every check is one API call. A sign-in that finds a pair match and then runs the site check makes two. Every call that passes the license check counts toward your organization’s per-second rate limit, failed calls included. That limit is shared with every other call your organization makes. Each of those calls returnsX-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers. They show your current window, usually one second. Read them to space out bursts. Rate limits covers the limits.
Read the leak records behind an answer
Support and investigation work sometimes needs the records, not just the yes/no.credential_check_leak_search returns the records a check counted. Read and page the records outside the sign-in path. Send the same values you checked, and totalElements matches the leakCount you got, unless new records were indexed in between.
1
Run a check
Call the email, password, or pair check. Move on when
found is true.2
Search with the same values
Send the same
email, password, or both in the body. Add website to narrow to one site, and dateFrom or dateTo to narrow by date.3
Page through the records
Raise
page until you have read totalElements records.Body filters
You need at least one of
email, password or website. Dates alone are refused with 400. Every filter you send narrows the result. dateFrom and dateTo each work on their own. Send dates as YYYY-MM-DD. A value longer than 10 characters is refused with 400. An impossible date such as 2024-02-30 is refused with 422.
Query parameters
Records are ordered by
createdAt, newest first by default. sort=createdAt,asc returns the oldest first. The API also accepts leakPublishDate and leakDiscoverDate as the field name, but the order still follows createdAt. Only the direction changes it.
What comes back
totalElements: the number of matching records. It stops at 10,000, likeleakCount.page/size: the page you asked for and its size.results: the records on this page.
totalElements by size and round up.
Each record:
id: the record’s unique ID.createdAt: when the record was recorded, asYYYY-MM-DD HH:MM:SS. The date filters and the sort use this field.email: the leaked login, as an email address or another login name.password: the leaked password.website: the site the credential was used on.leakId/leakName: the leak the record came from. When the leak was published inside a larger collection, these name the collection.leakPublishDate/leakDiscoverDate: when that leak or collection was published and when it was discovered, asYYYY-MM-DD.fileName: not populated. It is alwaysnull.
leakName and the two leak dates come from the leak catalogue. When the catalogue has no entry for the leak, all three are null and leakId is the record’s own leak ID.
Paging stops at record 10,000. A page whose last record would pass 10,000 is refused with
422. At size=100 the last page you can read is 99. At size=10 it is 999. To reach the rest, flip the sort direction or narrow the date range.Errors
The API validates your request first, then hands it to the lookup service that reads the leak index. A400 never reaches the leak index. For what a live flow does with each status, see Let the user through when a check cannot answer.
Any other error status from the lookup service reaches you unchanged, with its body. Treat it like a
502.
Other credential searches
Credential Check reads the account index, which holds the same parsedlogin / password / website records the ULP feed searches. You pass the raw value, not a Lucene query. The API escapes it for you, so wildcards, quotes and operators match literally.
Start from Find leaked credentials when you need more than a yes/no.