Next.js · Email breach checks
Check email breaches in a Next.js route handler
This route handler at app/api/breaches/email/route.ts checks the signed-in user's own address rather than one supplied in the request body. That keeps the endpoint from becoming a public lookup service anyone could use to probe other people's emails or spend your credits. The handler calls Finowl through the shared helper, then maps each breach's Title, BreachDate, DataClasses and IsVerified fields into a smaller camelCase shape for your UI.
Errors are translated for the browser instead of passed through. A 400 becomes a 422 the UI can explain, a 401 is logged on the server and returned as 503 because it is a configuration problem, and a 429 keeps its Retry-After header so a client fetch can wait the right amount of time. Anything that is not a FinowlError, such as a timeout abort, is rethrown to Next.js.
Steps
- 1
Add lib/finowl.ts from the Next.js integration page and set FINOWL_API_KEY in .env.local.
- 2
Replace the auth import with the session helper from your own authentication setup.
- 3
Create app/api/breaches/email/route.ts with the example and start the dev server.
- 4
Call the route with fetch from a security settings page and render the breaches list.
- 5
Show a gentler label for breaches where verified is false.
npm install server-only// app/api/breaches/email/route.ts
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth"; // your session helper
import { finowl, FinowlError } from "@/lib/finowl";
type Breach = { Title: string; BreachDate: string; DataClasses: string[]; IsVerified: boolean };
type EmailBreachResult = { email: string; breached: boolean; breaches: Breach[] };
export async function POST() {
// Check the signed-in user's own address, so this route is not an open lookup service.
const session = await auth();
if (!session?.user?.email) {
return NextResponse.json({ error: "Sign in first." }, { status: 401 });
}
try {
const result = await finowl<EmailBreachResult>("/v1/breaches/email", { email: session.user.email });
return NextResponse.json({
breached: result.breached,
breaches: result.breaches.map((b) => ({
title: b.Title,
date: b.BreachDate,
exposed: b.DataClasses,
verified: b.IsVerified,
})),
});
} catch (err) {
if (!(err instanceof FinowlError)) throw err;
switch (err.status) {
case 400:
return NextResponse.json({ error: "That address cannot be checked." }, { status: 422 });
case 401:
console.error("Finowl rejected FINOWL_API_KEY");
return NextResponse.json({ error: "Breach check unavailable." }, { status: 503 });
case 429:
return NextResponse.json(
{ error: "Too many checks. Try again shortly." },
{ status: 429, headers: err.retryAfter ? { "Retry-After": String(err.retryAfter) } : undefined },
);
default:
return NextResponse.json({ error: "Breach check failed." }, { status: 502 });
}
}
}IN PRODUCTION
Production tips
Rate limit the route per user as well. Finowl allows 60 requests per minute per account, so one person refreshing a settings page should not be able to consume your whole budget.
Store each user's result in your database with a checked-at timestamp and serve it from there, re-querying Finowl on a schedule rather than every time the page loads.
Return only the fields the UI needs. Trimming the payload to title, date, exposed data and verification status keeps responses small and avoids exposing more than the page displays.
Each lookup costs 1 credit, with 100 free credits every month. See pricing and error codes.
A FEW GOOD QUESTIONS
Common questions
Related guides
- Check breached passwords in a Next.js server action
- Suggest gender from names in Next.js server actions
- Check email breaches in Python with requests
- Check email breaches in Node.js with fetch
- Check email breaches in TypeScript with typed results
- Check email breaches in Go with net/http
- Email breach checks for fintech signups and logins
- Email breach checks for online stores and shopper logins
- Email breach checks for SaaS logins and admins
Check email breaches in a Next.js route handler
Grab an API key, paste the example and run it. 100 free credits every month.