Skip to content

cURL / SDK Examples

All examples use the public base URL:

https://api.zelnum.com/api/v1

Replace YOUR_KEY_HERE with a key from Authentication, and take the product slug from Bulk products.

Terminal window
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 }
}
Terminal window
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.

Terminal window
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 4
done

Four seconds between polls is a reasonable interval for a task of any size; the task page reports progress as a percentage.

Terminal window
curl -s "https://api.zelnum.com/api/v1/tasks/$TOKEN/result" \
-H "X-API-Key: YOUR_KEY_HERE" | jq -r .download_url

The download_url is time-limited — fetch it before expires_at, or the file is gone. Then:

Terminal window
curl -L -H "X-API-Key: YOUR_KEY_HERE" \
"https://api.zelnum.com/api/v1/tasks/$TOKEN/download" -o results.csv
#!/usr/bin/env bash
set -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 4
done
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.