Skip to main content

Telephony API

Complete India KYC, rent Indian phone numbers, link them to a voice-agent bot, place outbound PSTN calls, and fetch recordings — all with your cm_ key.

Overview

The Telephony API is a full lifecycle for CallMissed Numbers: submit an India KYC (compliance) application, wait for it to be accepted, search available Indian numbers, buy one (a paid action that draws your real credit balance), manage it, and place outbound PSTN calls answered by your AI voice agent.

Base path: https://api.callmissed.com/api/v1/telephony

The journey is ordered. You cannot buy a number until you hold an accepted KYC application, and you cannot place a call until you own an active number. Follow the flow below top to bottom.

  1. 1

    Submit KYC

    Upload your business documents and details once

  2. 2

    CallMissed

    Reviews the application; poll or sync until it is

  3. 3

    Buy & call

    Search a number, buy it (paid), link a bot, place calls

Authentication. Every endpoint accepts both a JWT (Authorization: Bearer <jwt>) and an API key (Authorization: Bearer cm_<key>). API-key callers need the telephony:read scope for search/list/get and telephony:write for buy, release, patch, KYC submit/sync, and originating calls. Money-affecting and destructive actions (buy, release, patch, KYC submit, originate call) additionally require an owner/admin role when called with a JWT.

Availability. Telephony is India-only and enabled per tenant. When the feature is not enabled for your tenant, the routes are unmounted and every call returns 404.

1. Submit KYC (Compliance)

Every rented Indian number must be backed by an accepted KYC application. Submission is a multipart form carrying your business details plus the two mandatory documents:

  • Registration certificate — Certificate of Incorporation (CIN) or Udyam certificate
  • GST certificate

Files must be PDF, JPEG, or PNG, up to 5 MB each. The legal business name must match exactly on both documents or the application is rejected upstream.

POST /compliance · scope telephony:write (owner/admin for JWT)

Form fields:

FieldTypeRequiredNotes
aliasstring (1–128)YesA label for this application
business_namestring (1–100)YesLegal name, exactly as printed on both documents
registration_numberstring (1–64)YesCIN or Udyam number
emailstring (3–254)YesBusiness contact email
address_line1string (1–255)Yes
address_line2string (0–255)No
citystring (1–100)Yes
statestring (1–100)Yes
postal_codestring (1–16)Yes
registration_certfileYesCOI or Udyam certificate (PDF/JPEG/PNG, ≤ 5 MB)
gst_certfileYesGST certificate (PDF/JPEG/PNG, ≤ 5 MB)
curl -X POST https://api.callmissed.com/api/v1/telephony/compliance \
  -H "Authorization: Bearer cm_your_api_key" \
  -F 'alias=Acme India KYC' \
  -F 'business_name=ACME TECHNOLOGIES PRIVATE LIMITED' \
  -F 'registration_number=U72900KA2020PTC000000' \
  -F 'email=compliance@acme.in' \
  -F 'address_line1=123 MG Road' \
  -F 'address_line2=Suite 400' \
  -F 'city=Bengaluru' \
  -F 'state=Karnataka' \
  -F 'postal_code=560001' \
  -F 'registration_cert=@certificate-of-incorporation.pdf' \
  -F 'gst_cert=@gst-certificate.pdf'

Response (200 OK) — a compliance application:

{
  "id": "3f9a1c20-7d8e-4b1a-9c2f-5e6a7b8c9d0e",
  "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "alias": "Acme India KYC",
  "country_iso": "IN",
  "number_type": "local",
  "user_type": "business",
  "status": "submitted",
  "rejection_reason": null,
  "business_name": "ACME TECHNOLOGIES PRIVATE LIMITED",
  "registration_number": "U72900KA2020PTC000000",
  "created_at": "2026-04-19T12:00:00Z"
}

Status codes

CodeMeaning
200Application created and submitted
403Missing telephony:write scope, or JWT caller is not an owner/admin
413A document exceeds the 5 MB limit
422A document is missing, empty, or not a PDF/JPEG/PNG
404Telephony not enabled for your tenant

2. Check KYC Status

Applications move through draft → submitted → accepted / rejected. Only an accepted application can back a number purchase.

EndpointScopePurpose
GET /compliancetelephony:readList your applications
GET /compliance/{application_id}telephony:readGet one application + status
POST /compliance/{application_id}/synctelephony:writeRefresh status from the carrier

GET /compliance accepts limit (1–200, default 50) and offset (≥ 0). It returns an array of applications, newest first. POST /compliance/{application_id}/sync pulls the latest status and, if rejected, populates rejection_reason.

# List applications
curl https://api.callmissed.com/api/v1/telephony/compliance \
  -H "Authorization: Bearer cm_your_api_key"

# Refresh one application's status
curl -X POST https://api.callmissed.com/api/v1/telephony/compliance/{application_id}/sync \
  -H "Authorization: Bearer cm_your_api_key"

Once status is accepted, the application carries a compliance reference you pass as compliance_application_id when buying a number (step 4). A GET/sync on an application you do not own returns 404.

