Look up NAFDAC registration numbers and get product details as JSON through an HTTP API, a web dashboard, or a Telegram bot.
Disclaimer: 9ja Checkr is not affiliated with NAFDAC or any government body. We automate NAFDAC’s public web lookup and return structured data; we are not an official NAFDAC API. Results are informational — confirm compliance and critical decisions on official channels. Integrators should disclose this to their own users. Website: 9jacheckr.xyz/disclaimer.
This is the part people ask about most.
We do not use a NAFDAC “developer API” or a special data feed. NAFDAC exposes a public web tool where anyone can enter a registration number and see whether a product is on the register. Our server uses that same public flow: it submits the number the way a browser would, reads the HTML page NAFDAC sends back, and extracts the fields we need (name, manufacturer, category, dates, ingredients, etc.).
In plain steps:
- Request - Your client calls our API with a NAFDAC number (e.g.
GET /api/verify/01-5713with an API key). - Our server → NAFDAC - The backend sends an HTTP POST to NAFDAC’s verification URL with the certificate number and the same kind of form data their site expects (including things like an anti-forgery token and session cookie). Those are configuration values in the server environment, not secrets inside this repo.
- Response - NAFDAC returns a web page (HTML), not JSON. We parse that HTML to build a structured product record.
- Your client - We return that record as JSON from our API.
So technically: automation of NAFDAC’s public verification page + HTML parsing.
Things to be aware of
- If NAFDAC changes their page layout, our parser may break until it’s updated.
- We store successful results in our database so we don’t hit NAFDAC on every repeat request. Stale rows are re-fetched on demand (see
apps/server/README.md, product cache env vars).
Relevant code: apps/server/src/utils/nafdacRegistrationClient.ts (request) and apps/server/src/utils/nafdacHtmlParser.ts (parsing).
| Piece | Role |
|---|---|
| Web | Next.js marketing site, public /verify via same-origin BFF (/api/verify-lookup), login, dashboard (API keys). |
| Server | Express: verify route, optional Mongo cache, Better Auth + API keys, bot hooks. |
| Bot | Telegram bot that calls the API with an internal token. |
| MongoDB | Products (cached lookups), users/sessions (auth), API keys, usage metrics, bot data. |
Clients talk only to our API. The API is the only part that talks to NAFDAC’s portal (when a number isn’t already cached).
Call the 9ja Checkr API from your backend, script, or mobile app to look up a NAFDAC registration number and get JSON back. Every response has an ok boolean; errors always include a code and human-readable message.
- Sign in on the 9ja Checkr website (Google sign-in).
- Open the dashboard and create an API key under the keys section.
- Copy the key when it is shown — the full secret is only displayed once. Store it in an environment variable or secrets manager, not in source control.
If the key is rotated or revoked, old keys stop working immediately (INVALID_API_KEY).
Production: https://api.9jacheckr.xyz
Use HTTPS. All API paths are under /api/....
Method: GET
URL: https://api.9jacheckr.xyz/api/verify/{nafdac}
Replace {nafdac} with the number from the product label (e.g. 01-5713). If the value has spaces or odd characters, URL-encode it (e.g. encodeURIComponent in JavaScript).
Required header:
| Header | Value |
|---|---|
x-api-key |
Your API key (starts with njc_). |
You do not need cookies or a session for verify — the key is enough.
Monthly API usage quota: Counts verify outcomes (each successful lookup and each not-found, including every row in batch) plus each successful product search request (GET /api/products/search). Free: 300 per UTC calendar month (verifies only; search requires Pro). API Pro: 50,000 combined units per month (subscription billed separately, e.g. ₦10,000/mo via Paystack). Exceeding the cap returns 429 PLAN_QUOTA_EXCEEDED.
Website lookup (not for third-party apps): The /verify page calls your own Next.js route POST /api/verify-lookup (same origin, no secret in the browser). That server route forwards to the API with an internal header. GET /api/public/verify/{nafdac} on the API requires a shared server secret (WEB_VERIFY_INTERNAL_SECRET); it is not documented for integrators and is not usable from browsers without that secret. Use GET /api/verify/{nafdac} + x-api-key for integrations.
When the number is found on the register, the body looks like:
{
"ok": true,
"product": {
"nafdac": "01-5713",
"name": "…",
"category": "…",
"source": "…",
"manufacturer": "…",
"approvedDate": "2025-07-30T00:00:00.000Z",
"expiryDate": "2030-07-29T00:00:00.000Z",
"ingredients": ["…"]
}
}| Field | Notes |
|---|---|
approvedDate / expiryDate |
ISO 8601 strings, or null if unknown. |
ingredients |
Array of strings; may be empty. |
Check ok: false and branch on code (and HTTP status) in your integration.
| HTTP | code |
What to do |
|---|---|---|
| 401 | MISSING_API_KEY |
Send the x-api-key header. |
| 401 | INVALID_API_KEY |
Key wrong, revoked, or typo — fix the key or create a new one in the dashboard. |
| 403 | KEY_PLAN_DISABLED |
On the Free plan, only your primary (oldest) key works. Use that key or upgrade to API Pro. |
| 400 | INVALID_NAFDAC |
Bad or empty number in the URL — validate input before calling. |
| 404 | NOT_FOUND |
Valid request, but no product for that number (not on the register from NAFDAC’s side). Show a clear message to your user. |
| 429 | RATE_LIMITED |
Too many requests — wait and retry with backoff; don’t hammer the API. |
| 500 | INTERNAL_ERROR |
Something failed on our side — retry later; if it persists, contact us. |
Limits are separate for the dashboard vs product API:
Dashboard (/api/keys/* — session cookies, keys, metrics, billing): high cap per client IP (see rateLimiter.ts dashboardKeysRateLimiter).
Product API (GET /api/verify/:nafdac, POST /api/verify/batch, GET /api/products/search with x-api-key or bot token): plan-based per account (and bot user id where applicable), 15-minute window — see verifyPlanRateLimiter.ts:
| Plan (API key) | Max requests / 15 min (verify + batch + search combined) |
|---|---|
| Free | 45 |
| API Pro | 220 |
Other mounts: /api/bot/* and /api/public/* have their own IP-based caps in rateLimiter.ts.
When you exceed a limit, the API responds with 429 and code: RATE_LIMITED. Responses may include RateLimit-* headers — use them to back off before retrying.
If you self-host, tune numbers in rateLimiter.ts and verifyPlanRateLimiter.ts.
cURL
curl -sS "https://api.9jacheckr.xyz/api/verify/01-5713" \
-H "x-api-key: YOUR_API_KEY_HERE"JavaScript (fetch)
const res = await fetch('https://api.9jacheckr.xyz/api/verify/01-5713', {
headers: { 'x-api-key': process.env.CHECKR_API_KEY },
});
const data = await res.json();
if (data.ok) {
console.log(data.product);
} else {
console.error(data.code, data.message);
}Node.js (@9jacheckr/sdk) — see packages/sdk/README.md.
From a browser: your site’s origin must be allowed for CORS (we allow the official web app origin). For server-side or mobile apps, call the API directly with the key as above.
If you are contributing or generating types from the server, see apps/server/src/types/types.ts (VerifyApiSuccess, VerifyApiErrorBody, ProductPlain).
This project is open source. If you deploy the stack yourself, base URL and limits depend on your hosting; see apps/server/README.md and env configuration there.
This project is licensed under the GNU Affero General Public License v3.0 (GNU AGPLv3; SPDX AGPL-3.0-only). Like the GPL, it is strong copyleft for distribution: modified or combined works generally require corresponding source under the same license, notice preservation, and documenting changes. AGPL adds obligations for network use: if you run a modified version as a service that users interact with over a network, you typically must offer those users the corresponding source as well. Commercial use, private use, modification, and patent use are allowed; the license disclaims liability and warranty. Full terms: LICENSE.
npm install
npm run dev -w web # site + dashboard
npm run dev -w server # API
npm run dev -w bot # TelegramEnv vars: see each app’s README.md (apps/web, apps/server, apps/bot).