API documentation API v1
Check one phone number or e-mail in real time, or submit a list as a bulk job and download one result file. Every request uses a Bearer API key.
Authentication
Create a key on the API keys page, then replace the placeholder below in your own terminal. The examples are only text; copying them sends nothing.
https://api.numdetect.com# API origin: https://api.numdetect.com
export NUMDETECT_API_KEY='YOUR_API_KEY'- Send Authorization: Bearer <key> with every request. JSON requests also need Content-Type: application/json. No cookies, CSRF header or Origin header are needed.
- A key can read products, run single and bulk checks, read their results and read your balance history. Account settings, keys and top-ups are managed only in the signed-in dashboard.
- You can create up to 10 keys, for example one per system, and delete any of them at once.
Keep keys in your server environment. Do not put keys in URLs, screenshots, shared logs or committed source files.
Products
Read the current catalog before choosing product IDs. The response lists every product on sale with its input type, prices, bulk minimum and supported countries.
curl --fail-with-body --silent --show-error -X GET "https://api.numdetect.com/api/v1/products" \
-H "Authorization: Bearer $NUMDETECT_API_KEY"single and bulk tell whether the product is available for that kind of check right now. countries is ["*"] for every country, a list of country codes, or empty for e-mail products. Prices are USD micros per check.
Single check
POST /api/v1/checks takes one phone number or one e-mail address and a list of product IDs. The request waits until every product has answered (up to about 45 seconds) and returns the finished check.
curl --fail-with-body --silent --show-error -X POST "https://api.numdetect.com/api/v1/checks" \
-H "Authorization: Bearer $NUMDETECT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"phone":"+12025550147","products":["whatsapp","telegram"]}'curl --fail-with-body --silent --show-error -X POST "https://api.numdetect.com/api/v1/checks" \
-H "Authorization: Bearer $NUMDETECT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","products":["apple_email","spotify_email"]}'Write phone numbers with their + country code. For a number without one, add "country": "GB" (an ISO country code) to the request.
The response is the check: id, the normalized input, its country, charged and one entry in results per product. Each result has status, result (true or false when status is success) and info for extra fields some products return.
successA definite answer: result is true (registered) or false (not registered). Only these are charged.
unknownThe product could not tell. result is null and nothing is charged; never read it as false.
failedThe product could not be reached. result is null and nothing is charged; you can check again later.
curl --fail-with-body --silent --show-error -X GET "https://api.numdetect.com/api/v1/checks/$CHECK_ID" \
-H "Authorization: Bearer $NUMDETECT_API_KEY"curl --fail-with-body --silent --show-error -X GET "https://api.numdetect.com/api/v1/checks?limit=20" \
-H "Authorization: Bearer $NUMDETECT_API_KEY"Bulk check
POST /api/v1/bulk-jobs uploads a file as multipart form data. The file is split into one list per country, and each country is checked by each product you choose.
curl --fail-with-body --silent --show-error -X POST "https://api.numdetect.com/api/v1/bulk-jobs" \
-H "Authorization: Bearer $NUMDETECT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "[email protected]" \
-F "products=whatsapp,telegram" \
-F "input_kind=phone" \
-F "max_cost=50000000" \
-F "column=0" \
-F "header=true"- file: UTF-8 CSV or plain text, up to 100 MB and 1,000,000 rows. Other columns are kept and returned in the result file.
- input_kind is phone or email. column is the 0-based column with the numbers (default 0); header=true skips the first row.
- country (optional) is used for phone numbers written without a country code. Numbers starting with + or 00 keep their own country.
- max_cost is the most you accept to pay, in USD micros. The job is refused, and nothing is charged, if its cost would be higher or your balance is too low.
The response is the job with status running, its row counts (total, valid, duplicate, invalid), the amount held and one task per country and product. Keep id as your JOB_ID.
Bulk status and results
Read the job until status is completed, failed or cancelled. Large lists can take a while, so poll every minute or two, or use a webhook.
curl --fail-with-body --silent --show-error -X GET "https://api.numdetect.com/api/v1/bulk-jobs/$JOB_ID" \
-H "Authorization: Bearer $NUMDETECT_API_KEY"curl --fail-with-body --silent --show-error -X GET "https://api.numdetect.com/api/v1/bulk-jobs?limit=20" \
-H "Authorization: Bearer $NUMDETECT_API_KEY"Download the result file
When result_ready is true, GET /result returns a download link valid for 10 minutes. The link itself needs no key. Ask for a new link whenever you need to download again.
url=$(curl --fail-with-body --silent --show-error -X GET "https://api.numdetect.com/api/v1/bulk-jobs/$JOB_ID/result" \
-H "Authorization: Bearer $NUMDETECT_API_KEY" | jq -r .url)
curl --fail --silent --show-error --output result.csv "$url"result.csv keeps every original row in its original order and adds numdetect_status (valid, duplicate or invalid), numdetect_country and one column per product with yes, no, unknown, unsupported, failed or cancelled.
Cancel a running job
Countries that have not been checked yet stop, and their held amount returns to your balance.
curl --fail-with-body --silent --show-error -X POST "https://api.numdetect.com/api/v1/bulk-jobs/$JOB_ID/cancel" \
-H "Authorization: Bearer $NUMDETECT_API_KEY"Webhooks
Set a webhook URL on the account settings page to be told when a bulk job finishes. We send one HTTPS POST per finished job:
{
"type": "bulk_job.finished",
"data": {
"id": "10000000-0000-4000-8000-000000000003",
"status": "completed",
"total_rows": 1000,
"valid_rows": 982,
"charged": 245500,
"completed_at": "2026-11-11T08:30:00Z"
}
}Each request carries webhook-id, webhook-timestamp and webhook-signature headers. The signature is v1, followed by the Base64 HMAC-SHA256 of "<webhook-id>.<webhook-timestamp>.<body>", using your signing secret (whsec_…, Base64 after the prefix). Compare it before trusting the body.
Answer with any 2xx status. Failed deliveries are retried a few times, a few minutes apart. Treat webhook-id as unique, since a retried delivery can arrive twice.
Fees and balance
A single check is charged its price for each product with a definite answer. Unknown and failed answers are free.
A bulk job holds its cost when you submit it: for each country and product, max(valid numbers, the product's bulk minimum) × the bulk price. A completed list is charged in full, unknown rows included. A list that fails or is cancelled is returned to your balance. Duplicate and invalid rows, and countries a product does not support, are free.
Amounts are integer USD micros: 1 USD = 1,000,000 micros.
curl --fail-with-body --silent --show-error -X GET "https://api.numdetect.com/api/v1/balance-logs?limit=20" \
-H "Authorization: Bearer $NUMDETECT_API_KEY"POST /checks and POST /bulk-jobs need an Idempotency-Key header (up to 128 characters). Use a new key for each new request. If a response is lost, send the same request again with the same key: you get the first result instead of a second charge.
Price listErrors
Errors use {error: {code, message, request_id}}. Match the stable code rather than the message, and include request_id when you contact support.
| HTTP | Code | What to do |
|---|---|---|
| 400 | invalid_request | Check the fields, the JSON or form syntax and the input format. |
| 401 | unauthorized | Check that the key is present and has not been deleted. |
| 403 | forbidden | This action is only available in the signed-in dashboard, or the account is disabled. |
| 404 | not_found | Check the ID; only your own checks and jobs can be read. |
| 409 | conflict | The Idempotency-Key was already used for a different request. Use a new key for a new request. |
| 409 | insufficient_balance / max_cost_exceeded | Top up your balance, or raise max_cost if you accept the higher cost. |
| 409 | product_not_supported | Read the product catalog: the product is off sale or does not take this input. |
| 429 | rate_limited | Slow down and try again shortly. |
| 503 | unavailable / dependency_unavailable | Checks are temporarily unavailable. Try again later; nothing was charged. |
Get your API keyBulk checksWebhook settingsBilling
Back to authentication