Integrate in three steps.
Everything you need to screen passwords from your own server: the request, the response, the errors, the metering, and what to do when the API is down.
- Base URL
https://api.knownpass.comThis host stays; it keeps the project’s original name.
- API version
- v1
- Updated
On this page
The whole integration
Hash the password on your server with the public salt, send the first six characters of the hash, and compare the returned hashes locally. The raw password and its full hash never leave your infrastructure. One GET request, one JSON response, usually under 100 ms from Europe on an open connection.
1. Hash locally
Compute the SHA-1 of the fixed salt string followed by the password, as lowercase hex. The first six characters are the prefix you send. The remaining 34 are the suffix you keep.
// any runtime with SHA-1
const hash = sha1("Salted for knownpass.com: " + password).toLowerCase();
const prefix = hash.slice(0, 6); // sent
const suffix = hash.slice(6); // kept
// "password123"
// hash = e4ed298fced32b284fb61840470dd5f9e030b568
// prefix = e4ed29 (sent)
// suffix = 8fced32b284fb61840470dd5f9e030b568 (kept, 34 chars)
Why the salt keeps the old name. The salt and the API host keep the original name: their domain belongs to KnownPass s.r.o., the company that runs PasswordRadar (formerly knownPass). The salt is part of every hash in the dataset, so it stays exactly as it is, trailing space included, and existing integrations keep working unchanged.
SHA-1 is a lookup key here, not a password-storage hash. The salt is public and fixed on purpose: it keeps PasswordRadar prefixes from being replayed against other services. Details in the threat model.
2. Send the prefix
One authenticated GET request, with the prefix in the path.
GET https://api.knownpass.com/v1/range/{prefix}
Authorization: Bearer kpp_…
| Part | Value |
|---|---|
{prefix} | Exactly six lowercase hex characters. Anything else, uppercase included, returns 400. |
Authorization | Bearer followed by your API key. Missing or invalid returns 401. |
Free keys are issued by email today: write to hello@passwordradar.eu. No card. With your key in the environment variable PASSWORDRADAR_API_KEY, the worked example from step 1 is:
$ curl -H "Authorization: Bearer $PASSWORDRADAR_API_KEY" \
https://api.knownpass.com/v1/range/e4ed29
3. Compare locally
The response is JSON. prefix echoes what you sent; results maps every known hash in that bucket to the categories it was found in. Keys are full 40-character lowercase hashes: hundreds of them per bucket, and the count depends on the datasets your key includes.
{
"prefix": "e4ed29",
"results": {
"e4ed29003bbcf986aa376363a8e0de6e6f970afa": ["Brute-force space"],
…
"e4ed298fced32b284fb61840470dd5f9e030b568": [
"Website leaks", "Malware leaks", "Password lists"
],
…
}
}
If the hash you computed in step 1 is a key in results, the password is known: block it at sign-up and password change, require a change at login. If it isn’t, proceed. In the example above it is: the second key shown is the hash of password123 from step 1, so that password is known. The response carries no counts and no plaintexts.
// hash: the 40-character value from step 1
const known = Object.hasOwn(body.results, hash);
Categories
Categories you may see: Website leaks, Malware leaks, Password lists, Wordlists, Common, Masks & patterns, Brute-force space, Tailored. Treat any match as a block; the categories are for your logs and your users’ error message. What each one covers is in Data and provenance.
Today the live API still returns internal category names, such as Websites or Bruteforced, instead of these labels, while the customer-facing vocabulary is being settled.
Response codes
| Code | Meaning | What to do |
|---|---|---|
200 | JSON {prefix, results} for the prefix. An empty results object is valid: nothing known in that bucket. | Compare locally. |
400 | invalid_input: the prefix is not exactly six lowercase hex characters. | Fix the client: length and hex case. |
401 | unauthorized: missing or invalid Authorization header. | Check the key. If it leaked, email hello@passwordradar.eu to rotate it; self-serve rotation is planned for the admin console. |
403 | forbidden, bulk download only: the key has no bulk-download entitlement for this dataset. | Use your own key, not the demo key. Every plan includes bulk download. |
429 | Over the 1,000 requests a day included with the key, and no payment method or prepaid credit. Retry-After gives the wait in seconds. | Fail open and back off. Add a payment method or prepaid credit for more. |
5xx | Our fault. | Fail open. Check the status page. |
Error bodies are JSON with an error code and a request_id; a 400 adds a detail string. Quote the request_id when you report a problem.
{
"error": "invalid_input",
"detail": "the request does not match this endpoint",
"request_id": "…"
}
Metering and limits
- Every key
- 1,000 requests a day included, enough to integrate and test. Beyond that, €0.01 per request, pay as you go or from prepaid credit. The same on every plan; Free needs no card.
- Without a payment method or credit
- Requests over 1,000 a day return
429withRetry-After. - Bulk download
- Not counted against the daily requests.
- Trial keys
- 100 requests a day, a single key, no registration, deleted after 30 days without activity. They belong to the self-serve sign-up, which is still a preview.
- Demo key
- The key in the landing-page demo is public and shared. It is throttled and may be rotated without notice. Don’t build on it.
Key holders get 90 days’ notice before any change to limits or prices affects their keys. Plans are in pricing; to add a payment method or prepaid credit, write to hello@passwordradar.eu.
Fail open
PasswordRadar is a check, not a dependency. If the request times out, the network fails, or the API returns 429 or 5xx: let the flow continue, log that the check was skipped, and re-check at the next login or password change. A 400, 401 or 403 is different: the client or the key is misconfigured, and screening stays off until someone fixes it. So is any other error, such as a key with a stray invisible character, which the HTTP client refuses before sending anything, or a TLS failure. Let the flow continue there too, but log it as an error, with its cause, so it can’t go unnoticed. If every check is being skipped, look at the path to the API: a firewall, or a proxy your HTTP client doesn’t use. Suggested client timeout: 2 seconds. It also covers opening the connection, a few round trips when the connection is new, as it is for most checks on a quiet server: well under 200 ms from Europe, close to a second from Asia or South America. There is no uptime SLA today; the status page is updated by hand.
Where to put the check
- Sign-up and password change
- Block on match and ask for a different password. Tell the user why: “This password is on a list of passwords attackers try first. Please choose another.”
- Login
- Check the password the user just typed, at most once every 30 days per user: store when you last checked, and skip the call if it was recent. On a match, let them in, then require a new password before anything else; it may have leaked since they set it.
- Reset
- Same as sign-up.
The reference is NIST SP 800-63B-4 (opens in a new tab), section 3.1.1.2: when a password is set or changed, the verifier SHALL compare it against a blocklist of commonly used, expected or compromised passwords, and context-specific words such as the organisation’s or the service’s name belong on that list. The same section rules out composition rules and periodic rotation. National rules can differ: the Czech Decrees No. 409/2025 Sb. and No. 410/2025 Sb. still require a change at least every 18 months for accounts that rely on a password alone. Check the rules that apply to you before you drop rotation.
Machine-readable
- OpenAPI 3.1
- passwordradar.eu/openapi.json live
The public spec: the range, health and bulk-download endpoints, bearer auth, and the error codes above.
- llms.txt
- passwordradar.eu/llms.txt live
Written for coding assistants. Paste the URL into Claude Code, Cursor or Copilot and ask for the integration. It tells the assistant to hash locally and send only the prefix; check that the code it writes does.
- Reference client
- in progress
A single self-contained JavaScript component, licensed Apache-2.0. The repository link is coming.
Bulk download
live
Download the base dataset as files and screen passwords inside your own network. Included in every plan, Free too: self-hosting needs it. Downloads don’t count against the daily requests.
GET https://api.knownpass.com/v1/bulk/{dataset}/manifest
GET https://api.knownpass.com/v1/bulk/{dataset}/files/{filename}
Authorization: Bearer kpp_…
The manifest lists the published files; each file is served with Range support, so an interrupted download resumes where it stopped. A key without the bulk-download entitlement gets 403. The dataset IDs and the manifest’s format aren’t published yet: ask at hello@passwordradar.eu, for example when you ask for a key.
The base dataset is licensed CC BY-NC 4.0, plus an added permission: commercial self-hosting is allowed. Screening the passwords of your own staff and of your own product’s users is fine, paid products included. Selling the screening itself needs a written exemption from the author, Šimon Podlesný (hello@passwordradar.eu): reselling or redistributing the dataset, running it for other organisations, or making it a paid feature of a security product. The full terms are in Data and provenance.
Identity providers
- Plain REST
- live
Your own code can call PasswordRadar today with one GET request, from any language, as above.
- Integrations
- planned
Keycloak, Zitadel, Ory, Authentik, Nextcloud and Supabase Auth. Until they ship, an off-the-shelf identity provider such as Keycloak needs a small extension or script to make that call.
Design partners decide the order. Running one of these and want it sooner?