Developer API

API Reference

A live US phone number with an AI agent answering it, by API. Provision numbers, read calls, pull transcripts and recordings, and receive signed webhooks. Base URL https://kwickphone.com/v1.

Authentication

Every request authenticates with an API key as a Bearer token. Create keys in the developer console — the secret (kp_live_…) is shown once at creation; store it securely. Requests are HTTPS only and authenticate by Bearer token alone (no cookies).

curl https://kwickphone.com/v1/numbers \
  -H "Authorization: Bearer kp_live_xxxxxxxxxxxxxxxx"
  • Up to 5 active keys per account; revoke and rotate anytime in the console.
  • IP allowlist (optional): lock a key to specific IPs/CIDRs when you create it. A key used from a non-allowed IP is rejected exactly like an invalid key.
  • A missing or invalid key returns 401 unauthorized.

Balance & funding

The API runs on a prepaid balance. Numbers cost $5/number/month and the first month is charged when you provision. You must have at least $5 of balance to provision — otherwise POST /v1/numbers returns 402 insufficient_balance.

Fund your balance in the console under Billing → API balance: add funds manually, or turn on auto-top-up (when your balance drops below a threshold we charge your card on file, $5–$500 per top-up). Talk time is billed per the plan on the pricing page.

Quickstart

Prefer working examples? Three open-source (MIT) starters, basic → advanced: ai-front-desk-demo (provision + signed webhooks + transcripts/recordings) → chess-club-receptionist (extract intake → roster/leads → nightly digest) → ai-booking-line (+ Stripe deposit pay-link, reconciled by webhook). Each has a one-click Deploy to Render.

Fund your balance, create a key, then:

# 1 — provision a number that answers for your business line
curl -X POST https://kwickphone.com/v1/numbers \
  -H "Authorization: Bearer $KP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"business_phone":"+13465551234","area_code":"346","webhook_url":"https://yourapp.com/hooks/kp"}'

# 2 — your webhook URL now receives a signed call.completed event after each call
# 3 — or pull calls on demand:
curl https://kwickphone.com/v1/calls -H "Authorization: Bearer $KP_API_KEY"

Provision a number

POST /v1/numbers

Buys a local US number and assigns an AI agent that answers for your business_phone. Charges the first month ($5) on success.

