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
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
Buys a local US number and assigns an AI agent that answers for your business_phone.
Charges the first month ($5) on success.
| Body field | Required | Description |
|---|---|---|
business_phone | yes | E.164 US number the AI answers for (e.g. +13465551234). Keys the agent to your business line. |
area_code | no | Preferred area code for the new number (defaults to the business number's area). |
webhook_url | no | HTTPS 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" }
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
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
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
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
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
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
| Header | Value |
|---|---|
X-KwickPhone-Timestamp | Unix epoch seconds when we signed the request. |
X-KwickPhone-Signature | v1=<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);
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." } }
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed body / bad business_phone / non-HTTPS webhook URL. |
| 401 | unauthorized | Missing/invalid key, or key used from a non-allowed IP. |
| 402 | insufficient_balance | Balance below $5 — top up first. |
| 404 | not_found / no_recording | Unknown/foreign id, or no recording for that call. |
| 409 | no_numbers | No stock in that area code — try another. |
| 429 | number_limit / provision_velocity / rate_limited | A limit was hit (see below). |
| 503 | provider_unavailable | Transient 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.