9ja Checkr API

JSON HTTP API for NAFDAC registration lookups. Use GET /api/verify/:nafdac with an x-api-key header. Free and API Pro tiers — Pro adds batch verify and product search. Not a government feed — disclaimer.

Overview

Lookups use the same public NAFDAC registration channel the site automates; responses may be served from our cache after the first fetch. All successful responses are JSON with an ok field.

All requests go to: https://api.9jacheckr.xyz

Free tier

Free
  • 300 API uses / month (single GET verify only)
  • 1 API key
  • GET /api/verify/:nafdac
  • Lower per-window rate limits
  • No dashboard metrics

Pro tier

Pro
  • 50,000 API uses / month (verify rows + successful search)
  • Multiple API keys
  • POST /api/verify/batch (up to 40 numbers)
  • GET /api/products/search (indexed DB)
  • Higher per-window rate limits
  • Full dashboard metrics
  • Commercial use

Authentication

Send your API key on every request as the x-api-keyheader (same as in the dashboard "Example request" snippet).

Request header
x-api-key: YOUR_API_KEY
💡
Get your API key from the dashboard. Free keys are available immediately after sign-up — no credit card required.
⚠️
Never expose your API key in client-side code or public repositories. Rotate it from the dashboard if compromised.

Quick start

Create a key under API Keys, then run (replace YOUR_API_KEY):

curl
curl -sS "https://api.9jacheckr.xyz/api/verify/01-5713" \
  -H "x-api-key: YOUR_API_KEY"
💡
Use encodeURIComponent on the path segment if the registration contains characters that need encoding.
Response — 200 OK (found)
{
  "ok": true,
  "product": {
    "nafdac": "01-5713",
    "name": "Example product name",
    "category": "Food",
    "source": "nafdac-registration",
    "manufacturer": "Example manufacturer",
    "approvedDate": "2019-04-01T00: 00: 00.000Z",
    "expiryDate": "2024-04-01T00: 00: 00.000Z",
    "ingredients": []
  }
}
Response — 404 (not found)
{
  "ok": false,
  "code": "NOT_FOUND",
  "message": "Product not found for this NAFDAC number",
  "nafdac": "99-9999"
}

Endpoints

Verify a product

FreePro

Look up one registration number. Returns ok: true and a product object when found, or 404 with NOT_FOUND when not. Counts as one monthly API use (Free or Pro).

GET/api/verify/:nafdac

Path

nafdac*stringNAFDAC registration number (e.g. 01-5713, A1-5645). Pass raw or URL-encoded in the path.
Request
curl -sS "https://api.9jacheckr.xyz/api/verify/01-5713" \
  -H "x-api-key: YOUR_API_KEY"
Response — 200 (found)
{
  "ok": true,
  "product": {
    "nafdac": "01-5713",
    "name": "…",
    "category": "Food",
    "source": "nafdac-registration",
    "manufacturer": "…",
    "approvedDate": "2019-04-01T00: 00: 00.000Z",
    "expiryDate": "2024-04-01T00: 00: 00.000Z",
    "ingredients": ["…"]
  }
}
Response — 404 (not found)
{
  "ok": false,
  "code": "NOT_FOUND",
  "message": "Product not found for this NAFDAC number",
  "nafdac": "99-9999"
}

Batch verify

Pro

Up to 40 numbers per request. Each row (found or not found) counts as one unit toward the same monthly API cap as single verify. Requires API Pro — same x-api-key header and JSON body { "nafdac": string[] }.

POST/api/verify/batch
Request
curl -sS -X POST "https://api.9jacheckr.xyz/api/verify/batch" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{"nafdac":["01-5713","04-8127"]}'
Response — 200 OK
{
  "ok": true,
  "results": [
    {
      "nafdac": "01-5713",
      "ok": true,
      "product": {
        "nafdac": "01-5713",
        "name": "…",
        "category": "Food",
        "source": "nafdac-registration",
        "manufacturer": "…",
        "approvedDate": null,
        "expiryDate": null,
        "ingredients": []
      }
    },
    {
      "nafdac": "99-9999",
      "ok": false,
      "code": "NOT_FOUND",
      "message": "Product not found for this NAFDAC number"
    }
  ]
}

Product search

Pro

API Pro only. Query our indexed product database (built from registrations we have seen) — not a live NAFDAC request per search. Multi-word q: every token must match somewhere (any field). Optional limit 1–50 (default 20). Each successful search response counts one monthly unit. Minimum query length: 2 characters.

GET/api/products/search?q=&limit=

Query parameters

q*stringSearch string (≥2 chars). Tokenized; all tokens must match.
limitnumber1–50, default 20.
Request
curl -sS "https://api.9jacheckr.xyz/api/products/search?q=sardine&limit=10" \
  -H "x-api-key: YOUR_API_KEY"
Response — 200 OK
{
  "ok": true,
  "results": [
    {
      "nafdac": "01-5713",
      "name": "TITUS SARDINE IN VEGETABLE OIL",
      "category": "Food",
      "manufacturer": "UNIMER S.A"
    }
  ]
}

Photo verify (Telegram)

Label photo OCR and extraction are not exposed on this HTTP API. They are available through Telegram Bot Pro via @NaijaCheckrBot (/upgrade).

💡
Integrators who need images should ask users for the NAFDAC number as text, or direct them to the bot for photo verify.

JavaScript SDK

Official @9jacheckr/sdk wraps the same REST endpoints with typed results. Uses global fetch (Node 18+). The client always talks to https://api.9jacheckr.xyz — for a local API, use the HTTP examples above.

⚠️
Do not ship your API key in browser bundles. Use the SDK on the server or in scripts only.
Install
npm install @9jacheckr/sdk

