Using a background check API in Node.js.
A working tutorial for calling a background check API from Node.js with the built-in fetch: a small client, TypeScript types for the response, criminal records, batch, an Express webhook handler and retries.
25 free calls · No card · $0.15 a call, $0.11 past 2,000 a month
const res = await fetch(
'https://api.offendersearch.app/v1/search',
{
method: 'POST',
headers: {
'X-API-Key': process.env.OFFENDERSEARCH_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: {
firstName: 'John',
lastName: 'Doe',
dob: '1980-04-12',
},
}),
},
);
const { records, sourceStatus } = await res.json();What you need to call a background check API from Node.js
Node 18 or newer, which ships fetch, and an API key in the OFFENDERSEARCH_KEY environment variable. Sign up to create one — it works at once on 25 free calls, no card. There is no package to install.
One rule before any code: the key belongs on your server. Never call the API from browser JavaScript, where the key would be visible to every visitor. Call your own backend route, and let that route call the API.
A small fetch client and your first search
Every endpoint takes the same X-API-Key header and returns JSON, so one helper covers the API. It sets a timeout with AbortSignal.timeout and turns a non-2xx response into an error carrying the API’s own error.code and error.message.
// client.mjs — Node 18+ has fetch built in, so there is nothing to install.
const BASE = 'https://api.offendersearch.app';
export async function api(path, body, { method = 'POST', headers = {} } = {}) {
const res = await fetch(BASE + path, {
method,
headers: {
'X-API-Key': process.env.OFFENDERSEARCH_KEY,
'Content-Type': 'application/json',
...headers,
},
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(60_000),
});
const json = await res.json();
if (!res.ok) {
const err = new Error(json.error?.message ?? res.statusText);
err.status = res.status;
err.code = json.error?.code;
err.retryAfter = Number(res.headers.get('Retry-After')) || undefined;
throw err;
}
return json;
}
const data = await api('/v1/search', {
query: { firstName: 'John', lastName: 'Doe', dob: '1980-04-12' },
});
console.log(data.records.length, 'records');Type the response and triage the matches
Two fields drive every decision: matchState on each record and sourceStatus on the response. Typing them keeps a reviewer queue honest — only a full date-of-birth match is treated as an identification.
type MatchState = 'dob_match' | 'year_match' | 'age_match' | string;
interface SearchRecord {
matchState: MatchState;
matchConfidence: number;
matchBasis: string;
sources: { code: string; level: string }[];
}
interface SearchResponse {
records: SearchRecord[];
sourceStatus: { source: string; ok: boolean; incomplete: boolean }[];
}
export function triage(res: SearchResponse) {
const confirmed = res.records.filter((r) => r.matchState === 'dob_match');
const review = res.records.filter((r) => r.matchState !== 'dob_match');
const unanswered = res.sourceStatus.filter((s) => !s.ok).map((s) => s.source);
return { confirmed, review, unanswered };
}All match states are defined in the matching reference; all record fields in the record object.
Search criminal records from Node.js
Criminal records are at POST /v1/criminal/search on the same key. A last name is required, results page with page and perPage, and each record states its type — booking, custody, court case or warrant. See the criminal record reference.
const res = await fetch(
'https://api.offendersearch.app/v1/criminal/search',
{
method: 'POST',
headers: {
'X-API-Key': process.env.OFFENDERSEARCH_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: {
firstName: 'Morgan',
lastName: 'Okafor',
dob: '1990-06-18',
state: 'OK',
},
page: 1,
perPage: 50,
}),
},
);
const { status, records, sourceStatus } = await res.json();Batch up to 1,000 people in one request
POST /v1/batch takes a queries array of up to 1,000 people. Results come back with the index of the row they answer. Prefer one batch to a loop of single calls: it is one round trip and is billed the same, per row.
const people = [
{ firstName: 'John', lastName: 'Smith', state: 'FL', dob: '1980-04-12' },
{ firstName: 'Jane', lastName: 'Doe', state: 'TX' },
];
const { results } = await api('/v1/batch', { queries: people });
for (const r of results) {
const person = people[r.index]; // join back by index
console.log(person.lastName, r.status, r.counts.records);
}Async searches and an Express webhook handler
POST /v1/searches accepts a search and returns a searchId; a webhookUrl gets a notification when it finishes. Acknowledge the webhook straight away, then fetch the search with your own key — that response is the source of truth.
import express from 'express';
import { randomUUID } from 'node:crypto';
import { api } from './client.mjs';
// 1. Start the search. Reuse the same Idempotency-Key if you retry this request.
const job = await api(
'/v1/searches',
{
query: { lastName: 'Doe', firstName: 'John', dob: '1980-04-12' },
webhookUrl: 'https://example.com/hooks/offendersearch',
},
{ headers: { 'Idempotency-Key': randomUUID() } },
);
// 2. Receive the notification. It is unsigned and sent once, so fetch the
// search with YOUR key and trust that response instead of the payload.
const app = express();
app.post('/hooks/offendersearch', express.json(), async (req, res) => {
res.sendStatus(200);
const result = await api(`/v1/searches/${req.body.searchId}`, null, { method: 'GET' });
await saveResult(result.searchId, result); // de-duplicate on searchId
});Retries, rate limits and error codes
Retry on 429 and on 5xx only. A 429 carries a Retry-After header in seconds; honour it rather than guessing.
export async function withRetry(fn, attempts = 4) {
for (let i = 0; ; i++) {
try {
return await fn();
} catch (err) {
const retryable = err.status === 429 || err.status >= 500;
if (!retryable || i === attempts - 1) throw err;
const wait = err.retryAfter ? err.retryAfter * 1000 : 2 ** i * 1000;
await new Promise((r) => setTimeout(r, wait));
}
}
}
const data = await withRetry(() =>
api('/v1/criminal/search', { query: { lastName: 'Okafor', state: 'OK' } }),
);| Status | Retry? | Meaning |
|---|---|---|
| 400 | No | A parameter is missing or invalid |
| 401 | No | Missing or invalid X-API-Key |
| 402 | No | A payment method is needed to continue |
| 422 | No | Understood and declined; the message says how to narrow the query |
| 429 | Yes, after Retry-After | Rate limit or quota reached |
| 5xx | Yes, with backoff | A temporary server-side failure |
Full list in the errors reference.
Using it from Next.js, Express or a serverless function
In Next.js, call the API from a route handler or a server action, never from a client component. In Express, wrap it in a route your front end calls. In a serverless function, prefer the async endpoint for anything that might outlast the platform’s request limit, and let the webhook wake a second function.
Standard API results are not a consumer report and must not be used for a hiring or tenant decision; those run as FCRA workflows through FCRA Partner Access.
More on the background check API
The product is on the background check API page. The same walkthrough in Python is background check API in Python, and the language-neutral guide is background check API integration. For AI agents, the MCP server for offender search exposes the same searches as tools.
Background check API Node.js questions
Is there an npm package for the background check API?
No package is needed. Node 18+ includes fetch, and the client on this page is about twenty lines.
Can I call the background check API from the browser?
No. That would expose your API key. Call it from your server and return only what the page needs.
Does it work with TypeScript?
Yes. The types on this page cover the fields that drive decisions, and the OpenAPI definition at /openapi.json can generate a full client.
Can I use axios instead of fetch?
Yes. Any HTTP client works with the same URLs, header and JSON bodies.
How do I verify the webhook?
The async-search webhook is unsigned and sent once. Treat it as a notification and fetch GET /v1/searches/{searchId} with your key to get the result.
How many checks can I send at once?
Up to 1,000 people per batch request. Send larger lists as several batches.
Are there free calls for testing from Node.js?
Every new account gets 25 free calls with no card, on the production endpoints.
Does it run on Vercel, Cloudflare Workers or AWS Lambda?
Yes. It is plain HTTPS, so any runtime with fetch can call it. Keep the key in the platform’s secret store.
Run the first search from Node
Create a key, export it, and run client.mjs. 25 calls are free.
The Offendersearch APIs
One account, one key, one bill — the sex offender registry and criminal records, side by side.
Sex offender registry
- Sex offender API — One call across 58 US sex offender registries, with scored matches and a citation on every record.
- Sex offender search API — Endpoints, request shapes, record fields, matching and batch, in detail.
- Sex offender database — The national registry data in one schema — photos, aliases, offenses, addresses.
- Registered offender database — What a registered offender record holds, and how its accuracy is measured.
- Bulk sex offender search — Screen a whole list of names in one upload or one API call.
- Sex offender registry API — Choose which of the 58 registries a search covers, and see which ones answered.
- Offender search API — The registry search and the criminal search side by side, on one key.
Criminal records
- Criminal records API — County, state and federal records — jail, prison, court and warrant — through one API.
- Criminal background check API — One cited answer per person for onboarding and screening flows.
- Criminal search API — The search endpoint itself: parameters, record types and coverage, call by call.
- Court records API — Criminal court cases with charges and a disposition on each charge.
- Arrest records API — County jail bookings: booking date, custody status, facility and photo link.
- Inmate search API — State prison and county jail custody, with sentence, release and parole dates.
- Background check API — The registry and criminal layers together, on one key and one bill.
- Employment background check API — The registry and criminal data layer for HR, ATS and screening platforms.
- Employment screening API — The stages of a hiring pipeline, and where criminal and registry checks sit in it.
- Bulk criminal record search — Run a list of names against criminal records in one call.
- Statewide criminal search — One state at a time: prison, court and warrant systems that cover the whole state.
- Federal criminal records — Federal criminal records through the same API, and what a federal search covers.
Build with it
- API documentation — Quickstart, every endpoint, the record object and the OpenAPI spec.
- Sex offender map API — Clusters, pins and names for your own map, and the full record when one is opened.
- Background check API pricing — $0.15 a call, $0.11 past 2,000 a month, 25 free calls to start — worked out by volume.
- Background check API integration — Keys, the first call, async and webhooks, batch, monitoring and errors, step by step.
- Free background check API — 25 free calls on real data, no card — what is included and what comes after.
- Pay-as-you-go background check API — Per call, per report or subscription: the pricing models, with costs by volume.
- Background check API in Python — A working tutorial with requests: search, batch, async, errors and retries.
- NSOPW API — Is there one? What exists, and a developer API for searching US registries.
- MCP server for offender search — Give Claude and other AI agents the same search as a tool.
- Integrations — Screening inside referral software — CarePort, ExtendedCare, Ensocare and Aidin.
- AI integration — Offender safety checks for AI agents and chatbots, with cited answers.
- Offendersearch vs offenders.io — A side-by-side comparison of the two registry APIs.
Screening and compliance
- Tenant background check — Screening applicants for a rental, and where an FCRA workflow is required.
- FCRA Partner Access — The FCRA workflow for hiring and tenant decisions — pricing by quote.
- FCRA-compliant background check data — Accuracy, disputes and permissible purpose for consumer reporting agencies.
- FCRA disputes API — Open, track and resolve a consumer dispute against a record.
- Sex offender monitoring — Watch a person or an address and get an alert when the registry changes.
- Tenant screening API — The registry and criminal layer for property software, with FCRA workflows for rental decisions.
- Non-FCRA background check API — What a public-records lookup may be used for, and when a decision needs FCRA instead.