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.Lost & Found — report a found item
Log an item your staff found (e.g. housekeeping, from your PMS app). Requires a
location_id your account owns with Lost & Found enabled. Logging an item
instantly reverse-matches waiting claims — matching claimants are texted automatically.
| Body field | Required | Description |
|---|---|---|
location_id | yes | Your location with Lost & Found enabled (404 location_not_found otherwise). |
description | yes | What the item is — drives matching. |
category / color / found_area / storage_ref / found_by | no | Category (e.g. wallet, umbrella), color, where it was found, bin/shelf, finder's name. |
found_date | no | YYYY-MM-DD; defaults to today (UTC). |
image_url | no | Photo URL, stored as-is. |
curl -X POST https://kwickphone.com/v1/lostfound/found-items \ -H "Authorization: Bearer $KP_API_KEY" -H "Content-Type: application/json" \ -d '{"location_id":12,"category":"umbrella","description":"compact travel umbrella","color":"black","found_area":"room 214","storage_ref":"bin 3","found_by":"Maria (housekeeping)"}' 201 Created { "id": 31, "category": "umbrella", "description": "compact travel umbrella", "color": "black", "found_by": "Maria (housekeeping)", "found_area": "room 214", "found_date": "2026-07-01", "storage_ref": "bin 3", "image_url": "", "status": "stored", "source": "api", "created_at": "2026-07-01T18:20:03+00:00" }
Lost & Found — file a claim
File a lost-item claim on a guest's behalf. claimant_name and
claimant_phone are required — the claimant is texted the moment a matching item is
logged. Optional: claimant_email, category,
description, color, lost_area,
lost_date, proof_note, image_url.
curl -X POST https://kwickphone.com/v1/lostfound/claims \ -H "Authorization: Bearer $KP_API_KEY" -H "Content-Type: application/json" \ -d '{"location_id":12,"claimant_name":"Ana","claimant_phone":"+18325550101","category":"wallet","description":"brown leather wallet","lost_area":"hotel bar","proof_note":"library card + about $40 inside"}' 201 Created — high-value category: has_match only, never the matched item's details { "id": 9, "claimant_name": "Ana", "claimant_phone": "+18325550101", "category": "wallet", "description": "brown leather wallet", "status": "open", "source": "api", "created_at": "2026-07-01T18:22:41+00:00", "has_match": true, "verify_required": true }
verify_required:true
and never the matched item's details — ownership is proven at pickup, not by repeating a description. Low-value
categories include candidates (description/color/area/date). Urgent categories (a lost child,
person, medication) are refused with 422 urgent_category — connect the reporter to venue
staff or 911; they are never queued via the API.Lost & Found — list items & claims
Paginated (25/page). Query params: location_id (required),
status, category, area,
from, to, q (free-text),
page.
curl "https://kwickphone.com/v1/lostfound/found-items?location_id=12&status=stored&q=umbrella" \ -H "Authorization: Bearer $KP_API_KEY" 200 OK { "data": [ { "id": 31, "category": "umbrella", "status": "stored", … } ], "total": 1, "page": 1, "pages": 1, "per": 25 }
Lost & Found — item / claim detail
Pass ?location_id=. Adds photos (all photo URLs, primary
first) and timeline — the chain-of-custody audit trail.
curl "https://kwickphone.com/v1/lostfound/found-items/31?location_id=12" \ -H "Authorization: Bearer $KP_API_KEY" 200 OK { "id": 31, "category": "umbrella", …, "photos": ["https://…/1.jpg"], "timeline": [ { "created_at": "2026-07-01T18:20:03+00:00", "action": "logged", "staff_name": "API", "note": "" } ] }
Lost & Found — reunite (release an item)
Marks the claim reunited with found_item_id and the item returned. For high-value
categories the release is refused unless "verified": true — your staff confirmed
proof of ownership against the item. Every release is written to the chain of custody.
curl -X POST https://kwickphone.com/v1/lostfound/claims/9/reunite \ -H "Authorization: Bearer $KP_API_KEY" -H "Content-Type: application/json" \ -d '{"location_id":12,"found_item_id":31,"verified":true}' 200 OK { "reunited": true, "claim_id": 9, "found_item_id": 31, "verified": true }
Failure modes: 409 verification_required (high-value without
verified — nothing changes), 409 claim_not_open,
409 item_not_available, 404 not_found.
lostfound.found_logged,
lostfound.claim_filed and lostfound.reunited — claimant phone
numbers and proof notes never leave the system on those events.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 / location_not_found | Unknown/foreign id, no recording for that call, or a location that isn't yours / doesn't run Lost & Found. |
| 409 | no_numbers | No stock in that area code — try another. |
| 409 | verification_required / claim_not_open / item_not_available | Lost & Found release refused: high-value without verified, claim already closed, or item not in storage. |
| 422 | urgent_category | Lost child/person/medication — route the reporter to staff or 911, never the API queue. |
| 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.