3. Search Available Numbers

Search for Indian numbers before buying. Rates are returned after your tenant markup — rental_credits is what you will actually be charged per month.

GET /numbers/search · scope telephony:read

Query parameters

ParamValuesDefault
country_iso2-letter ISO (IN)IN
typelocal / mobile / tollfree—
patterndigit substring to match (max 32 chars)—
limit1–2020
curl "https://api.callmissed.com/api/v1/telephony/numbers/search?country_iso=IN&type=local&pattern=80802&limit=10" \
  -H "Authorization: Bearer cm_your_api_key"

Response (200 OK) — an array of search hits:

[
  {
    "number": "+918080247309",
    "number_type": "local",
    "country": "IN",
    "region": "Mumbai",
    "monthly_rental_rate_usd": 2.5,
    "rental_credits": 250,
    "voice_enabled": true,
    "sms_enabled": false
  }
]

4. Buy a Number

Rent one of the searched numbers. This is a paid action — it draws your real (paid) credit balance. The signup bonus does not cover a number rental; if your paid balance is short, you get a 402 telling you to top up.

POST /numbers · scope telephony:write (owner/admin for JWT)

Request body

FieldTypeRequiredNotes
e164stringYesThe number to buy, e.g. +918080247309
compliance_application_idstringYesThe compliance reference from your accepted KYC application

The purchase runs in a strict, money-safe order: it verifies your KYC application is accepted (else 409), confirms the number is still available and prices it live (else 422), deducts the rental credits, and only then rents the number. If the rent fails, the credits are refunded automatically.

curl -X POST https://api.callmissed.com/api/v1/telephony/numbers \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "e164": "+918080247309",
    "compliance_application_id": "your-accepted-compliance-reference"
  }'

Response (200 OK) — the rented number:

{
  "id": "9c2b7e30-1d8a-4c5f-9b3d-2f4a6e8b1c2d",
  "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "e164": "+918080247309",
  "country_iso": "IN",
  "number_type": "local",
  "status": "active",
  "bot_id": null,
  "alias": null,
  "monthly_rental_rate_usd": 2.5,
  "rental_credits": 250,
  "added_on": "2026-04-19",
  "renewal_date": "2026-05-19",
  "config": null,
  "metadata": null,
  "created_at": "2026-04-19T12:00:00Z"
}

Status codes

CodeMeaning
200Number rented and active
402Insufficient real balance — top up to buy
403Missing telephony:write scope, or JWT caller is not an owner/admin
409KYC not accepted, or you already hold this number
422Number no longer available, or e164 is malformed
404Telephony not enabled for your tenant

5. Manage Numbers

EndpointScopePurpose
GET /numberstelephony:readList your rented numbers
GET /numbers/{number_id}telephony:readGet one number
PATCH /numbers/{number_id}telephony:writeUpdate alias / linked bot / per-number call config
DELETE /numbers/{number_id}?confirm=truetelephony:writeRelease a number (permanent)

GET /numbers accepts status (e.g. active), limit (1–200, default 50), and offset (≥ 0). A number moves through pending → active → suspended (unpaid) → released.

# List your rented numbers
curl https://api.callmissed.com/api/v1/telephony/numbers \
  -H "Authorization: Bearer cm_your_api_key"

# Get one
curl https://api.callmissed.com/api/v1/telephony/numbers/{number_id} \
  -H "Authorization: Bearer cm_your_api_key"

# Update the alias and link a voice-agent bot
curl -X PATCH https://api.callmissed.com/api/v1/telephony/numbers/{number_id} \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"alias": "Support line", "bot_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"}'

Per-number call overrides

The optional config object holds per-number call-handling overrides that win over the linked bot's config on this number's calls — so two numbers can share one bot yet greet and speak differently. The object replaces the stored overrides on every write (send the full set each time; {} clears every override). Unknown keys return 422.

KeyTypeNotes
voice_modelstringVoice LLM model id
voicestringVoice / speaker id
languagestringe.g. hi-IN
stt_modelstringSpeech-to-text model id
tts_modelstringText-to-speech model id
tts_providerstringTTS provider id
tts_enginestringTTS engine id
greetingstringOpening line spoken on the call
system_promptstringOverrides the bot's persona for this number
max_call_duration_secondsinteger30–14400
toolsarray of stringsUp to 20 agent tool names
curl -X PATCH https://api.callmissed.com/api/v1/telephony/numbers/{number_id} \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"config": {"greeting": "Namaste! Aap Support line par pahunche hain.", "language": "hi-IN", "max_call_duration_seconds": 600}}'

Release a number

curl -X DELETE "https://api.callmissed.com/api/v1/telephony/numbers/{number_id}?confirm=true" \
  -H "Authorization: Bearer cm_your_api_key"

confirm=true is required — releasing a number is permanent, stops its monthly rental charge, and is not refunded. Returns 204 No Content on success, 400 if confirm is omitted, and 404 if the number is not found or not owned by your tenant.

