nokycnumber

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

http
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 with 415.
  • 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 in display when 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

http
Authorization: Bearer sk_live_5f3a8b2c4d1e9f6a7b8c2d1e9f6a7b8c
Content-Type: application/json
NoKYC-Version: 2026-04-01

Use 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:

ScopeAllowed methodsUse case
readGETReporting, monitoring dashboards.
read_writeGET · POST · PATCHMost server-side integrations.
adminAll including DELETEProvisioning 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

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "message": "Required parameter `country` is missing.",
    "param": "country",
    "request_id": "req_8a4c2f1e9b3d4a7c"
  }
}

Status codes

CodeMeaning
200OK · request succeeded
201Created · resource created
204No Content · resource deleted, no body returned
400Bad Request · malformed JSON or missing required parameter
401Unauthenticated · invalid, missing, or revoked API key
402Payment Required · account balance insufficient for the action
403Forbidden · key lacks scope for this operation
404Not Found · resource doesn't exist or is owned by another account
409Conflict · idempotency key reused with different parameters
422Unprocessable · semantic error (e.g. unsupported country)
429Too Many Requests · see Rate limits
500503Server / 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
curl https://api.nokycnumber.com/v1/numbers?limit=10 \
  -H "Authorization: Bearer sk_live_…"
json
{
  "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.

http
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

http
X-Rate-Limit-Limit: 100
X-Rate-Limit-Remaining: 73
X-Rate-Limit-Reset: 1745672460
Retry-After: 8

When 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-Deprecation response 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.

GET /v1/numbers

List all numbers on your account. Returns a paginated list of number objects.

FieldTypeDescription
limit optionalinteger1–100, default 25.
starting_after optionalstringCursor for pagination.
country optionalstringFilter by ISO-2 country code (e.g. fr).
type optionalstringFilter by mobile or landline.
status optionalstringFilter by active, paused, or released.
curl
curl https://api.nokycnumber.com/v1/numbers?country=fr&type=mobile \
  -H "Authorization: Bearer sk_live_…"
json
{
  "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
}
POST /v1/numbers

Activate a new number from a reservation id. Charges the configured period to your account balance.

FieldTypeDescription
available_id requiredstringA navail_… id from /v1/numbers/available.
billing_period requiredstringmonthly, quarterly, or yearly.
addons optionalarrayAI add-ons to enable, e.g. ["ai-pickup","ai-trans"].
webhook_url optionalstringEndpoint that will receive number-scoped events.
curl
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"]
  }'
json
{
  "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"
}
GET /v1/numbers/{id}

Retrieve a single number by id.

curl
curl https://api.nokycnumber.com/v1/numbers/num_8a4c2f1e9b3d \
  -H "Authorization: Bearer sk_live_…"
PATCH /v1/numbers/{id}

Update mutable fields on a number. Use POST /v1/numbers/{id}/renew to extend the period.

FieldTypeDescription
auto_renew optionalbooleanToggle auto-renewal at period end.
webhook_url optionalstringReplace the number-scoped webhook target.
addons optionalarrayReplace the active set of AI add-ons.
on_off optionalobjectSet quiet hours / off windows. See AI configuration.
DELETE /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
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.

POST/v1/calls

Place an outbound call from one of your numbers.

FieldTypeDescription
from requiredstringA num_ id you own.
to requiredstringE.164 destination, e.g. +33612345678.
recording_enabled optionalbooleanDefault false. Recordings are stored 30 days.
callerid_mask optionalstring"hide", "rotate", or any E.164 number you own.
metadata optionalobjectArbitrary JSON returned in webhooks for the call.
curl
curl https://api.nokycnumber.com/v1/calls \
  -H "Authorization: Bearer sk_live_…" \
  -d '{
    "from": "num_8a4c2f1e9b3d",
    "to":   "+33612345678",
    "callerid_mask": "rotate"
  }'
json
{
  "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
}
GET/v1/calls

List calls. Filter by number, direction, status, or created_after / created_before.

GET/v1/calls/{id}

Retrieve a single call.

POST/v1/calls/{id}/hangup

Force-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.

POST/v1/sms

Send an SMS from one of your numbers.

FieldTypeDescription
from requiredstringA num_ id you own.
to requiredstringE.164 destination.
body requiredstringMessage body. Max 1,600 chars (10 segments).
send_at optionalstringISO-8601 future timestamp to schedule delivery.
media_urls optionalarrayUp to 5 image/video URLs. MMS only — works in supporting countries.
curl
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."
  }'
json
{
  "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
}
GET/v1/sms

List messages. Filter by number, direction, status.

GET/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.

GET/v1/voicemails

List voicemails. Pagination identical to other list endpoints.

GET/v1/voicemails/{id}

Retrieve a single voicemail with transcript and audio URL.

json
{
  "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.

DELETE/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.

GET/v1/numbers/{id}/ai

Retrieve the current AI configuration for a number.

json
{
  "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 }
}
PATCH/v1/numbers/{id}/ai

Update 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.

GET/v1/account

Retrieve your account.

json
{
  "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"
}
GET/v1/account/usage

Detailed 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

json
{
  "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.

shell
# 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): reject

Endpoints

POST/v1/webhook_endpoints

Register a new webhook endpoint. Returns the signing secret once; store it.

FieldTypeDescription
url requiredstringHTTPS URL we will POST events to.
enabled_events requiredarraySubscribe-list, e.g. ["sms.received","call.completed"]. Use ["*"] for all.
description optionalstringFree-form note for your own bookkeeping.
GET/v1/webhook_endpoints

List your endpoints.

DELETE/v1/webhook_endpoints/{id}

Remove an endpoint. Returns 204.

Event types

EventFired when
number.activatedA new number is provisioned and ready.
number.renewedPeriod renewal succeeded.
number.pausedNumber entered the on/off-window paused state.
number.releasedNumber permanently released back to inventory.
call.initiatedOutbound call placed.
call.answeredDistant party picked up.
call.completedCall ended (any reason).
call.recordedRecording finished and is available.
sms.receivedInbound SMS reached your number.
sms.sentOutbound SMS accepted by carrier.
sms.deliveredCarrier delivery receipt confirmed.
sms.failedCarrier rejected — check failure_code.
voicemail.createdNew voicemail recorded and transcribed.
ai.pickupAI auto-pickup answered a call.
account.balance_lowBalance fell below your configured threshold.

Object schemas

number

json
{
  "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

json
{
  "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

json
{
  "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