HTTP API v1
Programmatic access to phone numbers, calls, SMS, voicemails and AI features. JSON over HTTPS. Idempotent where it counts. No SDK required, but we ship 5 of them.
Introduction
The NoKYCNumber API is organised around REST. We use predictable resource-oriented URLs, accept and return JSON-encoded bodies, and use standard HTTP response codes, authentication, and verbs.
Base URL
https://api.nokycnumber.com/v1/All requests must be made over HTTPS. Calls made over plain HTTP will fail with 301 Moved Permanently. Requests without authentication will fail with 401 Unauthenticated.
JSON convention
- All requests with a body use
Content-Type: application/json. Form-encoded bodies are rejected with415. - Timestamps are returned as ISO-8601 UTC strings (
2026-04-26T14:23:00Z) and accepted in the same format. - Phone numbers are returned in E.164 with a leading
+(+33647189022) and pretty-printed indisplaywhen relevant. - Object IDs are prefixed by their resource:
num_,call_,sms_,vm_,whk_,evt_.
Authentication
Authenticate with a bearer key issued from your panel under Settings → API keys. Two key types exist:
sk_live_…— server-side secret. Full read/write. Never expose in client code.pk_live_…— publishable key. Currently reserved; not yet used by any endpoint.
Header format
Authorization: Bearer sk_live_5f3a8b2c4d1e9f6a7b8c2d1e9f6a7b8c
Content-Type: application/json
NoKYC-Version: 2026-04-01Use the NoKYC-Version header to pin to a specific version of the API surface (see Versioning). When omitted, your account's pinned default is used.
Scopes
Keys are issued with one of three scopes:
| Scope | Allowed methods | Use case |
|---|---|---|
read | GET | Reporting, monitoring dashboards. |
read_write | GET · POST · PATCH | Most server-side integrations. |
admin | All including DELETE | Provisioning automation, account management. |
Keys can be rotated at any time. Old keys remain valid for 5 minutes after rotation to allow zero-downtime deployment.
Errors
The API uses conventional HTTP status codes. 2xx means success, 4xx means a problem with your request, 5xx means we made a mistake and would like to hear about it.
Error envelope
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "Required parameter `country` is missing.",
"param": "country",
"request_id": "req_8a4c2f1e9b3d4a7c"
}
}Status codes
| Code | Meaning |
|---|---|
200 | OK · request succeeded |
201 | Created · resource created |
204 | No Content · resource deleted, no body returned |
400 | Bad Request · malformed JSON or missing required parameter |
401 | Unauthenticated · invalid, missing, or revoked API key |
402 | Payment Required · account balance insufficient for the action |
403 | Forbidden · key lacks scope for this operation |
404 | Not Found · resource doesn't exist or is owned by another account |
409 | Conflict · idempotency key reused with different parameters |
422 | Unprocessable · semantic error (e.g. unsupported country) |
429 | Too Many Requests · see Rate limits |
500 – 503 | Server / Service issue · safe to retry with backoff |
Error types
invalid_request_error— malformed input, missing fields, invalid format.authentication_error— key issue.authorization_error— scope issue.rate_limit_error— slow down.api_error— our fault. Retryable.resource_error— resource state prevents the operation (e.g. trying to make a call from a paused number).upstream_error— carrier or partner timed out.
Every error response includes a request_id. Always log it — it's the only way our support can correlate your call with our internal trace.
Pagination
List endpoints use forward-only cursor pagination. They return up to limit objects (default 25, max 100) and a has_more boolean. To fetch the next page, pass the last object's id as starting_after.
curl https://api.nokycnumber.com/v1/numbers?limit=10 \
-H "Authorization: Bearer sk_live_…"{
"object": "list",
"url": "/v1/numbers",
"data": [
{ "id": "num_8a4c2f1e9b3d", "object": "number", "number": "+33647189022", … },
{ "id": "num_2k7m9p3w8x4t", "object": "number", "number": "+12025550143", … }
],
"has_more": true,
"next_cursor": "num_2k7m9p3w8x4t"
}Lists are ordered with most recently created objects first. Backwards pagination via ending_before is not supported — page through the cursor instead.
Idempotency
POST requests that mutate state accept an Idempotency-Key header. We deduplicate on the key for 24 hours, returning the cached response for retries. Use a UUID per logical operation.
POST /v1/sms HTTP/1.1
Authorization: Bearer sk_live_…
Idempotency-Key: 9b3d4a7c-8a4c-2f1e-9b3d-4a7c8a4c2f1e
Content-Type: application/json
{ "from": "num_8a4c2f1e9b3d", "to": "+33612345678", "body": "Hello" }Replaying the same key with different body content returns 409 Conflict with idempotency_key_mismatch.
Idempotency keys are not required, but strongly recommended for any operation that costs money (calls, SMS, number purchases). Without one, a network retry can double-charge you.
Rate limits
By default each API key is limited to 100 requests per minute. Volume customers can request a lift from support. Limits are enforced per key, not per IP.
Response headers
X-Rate-Limit-Limit: 100
X-Rate-Limit-Remaining: 73
X-Rate-Limit-Reset: 1745672460
Retry-After: 8When you exceed the limit you receive 429 Too Many Requests with a Retry-After header in seconds. Linear backoff up to 4 retries is recommended; if it persists, request a higher limit.
Burst allowance
The limiter is a token bucket — short bursts above 100 rpm are tolerated as long as the rolling 60-second average stays below the cap.
Versioning
API versions are dated (2026-04-01). Breaking changes ship as a new dated version; non-breaking additions land in the current one. Pin a version with the NoKYC-Version header.
Compatibility
- We support each version for at least 24 months after the next release.
- Deprecation notices are emailed to account owners 90 days in advance.
- A
NoKYC-Deprecationresponse header warns when you're on an outdated version.
Current version: 2026-04-01.
Numbers
A number represents a virtual phone line you control. Numbers are tied to a country and a type (mobile or landline) and carry a billing period.
/v1/numbers
List all numbers on your account. Returns a paginated list of number objects.
| Field | Type | Description |
|---|---|---|
limit optional | integer | 1–100, default 25. |
starting_after optional | string | Cursor for pagination. |
country optional | string | Filter by ISO-2 country code (e.g. fr). |
type optional | string | Filter by mobile or landline. |
status optional | string | Filter by active, paused, or released. |
curl https://api.nokycnumber.com/v1/numbers?country=fr&type=mobile \
-H "Authorization: Bearer sk_live_…"{
"object": "list",
"data": [
{
"id": "num_8a4c2f1e9b3d",
"object": "number",
"number": "+33647189022",
"display": "+33 6 47 18 90 22",
"country": "fr",
"type": "mobile",
"tier": "standard",
"status": "active",
"billing_period": "yearly",
"renews_at": "2027-04-26T11:23:00Z",
"ai_enabled": true,
"created_at": "2026-04-26T11:23:00Z"
}
],
"has_more": false,
"next_cursor": null
}/v1/numbers/available
Search the inventory for available numbers in a given country and type. Use the returned id when calling POST /v1/numbers.
| Field | Type | Description |
|---|---|---|
country required | string | ISO-2 country code. |
type required | string | mobile or landline. |
tier optional | string | standard (default) or premium. |
pattern optional | string | Optional substring filter on local digits, e.g. "77". |
limit optional | integer | 1–50, default 10. |
curl "https://api.nokycnumber.com/v1/numbers/available?country=fr&type=mobile&tier=premium&limit=5" \
-H "Authorization: Bearer sk_live_…"{
"object": "list",
"data": [
{ "id": "navail_2k7m9p3w8x4t", "number": "+33611112222", "display": "+33 6 11 11 22 22", "tier": "premium", "pattern": "double-repeat", "price_usd_per_month": 11.98 },
{ "id": "navail_4n2v8j1q6h7y", "number": "+33612345678", "display": "+33 6 12 34 56 78", "tier": "premium", "pattern": "sequential", "price_usd_per_month": 11.98 }
],
"expires_at": "2026-04-26T11:33:00Z"
}Available numbers are reserved for 10 minutes. After expiry the same id may be assigned to another caller.
/v1/numbers
Activate a new number from a reservation id. Charges the configured period to your account balance.
| Field | Type | Description |
|---|---|---|
available_id required | string | A navail_… id from /v1/numbers/available. |
billing_period required | string | monthly, quarterly, or yearly. |
addons optional | array | AI add-ons to enable, e.g. ["ai-pickup","ai-trans"]. |
webhook_url optional | string | Endpoint that will receive number-scoped events. |
curl https://api.nokycnumber.com/v1/numbers \
-H "Authorization: Bearer sk_live_…" \
-H "Idempotency-Key: 9b3d4a7c-8a4c-2f1e-9b3d-4a7c8a4c2f1e" \
-d '{
"available_id": "navail_2k7m9p3w8x4t",
"billing_period": "yearly",
"addons": ["ai-pickup"]
}'{
"id": "num_8a4c2f1e9b3d",
"object": "number",
"number": "+33611112222",
"display": "+33 6 11 11 22 22",
"country": "fr",
"type": "mobile",
"tier": "premium",
"status": "active",
"billing_period": "yearly",
"renews_at": "2027-04-26T11:23:00Z",
"ai_enabled": true,
"created_at": "2026-04-26T11:23:00Z"
}/v1/numbers/{id}
Retrieve a single number by id.
curl https://api.nokycnumber.com/v1/numbers/num_8a4c2f1e9b3d \
-H "Authorization: Bearer sk_live_…"/v1/numbers/{id}
Update mutable fields on a number. Use POST /v1/numbers/{id}/renew to extend the period.
| Field | Type | Description |
|---|---|---|
auto_renew optional | boolean | Toggle auto-renewal at period end. |
webhook_url optional | string | Replace the number-scoped webhook target. |
addons optional | array | Replace the active set of AI add-ons. |
on_off optional | object | Set quiet hours / off windows. See AI configuration. |
/v1/numbers/{id}
Release a number. The number returns to inventory at period end and may be reassigned to another customer afterwards. Returns 204 No Content.
curl -X DELETE https://api.nokycnumber.com/v1/numbers/num_8a4c2f1e9b3d \
-H "Authorization: Bearer sk_live_…"Released numbers cannot be recovered. We recommend pausing first via PATCH with { "status": "paused" } if you might want it back.
Calls
A call is a single voice session originated from one of your numbers. Outbound calls are placed via POST /v1/calls; inbound calls are mirrored to your webhook and are also retrievable via the API.
/v1/callsPlace an outbound call from one of your numbers.
| Field | Type | Description |
|---|---|---|
from required | string | A num_ id you own. |
to required | string | E.164 destination, e.g. +33612345678. |
recording_enabled optional | boolean | Default false. Recordings are stored 30 days. |
callerid_mask optional | string | "hide", "rotate", or any E.164 number you own. |
metadata optional | object | Arbitrary JSON returned in webhooks for the call. |
curl https://api.nokycnumber.com/v1/calls \
-H "Authorization: Bearer sk_live_…" \
-d '{
"from": "num_8a4c2f1e9b3d",
"to": "+33612345678",
"callerid_mask": "rotate"
}'{
"id": "call_2k7m9p3w8x4t",
"object": "call",
"from": "num_8a4c2f1e9b3d",
"from_e164": "+33647189022",
"to": "+33612345678",
"status": "ringing",
"direction": "outbound",
"started_at": "2026-04-26T11:24:01Z",
"answered_at": null,
"ended_at": null,
"duration_sec": 0,
"recording_url": null,
"cost_usd": null
}/v1/callsList calls. Filter by number, direction, status, or created_after / created_before.
/v1/calls/{id}Retrieve a single call.
/v1/calls/{id}/hangupForce-end an in-flight call. Idempotent.
SMS messages
Send and retrieve SMS. Long messages are auto-segmented per GSM-03.38 / UCS-2 rules; the response shows the actual segment count for billing.
/v1/smsSend an SMS from one of your numbers.
| Field | Type | Description |
|---|---|---|
from required | string | A num_ id you own. |
to required | string | E.164 destination. |
body required | string | Message body. Max 1,600 chars (10 segments). |
send_at optional | string | ISO-8601 future timestamp to schedule delivery. |
media_urls optional | array | Up to 5 image/video URLs. MMS only — works in supporting countries. |
curl https://api.nokycnumber.com/v1/sms \
-H "Authorization: Bearer sk_live_…" \
-H "Idempotency-Key: 9b3d4a7c-8a4c-2f1e-9b3d-4a7c8a4c2f1e" \
-d '{
"from": "num_8a4c2f1e9b3d",
"to": "+33612345678",
"body": "Bonjour. Your code is 482917."
}'{
"id": "sms_4n2v8j1q6h7y",
"object": "sms",
"from_e164": "+33647189022",
"to": "+33612345678",
"body": "Bonjour. Your code is 482917.",
"direction": "outbound",
"segments": 1,
"status": "queued",
"created_at": "2026-04-26T11:24:50Z",
"delivered_at": null,
"cost_usd": 0.012
}/v1/smsList messages. Filter by number, direction, status.
/v1/sms/{id}Retrieve a single message including segment-level delivery status.
Voicemails
Voicemails are stored, transcribed, and translated automatically. Audio URLs are pre-signed and expire 1 hour after issuance.
/v1/voicemailsList voicemails. Pagination identical to other list endpoints.
/v1/voicemails/{id}Retrieve a single voicemail with transcript and audio URL.
{
"id": "vm_5p3w8x4t9k2m",
"object": "voicemail",
"number_id": "num_8a4c2f1e9b3d",
"from_e164": "+33612345678",
"received_at": "2026-04-26T11:25:42Z",
"duration_sec": 27,
"audio_url": "https://files.nokycnumber.com/vm/5p3w8x4t9k2m.mp3?expires=1745676342&sig=…",
"transcript": {
"text": "Bonjour, c'est Marc. Rappelez-moi quand vous pouvez.",
"language": "fr",
"translation_en": "Hello, this is Marc. Call me back when you can.",
"summary": "Marc asks for a callback.",
"sentiment": "neutral"
}
}Both translation_en and summary require the ai-summary add-on.
/v1/voicemails/{id}Permanently delete a voicemail and its audio file. Returns 204 No Content.
AI configuration
Each number can run an AI agent that picks up calls when you don't, screens unknown callers, summarises voicemails, or translates conversations live. Configure per-number via the AI sub-resource.
/v1/numbers/{id}/aiRetrieve the current AI configuration for a number.
{
"object": "ai_config",
"number_id": "num_8a4c2f1e9b3d",
"addons": ["ai-pickup", "ai-summary"],
"auto_pickup": {
"enabled": true,
"after_rings": 4,
"script": "I'm unavailable right now. Leave your name, the reason for the call, and a callback number.",
"language": "auto",
"voice": "neutral_fr"
},
"screening": { "enabled": false },
"translator": { "enabled": false, "target_language": "en" },
"voicemail_summary": { "enabled": true, "include_sentiment": true }
}/v1/numbers/{id}/aiUpdate one or more AI features. Each block is optional; only fields present are updated.
Voices
Available voices: neutral_en, neutral_fr, neutral_es, neutral_de, neutral_jp, warm_en, warm_fr. Custom-cloned voices for enterprise on request.
Account
Read account-level data: balance, usage stats, configured webhooks.
/v1/accountRetrieve your account.
{
"object": "account",
"email_hash": "9b3d4a7c8a4c2f1e",
"balance_usd": 47.32,
"default_country": "fr",
"active_numbers": 3,
"monthly_call_minutes": 412,
"monthly_sms_segments": 1820,
"created_at": "2026-04-12T09:15:00Z"
}/v1/account/usageDetailed usage breakdown for the current period. Pass period=last_30d or period=last_90d.
Webhooks
Subscribe to events from your account or specific numbers. We POST a JSON event to your endpoint and retry up to 5 times with exponential backoff over 24 hours.
Event envelope
{
"id": "evt_3d4a7c8a4c2f",
"object": "event",
"type": "sms.received",
"created": "2026-04-26T11:25:42Z",
"livemode": true,
"data": { "object": { "id": "sms_…", … } },
"request_id": "req_8a4c2f1e9b3d4a7c"
}Signature verification
Each request carries an X-NoKYC-Signature header in the form t=<ts>,v1=<hex>. The signature is HMAC-SHA256 of <ts>.<raw_body> using the endpoint's signing secret. Reject any request older than 5 minutes.
# Pseudocode
ts, v1 = parse(header)
if abs(now - ts) > 300: reject
expected = hmac_sha256(secret, f"{ts}.{body}")
if not constant_time_eq(expected, v1): rejectEndpoints
/v1/webhook_endpointsRegister a new webhook endpoint. Returns the signing secret once; store it.
| Field | Type | Description |
|---|---|---|
url required | string | HTTPS URL we will POST events to. |
enabled_events required | array | Subscribe-list, e.g. ["sms.received","call.completed"]. Use ["*"] for all. |
description optional | string | Free-form note for your own bookkeeping. |
/v1/webhook_endpointsList your endpoints.
/v1/webhook_endpoints/{id}Remove an endpoint. Returns 204.
Event types
| Event | Fired when |
|---|---|
number.activated | A new number is provisioned and ready. |
number.renewed | Period renewal succeeded. |
number.paused | Number entered the on/off-window paused state. |
number.released | Number permanently released back to inventory. |
call.initiated | Outbound call placed. |
call.answered | Distant party picked up. |
call.completed | Call ended (any reason). |
call.recorded | Recording finished and is available. |
sms.received | Inbound SMS reached your number. |
sms.sent | Outbound SMS accepted by carrier. |
sms.delivered | Carrier delivery receipt confirmed. |
sms.failed | Carrier rejected — check failure_code. |
voicemail.created | New voicemail recorded and transcribed. |
ai.pickup | AI auto-pickup answered a call. |
account.balance_low | Balance fell below your configured threshold. |
Object schemas
number
{
"id": "string · num_…",
"object": "string · constant \"number\"",
"number": "string · E.164",
"display": "string · pretty-printed",
"country": "string · ISO-2",
"type": "enum · mobile | landline",
"tier": "enum · standard | premium",
"tier_pattern": "string | null · double-repeat | sequential | mirror | round | …",
"status": "enum · active | paused | released",
"billing_period": "enum · monthly | quarterly | yearly",
"renews_at": "string · ISO-8601 UTC",
"auto_renew": "boolean",
"addons": "array · ai-pickup | ai-screen | ai-summary | ai-trans",
"ai_enabled": "boolean",
"metadata": "object | null",
"created_at": "string · ISO-8601 UTC"
}call
{
"id": "string · call_…",
"object": "string · constant \"call\"",
"from": "string · num_…",
"from_e164": "string · E.164",
"to": "string · E.164",
"direction": "enum · inbound | outbound",
"status": "enum · queued | ringing | in_progress | completed | failed | no_answer | busy",
"started_at": "string · ISO-8601 UTC",
"answered_at": "string | null",
"ended_at": "string | null",
"duration_sec": "integer",
"recording_url": "string | null · pre-signed, expires in 1h",
"cost_usd": "number | null"
}sms
{
"id": "string · sms_…",
"object": "string · constant \"sms\"",
"from": "string · num_…",
"from_e164": "string · E.164",
"to": "string · E.164",
"body": "string",
"media_urls": "array · up to 5",
"direction": "enum · inbound | outbound",
"segments": "integer · 1–10",
"status": "enum · queued | sent | delivered | failed | undelivered",
"failure_code": "string | null",
"created_at": "string · ISO-8601 UTC",
"delivered_at": "string | null",
"cost_usd": "number | null"
}Official SDKs
The HTTP API is the source of truth, but these SDKs give you typed bindings, retries, idempotency, and webhook verification out of the box.
-
node
npm install @nokycnumber/sdk -
python
pip install nokycnumber -
go
go get github.com/nokycnumber/sdk-go -
php
composer require nokycnumber/sdk -
ruby
gem install nokycnumber