Docs
API Reference

Breached Password API (range endpoint)

The range API implements a k-anonymity model that lets you check passwords against known breaches without exposing the password — or even its full hash — to our servers.

This in-browser demo runs against the live deployment using your signed-in console session (no API key needed here). In production you call the endpoint below with a project API key.

Try it liveConnecting…

The k-anonymity approach

Sending a full password hash to an external service creates a risk: an attacker who intercepts the hash can use rainbow tables to reverse it. The range-prefix model solves this by sending only a partial hash.

With a 5-character hex prefix, each request maps to roughly 1 million possible full hashes. The server returns every match for that bucket, and your application finds the exact match locally. An observer (including LeakJar) cannot determine which suffix you actually care about.

Endpoint

GET https://api.leakjar.com/v1/passwords/range/{prefix}

  • prefix: first 5 uppercase hex characters of the SHA-1 hash.
  • Authorization: Bearer token with a project API key (lj_…).
  • Quota: usage counts against your plan’s monthly check quota. See the RateLimit-* response headers and Errors & limits.
  • Cache: responses carry Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400. Safe to cache at the edge.

Response format

Content-Type: text/plain. One match per line, in SUFFIX:COUNT format (same shape as HIBP’s Pwned Passwords). The count is how many leaked rows in our corpus share that hash — treat it as a popularity / prevalence signal.

response.txttext
CBFDAC6008F9CAB4083784CBD1874F76618D2A97:3730471
7C4A8D09CA3762AF61E59520943DC26494F8941B:124812
D033E22AE348AEB5660FC2140AEC35850C4DA997:42
...

Step 1: Hash the password

Compute SHA-1 and convert to uppercase hex. This must happen on your server, never in the browser.

hash.tstypescript
import { createHash } from "crypto";

function sha1Hex(password: string): string {
  return createHash("sha1")
    .update(password, "utf8")
    .digest("hex")
    .toUpperCase();
}

Step 2: Extract the prefix

prefix.tstypescript
const hash = sha1Hex("user-password");
const prefix = hash.slice(0, 5);  // e.g. "CBFDA"
const suffix = hash.slice(5);     // remaining 35 chars

Step 3: Query the range endpoint

query.tstypescript
const res = await fetch(
  `https://api.leakjar.com/v1/passwords/range/${prefix}`,
  {
    headers: {
      Authorization: `Bearer ${process.env.LEAKJAR_API_KEY}`,
    },
  }
);

if (!res.ok) throw new Error(`LeakJar ${res.status}`);
const body = await res.text();
const entries = body
  .split("\n")
  .filter(Boolean)
  .map((line) => {
    const [s, c] = line.split(":");
    return { suffix: s, count: Number(c) };
  });

Step 4: Compare locally

compare.tstypescript
const match = entries.find((e) => e.suffix === suffix);
if (match) {
  console.log(`Password seen ${match.count} times in breaches.`);
}

Drop-in helper

check-password.tstypescript
import { createHash } from "crypto";

export interface BreachResult {
  breached: boolean;
  count: number;
}

export async function checkPassword(
  password: string
): Promise<BreachResult> {
  const hash = createHash("sha1")
    .update(password, "utf8")
    .digest("hex")
    .toUpperCase();

  const prefix = hash.slice(0, 5);
  const suffix = hash.slice(5);

  const res = await fetch(
    `https://api.leakjar.com/v1/passwords/range/${prefix}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.LEAKJAR_API_KEY}`,
      },
    }
  );
  if (!res.ok) throw new Error(`LeakJar ${res.status}`);

  for (const line of (await res.text()).split("\n")) {
    if (!line) continue;
    const [s, c] = line.split(":");
    if (s === suffix) {
      return { breached: true, count: Number(c) };
    }
  }
  return { breached: false, count: 0 };
}
Safety guarantee: LeakJar never receives the password, the full hash, or the user identity on this endpoint. The server only sees a 5-character prefix, which by design maps to roughly a million possible hashes.