Declare a number for AI calls (India)

TRAI requires a business making automated or AI calls to declare that use, and the caller IDs it uses, to its telecom provider first (press release 119/2026). After you have made that declaration with your provider, record it on the number. Campaigns check it before they dial; see TRAI readiness.

curl -X PUT https://api.callmissed.com/api/v1/telephony/numbers/{number_id}/a2p-declaration \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"series": "1600", "tsp_reference": "DECL-2026-0042", "confirm_declared_to_tsp": true}'
FieldTypeRequiredNotes
series140 | 1600 | 1601 | standardYes140, 1600 and 1601 are TRAI's commercial-call series; standard is an ordinary number
tsp_referencestring (≤64)NoYour telecom provider's reference for the declaration
confirm_declared_to_tspbooleanYesMust be true. You confirm the declaration was made to your provider

Returns the number with a2p_declared_at, a2p_series and a2p_tsp_reference set. Declaring again updates them. DELETE on the same path withdraws the declaration. Both need telephony:write. A released number returns 409.

6. Place a Call

Originate an outbound PSTN call from one of your active numbers. Link a bot_id to have your AI voice agent handle the call; if you omit it, the number's persistently bound bot is used.

POST /calls · scope telephony:write (owner/admin for JWT)

Request body

FieldTypeRequiredNotes
from_number_idUUIDYesAn active number you own
to_e164stringYesThe destination, e.g. +919000000000
bot_idUUIDNoVoice-agent bot to answer the call
reasonstring (≤ 500)NoPlain-language purpose; spoken on the outbound greeting
curl -X POST https://api.callmissed.com/api/v1/telephony/calls \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "from_number_id": "9c2b7e30-1d8a-4c5f-9b3d-2f4a6e8b1c2d",
    "to_e164": "+919000000000",
    "bot_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
    "reason": "Confirming your appointment for tomorrow"
  }'

Response (200 OK) — the created call (status advances via webhooks):

{
  "id": "5e6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c",
  "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "phone_number_id": "9c2b7e30-1d8a-4c5f-9b3d-2f4a6e8b1c2d",
  "bot_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
  "voice_session_id": "7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c",
  "direction": "outbound",
  "status": "initiated",
  "remote_e164": "+919000000000",
  "bill_duration_seconds": null,
  "billed_duration_seconds": null,
  "cost_credits": null,
  "hangup_cause_code": null,
  "hangup_source": null,
  "recording_id": null,
  "metadata": null,
  "created_at": "2026-04-19T12:00:00Z"
}

Status codes

CodeMeaning
200Call created and dialing
402Insufficient credits to reserve the call
403Missing telephony:write scope, or JWT caller is not an owner/admin
404from_number_id not found/active, or an explicit bot_id not found
422to_e164 is malformed
429Concurrent-call limit reached (up to 10 live calls per tenant)

7. List & Fetch Calls

EndpointScopePurpose
GET /callstelephony:readList your calls
GET /calls/{call_id}telephony:readGet one call
GET /calls/{call_id}/recordingtelephony:readSigned recording URL

GET /calls query parameters

ParamValuesDefault
directioninbound / outbound—
statusinitiated / ringing / in_progress / completed / failed / no_answer / busy—
limit1–20050
offset≥ 00
curl "https://api.callmissed.com/api/v1/telephony/calls?direction=outbound&status=completed&limit=50&offset=0" \
  -H "Authorization: Bearer cm_your_api_key"

Returns an array of calls, newest first. GET /calls/{call_id} fetches a single call (404 if not owned).

Recordings

If a call was recorded, fetch a short-lived signed URL for its audio:

curl https://api.callmissed.com/api/v1/telephony/calls/{call_id}/recording \
  -H "Authorization: Bearer cm_your_api_key"

Response (200 OK):

{ "url": "https://media.callmissed.com/recordings/....?token=..." }

The URL is time-limited — fetch it on demand rather than storing it. Returns 404 if the call has no recording, and 503 if recording storage is temporarily unavailable.

Scopes

ScopeGrants
telephony:readSearch numbers, list/get numbers, list/get compliance applications, list/get calls, fetch recording URLs
telephony:writeBuy/release/update numbers, submit & sync compliance applications, originate calls

Buy, release, patch, KYC submit, and originate-call also require an owner/admin role when called with a JWT (an API key carrying telephony:write is sufficient on its own).

Billing

ChargeWhen
Number rentalMonthly, in credits, per active number (rental_credits). Paid from your real balance — not the signup bonus. Renews on renewal_date.
Call usageReserved when a call is placed, then settled to the real cost from the call record after the call completes.

Releasing a number stops its monthly rental charge (no refund for the current period). Ensure sufficient credits before buying numbers or placing calls, or those calls return 402.

Webhooks

Telephony call-lifecycle and recording-ready events are delivered to the endpoints you configure via the Webhooks API. Payloads are HMAC-SHA256 signed — verify the X-CallMissed-Signature header exactly as shown on the Webhooks page before trusting a payload.