How to score test passwords in bulk via the server-safe zxcvbn api
- Step 1Confirm your data is synthetic, not real — The API transmits the password to the server. Only ever send generated, test, or fixture passwords through it. If any value is a real, in-use credential, score it in the browser tool instead — that path keeps it local.
- Step 2Submit one password per request — There's no batch endpoint. Send the
passwordin the request (viaoptions.passwordor the request body). The server runs zxcvbn 4.4.2 and returns JSON. To audit a dataset, loop over your list and call once per entry, controlling concurrency in your own code. - Step 3Parse the four returned fields — Read
score(0–4),crackTime(an object with the four scenario keys),feedback(warning + suggestions), andguesses(raw estimate). There's noguesses_log10in the API response — computeMath.log10(guesses)yourself if you want the log. - Step 4Aggregate into the metric you need — Bucket by
scorefor a quick distribution, or take log10 ofguessesfor a finer comparison across candidates. For a policy gate, assert every fixture'sscoremeets your floor and fail the build if one doesn't. - Step 5Handle the empty / invalid case — An empty password throws
Provide a password via options.password or request body.. Filter blank lines from your dataset before the loop, or catch the error per call so one bad row doesn't abort the run. - Step 6Keep pattern-level analysis in the browser — The API omits the
sequencearray, so if a candidate needs per-token diagnosis (which word was cheap, was it aspatialwalk) audit that single one in the browser tool, which returns the full zxcvbn JSON. For protecting any exported dataset, use aes-256-encryptor.
Browser tool vs server-safe API: what each returns
Same zxcvbn 4.4.2 engine, different response shapes. Choose the API for bulk synthetic scoring; the browser tool for real secrets and full pattern detail.
| Field / property | Browser tool | Server-safe API |
|---|---|---|
score (0–4) | Yes | Yes |
crackTime / crack_times_display | Yes (four scenarios) | Yes (four scenarios) |
feedback (warning + suggestions) | Yes | Yes |
guesses (raw) | Yes | Yes |
guesses_log10 | Yes | No — compute log10(guesses) |
sequence (matched patterns) | Yes | No |
| Password transmitted? | No (in-page) | Yes (in the request) |
| Best for | Real secrets, full detail | Synthetic/test datasets, automation |
Aggregations you can build from the API response
What to compute from looping the API over a dataset, and which returned field feeds it.
| Goal | Field used | How to compute |
|---|---|---|
| Score distribution of a wordlist | score | Count candidates per 0–4 bucket |
| Median strength of a generator | guesses | Median of log10(guesses) across outputs |
| Policy regression gate | score | Assert every fixture >= your floor; fail build otherwise |
| Weakest-candidate report | guesses, feedback | Sort ascending by guesses; surface feedback.warning |
| Threat-model crack-time table | crackTime | Pick the relevant scenario key per row |
Cookbook
Patterns for scripting the server-safe API over a synthetic dataset. The response shape is real (score, crackTime, feedback, guesses); these snippets are pseudocode showing the loop and aggregation, not a guarantee of endpoint paths. Synthetic data only.
Loop a generated wordlist, one call per password
There's no batch endpoint, so iterate and call once per candidate. Collect score and guesses for each. This is the core pattern for grading any dataset.
for (const pw of syntheticWordlist) {
const res = await auditApi({ password: pw }); // server-safe path
// res = { score, crackTime, feedback, guesses }
results.push({ pw, score: res.score, log10: Math.log10(res.guesses) });
}
// guesses_log10 is NOT in the response -> derive it as Math.log10(guesses)Build a score distribution histogram
Bucket the dataset by the 0–4 score to see how many candidates a generator produces in each band. A healthy generator should cluster at 3–4.
const buckets = [0,0,0,0,0];
for (const pw of dataset) {
const { score } = await auditApi({ password: pw });
buckets[score]++;
}
console.log(buckets);
// e.g. [ 12, 40, 180, 520, 248 ] -> most outputs at score 3, goodPolicy regression gate in CI
Feed your known-weak fixtures through and assert each is rejected by your score floor. Fail the build if any slips above the floor unexpectedly, or below it for strong fixtures.
const FLOOR = 3;
for (const weak of knownWeakFixtures) {
const { score } = await auditApi({ password: weak });
assert(score < FLOOR, `${weak} should be rejected but scored ${score}`);
}
// Strong fixtures asserted the other way: score >= FLOORSurface the weakest candidates with their reason
Sort by raw guesses ascending and print feedback.warning so you can see WHY the bottom of the dataset is weak.
const scored = [];
for (const pw of dataset) {
const r = await auditApi({ password: pw });
scored.push({ pw, guesses: r.guesses, warn: r.feedback.warning });
}
scored.sort((a,b) => a.guesses - b.guesses);
console.log(scored.slice(0, 10)); // 10 weakest + their warningsHandle the empty-password error per row
An empty value throws. Filter blanks first, and wrap each call so one bad row doesn't abort the whole run.
for (const pw of rawRows) {
if (!pw) continue; // skip blanks
try {
const r = await auditApi({ password: pw });
results.push(r);
} catch (e) {
// e.message: "Provide a password via options.password or request body."
errors.push({ pw, error: e.message });
}
}Edge cases and what actually happens
Empty password in the dataset
RejectedAn empty value throws Provide a password via options.password or request body. on the server path (the browser tool throws Enter a password to audit.). Filter blank rows before the loop, or catch the error per call so one empty entry doesn't abort the whole run.
Sending a real, in-use credential through the API
Transmitted by designThe API path sends the password to the server — that's how server-side scoring works. It's appropriate only for synthetic, generated, or test passwords. Never route a real credential through it; for those, the browser tool runs zxcvbn in-page and the value never leaves the device (the field is marked secret).
Expecting guesses_log10 in the API response
Not returnedThe server-safe response includes score, crackTime, feedback, and guesses — but not guesses_log10. Compute it yourself with Math.log10(guesses). Only the browser tool's full zxcvbn JSON exposes guesses_log10 directly.
Expecting the matched-pattern sequence array
Not returnedThe API omits the sequence array, so you can't see per-token patterns (dictionary:passwords, spatial, date) from a bulk call. For per-candidate pattern diagnosis, audit that single password in the browser tool, which returns the complete zxcvbn output.
Looking for a native batch endpoint
Not supportedThere's no batch or CSV endpoint — the tool scores one password per call. To audit a dataset you loop in your own code and control concurrency and rate. The browser tool likewise has a single field and no batch UI.
Dictionaries frozen at zxcvbn 4.4.2
Known limitationServer-side scoring uses the same zxcvbn 4.4.2 word lists as the browser, frozen at that release (2017-era). A dataset full of post-2017 meme- or breach-derived passwords won't be flagged as common even though attackers know them. Treat the scores as a strong lower bound on weakness, not proof of strength.
No user_inputs context passed
By designzxcvbn supports a user_inputs dictionary, but this tool calls it with the password only and passes no per-row identity context. So a candidate built from a fictional persona's name won't be penalised as identity-based. If your dataset models that threat, account for it separately — the API won't flag it.
Very long passphrase entries
SupportedLong passphrases are scored fine server-side and calc_time stays small. The score caps at 4, so use guesses (and its log10) to differentiate strong long entries rather than the coarse score. No file-size limit applies — it's a string, not a file.
Non-ASCII or emoji in dataset entries
Supportedzxcvbn operates on the raw string, so emoji and accented characters are accepted (counted as bruteforce) and scored without error on the server path. Just remember real login systems may mangle non-ASCII independent of the score, which matters if your dataset is meant to mirror production input.
Encrypting the dataset before or after scoring
Use a sibling toolThis tool only scores; it doesn't store or protect your dataset. To encrypt an exported results file or a synthetic corpus, use aes-256-encryptor (Web Crypto AES-GCM, PBKDF2 100k). To checksum it for integrity, use multi-hash-fingerprinter.
Frequently asked questions
Can I audit thousands of passwords in bulk?
Yes, but you loop one password per call — there's no batch endpoint. The slug is server-safe, so each request runs zxcvbn 4.4.2 server-side and returns score, crackTime, feedback, and guesses as JSON. Your script controls iteration, concurrency, and rate. This is intended for synthetic or test datasets; never send real credentials through the API.
What does the API return for each password?
Four fields: score (0–4), crackTime (the four-scenario crack-time object), feedback (warning plus suggestions), and guesses (the raw estimate). It does not return guesses_log10 (compute Math.log10(guesses)) or the sequence pattern array — those are only in the browser tool's full zxcvbn JSON.
Why isn't there a guesses_log10 field in the API response?
The server-safe path returns a trimmed object with score, crackTime, feedback, and guesses only. guesses_log10 is part of zxcvbn's full output, which the browser tool exposes. For scripted comparisons just derive it yourself: Math.log10(guesses) gives the same value the browser shows.
Is it safe to send real passwords to the API?
No. The API transmits the password to the server so it can score it. That's fine for generated, synthetic, or test passwords, but never for a real, in-use credential. For real secrets, use the browser tool — zxcvbn runs in-page there, the value never leaves the device, and the field is marked secret so it isn't logged.
How do I grade my password generator with this?
Loop the API over a sample of your generator's output, collect score and guesses for each, then compute the score distribution and the median of log10(guesses). A strong generator should cluster at score 3–4 with high log10 values. If too many land at 0–2, your generator's outputs are guessable and need more length or entropy.
Can I use it as a regression test for my password policy?
Yes — that's a great fit. Keep a fixture set of known-weak passwords and assert each comes back below your score floor, and a set of known-strong ones that should meet it. Run the loop in CI and fail the build if any fixture's score crosses the line unexpectedly. Same zxcvbn 4.4.2 engine as production, so scores won't drift.
How do I handle empty or malformed rows in my dataset?
An empty password throws Provide a password via options.password or request body.. Filter blank lines before the loop, and wrap each call in try/catch so one bad row doesn't abort the run — push failures to an errors array with the message. The tool only validates that the password is non-empty; it doesn't otherwise reject malformed strings.
Does the API tell me which pattern made a password weak?
Not in bulk — the API omits the sequence array, so you get the score and feedback warning but not the per-token pattern breakdown (dictionary:passwords, spatial, date). When you need to diagnose a specific candidate, audit that one password in the browser tool, which returns the full zxcvbn JSON including sequence.
Do server-side scores match what users see in the browser?
Yes. Both paths run the same zxcvbn 4.4.2 engine, so a given password gets the same score, crackTime, and guesses whether scored server-side or in-page. The only difference is the response shape: the API trims out guesses_log10 and sequence. Your tests therefore reflect exactly what production users will experience.
Are there file-size or tier limits for the API?
The input is a password string, not a file, so the security file-size caps don't apply to the value itself. The auditor is a Free-tier tool. Because you loop one call per password, throughput is governed by your own concurrency and any platform rate limits, not by a per-file size cap.
How current are the dictionaries the API scores against?
They're the zxcvbn 4.4.2 word lists, frozen at that release (2017-era), same as the browser tool. A dataset built around newer memes or breaches won't be flagged as common even though attackers may know those passwords. Use the scores as a strong lower bound on weakness and pair them with a live breach-list check if your pipeline needs current coverage.
How do I protect the dataset and results afterward?
This tool only scores — it doesn't store or secure your data. Encrypt an exported results file or a synthetic corpus with aes-256-encryptor (Web Crypto AES-GCM, PBKDF2 100k), checksum it with multi-hash-fingerprinter, and verify two copies match later with file-integrity-monitor.
Privacy first
Every JAD Security operation runs entirely in your browser. Files, passwords, and PGP private keys never leave your device — verified by zero outbound network requests during processing.