Body fieldRequiredDescription
business_phoneyesE.164 US number the AI answers for (e.g. +13465551234). Keys the agent to your business line.
area_codenoPreferred area code for the new number (defaults to the business number's area).
webhook_urlnoHTTPS URL to receive signed call events. Must be public HTTPS (internal/private addresses are rejected).
curl -X POST https://kwickphone.com/v1/numbers \
  -H "Authorization: Bearer $KP_API_KEY" -H "Content-Type: application/json" \
  -d '{"business_phone":"+13465551234","area_code":"346","webhook_url":"https://yourapp.com/hooks/kp"}'

201 Created
{
  "id": "num_12",
  "phone_number": "+13465700511",
  "business_phone": "+13465551234",
  "status": "active",
  "webhook_url": "https://yourapp.com/hooks/kp",
  "webhook_secret": "whsec_8f3c…",   // shown once — store it to verify webhooks
  "created_at": "2026-06-20T08:00:00+00:00"
}
Save webhook_secret now. It's returned only in this response and is the key you use to verify webhook signatures (see Webhooks).

Failure modes: 402 insufficient_balance, 409 no_numbers (no stock in that area code — try another), 429 number_limit / provision_velocity (see limits), 503 provider_unavailable (transient — retry). A failed provision is never charged.

List numbers

GET /v1/numbers

Returns your account's numbers, active first. webhook_secret is never returned here — only at creation.

curl https://kwickphone.com/v1/numbers -H "Authorization: Bearer $KP_API_KEY"

200 OK
{ "data": [
  { "id": "num_12", "phone_number": "+13465700511", "business_phone": "+13465551234",
    "status": "active", "webhook_url": "https://yourapp.com/hooks/kp", "created_at": "2026-06-20T08:00:00+00:00" }
] }

Status is one of active, past_due (a monthly charge failed — paused until paid), or released.

Release a number

DELETE /v1/numbers/{id}

Releases the number and frees it. Stops future monthly billing. No refund of the current month.

curl -X DELETE https://kwickphone.com/v1/numbers/num_12 \
  -H "Authorization: Bearer $KP_API_KEY"

200 OK
{ "released": true }

An unknown or not-yours id returns 404 not_found.

List calls

GET /v1/calls

Calls across all your numbers, newest first.

curl https://kwickphone.com/v1/calls -H "Authorization: Bearer $KP_API_KEY"

200 OK
{ "data": [
  { "id": "CA9f…", "number_id": "num_12", "phone_number": "+13465700511",
    "caller": "+12145559876", "direction": "inbound", "status": "completed",
    "outcome": "order", "duration_sec": 132, "started_at": "2026-06-20T07:55:10Z",
    "has_recording": true, "has_transcript": true }
] }

Get a transcript

GET /v1/calls/{call_id}/transcript

Returns the turn-by-turn transcript for a call (the id from the calls list). Returns 404 not_found if the call isn't one of your numbers' or has no transcript.

curl https://kwickphone.com/v1/calls/CA9f.../transcript \
  -H "Authorization: Bearer $KP_API_KEY"

Get a recording

GET /v1/calls/{call_id}/recording

Streams the call audio as audio/mpeg. 404 no_recording if none.

curl https://kwickphone.com/v1/calls/CA9f.../recording \
  -H "Authorization: Bearer $KP_API_KEY" -o call.mp3

Webhooks — events & signing

If you set webhook_url on a number, we POST a JSON event to it after each completed call. Delivery is at-least-once with retries (exponential backoff, up to 5 attempts); dedupe on the call id. Your endpoint should return 2xx quickly.

Event payload

POST https://yourapp.com/hooks/kp
{
  "type": "call.completed",
  "number_id": "num_12",
  "occurred_at": "2026-06-20T07:57:25+00:00",
  "call": { "call_sid": "CA9f…", "caller": "+12145559876", "outcome": "order",
            "duration_sec": 132, "started_at": "2026-06-20T07:55:10Z",
            "has_recording": true, "has_transcript": true }
}

Headers

HeaderValue
X-KwickPhone-TimestampUnix epoch seconds when we signed the request.
X-KwickPhone-Signaturev1=<hex> — HMAC-SHA256 over "{timestamp}.{raw_body}" keyed by the number's webhook_secret.

Verify the signature

Compute the HMAC over timestamp + "." + rawBody with your stored webhook_secret and compare in constant time.

// PHP
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_KWICKPHONE_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_KWICKPHONE_SIGNATURE'] ?? '';
$expected = 'v1=' . hash_hmac('sha256', $ts . '.' . $raw, $WEBHOOK_SECRET);
if (!hash_equals($expected, $sig)) { http_response_code(401); exit; }
// Node.js (express, raw body)
const crypto = require('crypto');
const ts  = req.headers['x-kwickphone-timestamp'];
const sig = req.headers['x-kwickphone-signature'];
const expected = 'v1=' + crypto.createHmac('sha256', WEBHOOK_SECRET)
  .update(ts + '.' + rawBody).digest('hex');
if (sig !== expected) return res.sendStatus(401);
Your endpoint must be public HTTPS. We refuse to deliver to private, loopback, or link-local addresses and never follow redirects — a webhook URL pointing at an internal host is rejected at send time. Also reject events whose X-KwickPhone-Timestamp is too old to prevent replay.

Errors

Errors use a consistent envelope and a matching HTTP status:

{ "error": { "code": "insufficient_balance", "message": "Add funds to your API balance before provisioning a number." } }
StatusCodeMeaning
400invalid_requestMalformed body / bad business_phone / non-HTTPS webhook URL.
401unauthorizedMissing/invalid key, or key used from a non-allowed IP.
402insufficient_balanceBalance below $5 — top up first.
404not_found / no_recordingUnknown/foreign id, or no recording for that call.
409no_numbersNo stock in that area code — try another.
429number_limit / provision_velocity / rate_limitedA limit was hit (see below).
503provider_unavailableTransient provisioning issue — retry shortly.

Limits & billing

  • Request rate: 60 requests/minute per key → 429 rate_limited.
  • Active numbers: up to 5 active numbers per account → 429 number_limit. Need more? Contact us to raise it.
  • Provisioning velocity: up to 3 provisions/hour per account → 429 provision_velocity.
  • API keys: up to 5 active keys per account.
  • Numbers: $5/number/month, charged on provision and monthly thereafter. If a monthly charge fails, the number goes past_due (paused) with a 7-day grace before release — keep your balance funded or auto-top-up on.
  • Talk time: per the pricing tiers; payments on calls are PCI SAQ-A pass-through.
Building a sandbox integration? Create a key, fund a small balance, and provision a test number — call logs, transcripts and recordings are all available. Questions or higher limits: see the overview.