cURL / SDK Examples
All examples use the public base URL:
https://api.zelnum.com/api/v1Replace YOUR_KEY_HERE with a key from Authentication, and take the product slug from Bulk products.
Submit an array
Section titled “Submit an array”curl -X POST "https://api.zelnum.com/api/v1/detect" \ -H "X-API-Key: YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "product": "YOUR_PRODUCT_SLUG", "country": "CN", "phones": ["+8613800138000", "+8613800138001"] }'The response hands you a task token and the amount pre-deducted:
{ "mode": "async", "task_id": "xKj9aP3qLm7v", "message": "Task queued. Poll GET /api/v1/tasks/xKj9aP3qLm7v for progress.", "summary": { "total": 5000, "cost_deducted": 0.500000 }}Upload a file
Section titled “Upload a file”curl -X POST "https://api.zelnum.com/api/v1/detect/file" \ -H "X-API-Key: YOUR_KEY_HERE" \ -F "product=YOUR_PRODUCT_SLUG" \ -F "country=CN" \ -F "file=@numbers.txt"numbers.txt holds one number per line, up to 50 MB. Note -F, not -d: this endpoint takes multipart/form-data.
Poll until it finishes
Section titled “Poll until it finishes”TOKEN=xKj9aP3qLm7v
while true; do status=$(curl -s "https://api.zelnum.com/api/v1/tasks/$TOKEN" \ -H "X-API-Key: YOUR_KEY_HERE")
printf '%s\n' "$status" | jq '{status, progress, valid, invalid, unknown}'
case "$(printf '%s' "$status" | jq -r .status)" in completed|failed) break ;; esac
sleep 4doneFour seconds between polls is a reasonable interval for a task of any size; the task page reports progress as a percentage.
Download the results
Section titled “Download the results”curl -s "https://api.zelnum.com/api/v1/tasks/$TOKEN/result" \ -H "X-API-Key: YOUR_KEY_HERE" | jq -r .download_urlThe download_url is time-limited — fetch it before expires_at, or the file is gone. Then:
curl -L -H "X-API-Key: YOUR_KEY_HERE" \ "https://api.zelnum.com/api/v1/tasks/$TOKEN/download" -o results.csvOne script, end to end
Section titled “One script, end to end”#!/usr/bin/env bashset -euo pipefail
API="https://api.zelnum.com/api/v1"KEY="YOUR_KEY_HERE"SLUG="YOUR_PRODUCT_SLUG"
task=$(curl -s -X POST "$API/detect" \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d "{\"product\":\"$SLUG\",\"country\":\"CN\",\"phones\":[\"+8613800138000\"]}" \ | jq -r .task_id)
echo "task $task submitted"
until [ "$(curl -s "$API/tasks/$task" -H "X-API-Key: $KEY" | jq -r .status)" = "completed" ]; do sleep 4done
url=$(curl -s "$API/tasks/$task/result" -H "X-API-Key: $KEY" | jq -r .download_url)curl -L "$url" -o results.csv
echo "wrote results.csv"Client examples
The whole cycle — submit, poll, download — in the languages batch jobs are usually written in. Each snippet submits an array and waits for the task to settle.
Python
import time
import requests
API = "https://api.zelnum.com/api/v1"
HEADERS = {"X-API-Key": "YOUR_KEY_HERE"}
task = requests.post(
API + "/detect",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"product": "YOUR_PRODUCT_SLUG",
"country": "CN",
"phones": ["+8613800138000", "+8613900139000"],
},
timeout=60,
).json()["task_id"]
while True:
status = requests.get(API + "/tasks/" + task, headers=HEADERS, timeout=30).json()
print(status["status"], status["progress"])
if status["status"] in ("completed", "failed"):
break
time.sleep(4)
if status["status"] == "completed":
result = requests.get(API + "/tasks/" + task + "/result", headers=HEADERS, timeout=30).json()
with requests.get(result["download_url"], headers=HEADERS, timeout=120, stream=True) as file:
with open("results.zip", "wb") as out:
for chunk in file.iter_content(chunk_size=65536):
out.write(chunk) Node.js
const API = 'https://api.zelnum.com/api/v1';
const headers = { 'X-API-Key': 'YOUR_KEY_HERE' };
const queued = await fetch(API + '/detect', {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify({
product: 'YOUR_PRODUCT_SLUG',
country: 'CN',
phones: ['+8613800138000', '+8613900139000'],
}),
}).then((r) => r.json());
let task;
do {
await new Promise((resolve) => setTimeout(resolve, 4000));
task = await fetch(API + '/tasks/' + queued.task_id, { headers }).then((r) => r.json());
console.log(task.status, task.progress);
} while (task.status === 'pending' || task.status === 'processing');
if (task.status === 'completed') {
const result = await fetch(API + '/tasks/' + queued.task_id + '/result', { headers })
.then((r) => r.json());
console.log('download:', result.download_url);
} PHP
$api = 'https://api.zelnum.com/api/v1';
$key = 'YOUR_KEY_HERE';
$request = function (string $method, string $url, ?array $body = null) use ($key) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => array_values(array_filter([
'X-API-Key: ' . $key,
$body ? 'Content-Type: application/json' : null,
])),
CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
return $result;
};
$queued = $request('POST', $api . '/detect', [
'product' => 'YOUR_PRODUCT_SLUG',
'country' => 'CN',
'phones' => ['+8613800138000', '+8613900139000'],
]);
do {
sleep(4);
$status = $request('GET', $api . '/tasks/' . $queued['task_id']);
echo $status['status'], ' ', $status['progress'], PHP_EOL;
} while (in_array($status['status'], ['pending', 'processing'], true));
if ($status['status'] === 'completed') {
$result = $request('GET', $api . '/tasks/' . $queued['task_id'] . '/result');
echo 'download: ', $result['download_url'], PHP_EOL;
} Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
const api = "https://api.zelnum.com/api/v1"
func call(method, url string, body any) map[string]any {
var reader *bytes.Reader
if body == nil {
reader = bytes.NewReader(nil)
} else {
encoded, _ := json.Marshal(body)
reader = bytes.NewReader(encoded)
}
req, _ := http.NewRequest(method, url, reader)
req.Header.Set("X-API-Key", "YOUR_KEY_HERE")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var out map[string]any
json.NewDecoder(res.Body).Decode(&out)
return out
}
func main() {
queued := call(http.MethodPost, api+"/detect", map[string]any{
"product": "YOUR_PRODUCT_SLUG",
"country": "CN",
"phones": []string{"+8613800138000", "+8613900139000"},
})
task := queued["task_id"].(string)
for {
status := call(http.MethodGet, api+"/tasks/"+task, nil)
fmt.Println(status["status"], status["progress"])
if status["status"] == "completed" || status["status"] == "failed" {
break
}
time.Sleep(4 * time.Second)
}
} Drop country from every body above when product is an email product. The bulk product list marks which is which.
- Task status — every field the poll returns
- Download results — retention and expiry rules
- Response format — shared conventions