All methods return a Promise that resolves (no throw for API/network errors). Branch on result.ok. Per-request timeout is about 25 seconds.

CheckrClient

ESM
import { CheckrClient } from '@9jacheckr/sdk';

const client = new CheckrClient({
  apiKey: process.env.CHECKR_API_KEY!,
});

const result = await client.verify('01-5713');

if (result.ok) {
  console.log(result.product.name, result.product.manufacturer);
} else {
  console.error(result.code, result.message);
}

Methods

verify(nafdac)Promise<VerifyResult>GET /api/verify/:nafdac — Free or Pro. Empty input returns ok: false with INVALID_NAFDAC (request not sent).
verifyBatch(numbers)Promise<BatchVerifyResult>POST /api/verify/batch — API Pro. Up to 40 strings per call; each row counts toward monthly usage. Empty list → INVALID_BODY.
searchProducts(query, { limit? })Promise<SearchResult>GET /api/products/search — API Pro. query ≥ 2 characters; limit 1–50 (default 20). Each successful response counts one monthly unit.
Batch (API Pro)
const batch = await client.verifyBatch(['01-5713', '04-8127']);

if (batch.ok) {
  for (const row of batch.results) {
    if (row.ok) console.log(row.nafdac, row.product.name);
    else console.log(row.nafdac, row.code, row.message);
  }
} else {
  console.error(batch.code, batch.message);
}
Product search (API Pro)
const search = await client.searchProducts('sardine', {
  limit: 10,
});

if (search.ok) {
  for (const hit of search.results) {
    console.log(hit.nafdac, hit.name, hit.manufacturer);
  }
} else {
  console.error(search.code, search.message);
}

Exported types include Product, VerifyResult, BatchVerifyResult, SearchResult, and row/hit types. Extra SDK-only code values include INVALID_RESPONSE, TIMEOUT, and NETWORK_ERROR.

💡
Source and README: monorepo path packages/sdk (see npm package @9jacheckr/sdk).

Rate limits

Two layers: a monthly usage cap per account (Free vs API Pro) and a sliding-window limiter on verify, batch, and search routes. Over the monthly cap you get 429 with PLAN_QUOTA_EXCEEDED. Over the window limit you get 429 with RATE_LIMITED.

PlanMonthly usage cap~Per 15 min / API key
Free300 units — single GET verify only (no batch/search)45 req / 15 min
Pro50,000 units — each verify row + each successful search220 req / 15 min

Successful responses may include RateLimit-* style headers from the sliding limiter (draft standard). Monthly remaining usage is shown in your dashboard, not only in response headers.


Error codes

All errors follow a consistent shape. The HTTP status indicates the category; the code field is machine-readable.

Error response shape
{
  "ok": false,
  "code": "NOT_FOUND",
  "message": "Product not found for this NAFDAC number",
  "nafdac": "99-9999"
}
StatusCodeMeaning
400INVALID_NAFDACMissing or empty path parameter
400INVALID_BODYBatch: body must include nafdac string array
400INVALID_QUERYSearch: q too short or no valid tokens
401UNAUTHORIZEDMissing or invalid API key
403FEATURE_REQUIRES_PROBatch or search needs API Pro
403KEY_PLAN_DISABLEDNon-primary key on Free plan
404NOT_FOUNDNo product for this NAFDAC number (verify)
429PLAN_QUOTA_EXCEEDEDMonthly API cap reached
429RATE_LIMITEDSliding window limit hit
500INTERNAL_ERRORUnexpected server error

Code examples

JavaScript / Node.js

fetch (Node 18+ / browser)
const url = `https://api.9jacheckr.xyz/api/verify/` + encodeURIComponent("01-5713");
const response = await fetch(url, {
  headers: { "x-api-key": "YOUR_API_KEY" },
});

const data = await response.json();

if (data.ok && data.product) {
  console.log(data.product.name);
}

Python

requests
import urllib.parse
import requests

nafdac = urllib.parse.quote("01-5713", safe="")
r = requests.get(
    f"https://api.9jacheckr.xyz/api/verify/{nafdac}",
    headers={"x-api-key": "YOUR_API_KEY"},
    timeout=30,
)
data = r.json()
if data.get("ok") and data.get("product"):
    print(data["product"]["name"])

TypeScript — typed helper

nafdac.ts
const API_KEY = process.env.CHECKR_API_KEY!;
const BASE = "https://api.9jacheckr.xyz";

export type Product = {
  nafdac: string;
  name: string;
  category: string;
  source: string;
  manufacturer: string;
  approvedDate: string | null;
  expiryDate: string | null;
  ingredients: string[];
};

export type VerifySuccess = { ok: true; product: Product };
export type VerifyError = {
  ok: false;
  code: string;
  message: string;
  nafdac?: string;
};

export async function verifyNafdac(
  nafdac: string,
): Promise<VerifySuccess | VerifyError> {
  const path = encodeURIComponent(nafdac);
  const res = await fetch(`${BASE}/api/verify/${path}`, {
    headers: { "x-api-key": API_KEY },
  });
  return res.json() as Promise<VerifySuccess | VerifyError>;
}

Upgrade to Pro

Need more than 300 lookups per month, multiple keys, batch verify, or product search? Upgrade to API Pro from your dashboard. Billed at ₦10,000/month. Photo verify stays on Telegram Bot Pro only.

API Pro

₦10,000/ month

Pro
  • 50,000 API uses / month
  • Multiple API keys
  • POST /api/verify/batch
  • GET /api/products/search
  • Higher sliding-window limits
  • Dashboard metrics
  • Commercial use
Upgrade in dashboard
💡
Questions or need a custom volume plan? Email us.