Skip to navigation

Phone numbers

Numbers have different capabilities: standard phone numbers support SMS/MMS and voice; iMessage lines require a separate purchase; RCS and WhatsApp use their own onboarding. You can provision new numbers, attach them to agents, list existing numbers, retrieve messages for a number, and release numbers when no longer needed.

SMS compliance: Receiving inbound SMS works out of the box. To send outbound SMS, US carriers require 10DLC (10-Digit Long Code) registration. Fill out the registration form, it takes about 5 minutes and we submit everything to the carriers for you. If you can’t figure it out, schedule a call and we’ll handle the registration for you. Voice does not require 10DLC registration, but outbound calls have separate funding, number-readiness, and concurrency requirements. See Calls.

Search available numbers

Preview purchasable numbers before buying one.

GET /v1/numbers/available

Query parameters

ParameterDescription
areaCode3-digit area code, e.g. 415. The most broadly supported filter.
cityCity name. Pair with state. Support varies by account.
state2-letter state code, e.g. CA. Pair with city for consistent results; standalone support varies by account.
zip5-digit ZIP. Support varies by account.
countryUS or CA. Defaults to US.
limitMax results, 1 to 30. Defaults to 10.

Searching with no criteria returns a page of whatever is in stock. Giving a Canadian area code searches Canada even though country defaults to US.

Criteria support varies by account and there is no single rule that holds everywhere:

  • areaCode is the most broadly supported filter.
  • city, state and zip work on some accounts and not others. Where they aren’t supported they are ignored rather than rejected, so check what comes back instead of assuming the filter applied.
  • state on its own is honored on some accounts and rejected with 400 on others. Pair it with city for consistent behavior.

Number search isn’t available on every account. When it isn’t, this endpoint returns 404 (or 422 if it’s enabled but not yet configured).

That doesn’t block you from buying a number. POST /v1/numbers accepts an areaCode and picks one for you on any account, so search is a convenience rather than a requirement. Contact support if you want search enabled.

Example

curl "https://api.agentphone.ai/v1/numbers/available?areaCode=415&limit=5" \
-H "Authorization: Bearer $AGENTPHONE_API_KEY"
{
"data": [
{
"phoneNumber": "+14155550123",
"city": "San Francisco",
"state": "CA",
"rateCenter": "SNFC CNTRL",
"areaCode": "415"
}
],
"widened": false
}

phoneNumber is the only guaranteed field. city, state and rateCenter are null when the inventory source returns no per-number detail, so key off phoneNumber and treat the rest as best-effort labels.

widened: true means your exact criteria were out of stock and these are nearby alternatives instead. Check it before assuming you got what you asked for.

Buying a number you found

Pass the same phoneNumber to POST /v1/numbers:

curl -X POST "https://api.agentphone.ai/v1/numbers" \
-H "Authorization: Bearer $AGENTPHONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phoneNumber": "+14155550123"}'

Search results are not reserved. This is live inventory, so a number can be bought by someone else between your search and your purchase. When that happens the purchase returns 409 with fresh alternatives in the same shape as the search response. Pick another and retry rather than treating it as fatal.

Search and buy under the same account. Each account and sub-account has its own inventory. Use the same account and X-Sub-Account-Id for GET /v1/numbers/available and POST /v1/numbers. Inventory and provisioning can differ across accounts; a candidate from another account or vendor may return 409 with NUMBER_UNAVAILABLE. If you don’t need a specific number, send an areaCode instead and the sub-account gets the next available number in that code.

import httpx
H = {"Authorization": f"Bearer {API_KEY}"}
def buy_in_area_code(area_code: str) -> dict:
r = httpx.get(
"https://api.agentphone.ai/v1/numbers/available",
params={"areaCode": area_code, "limit": 5},
headers=H,
)
r.raise_for_status()
for entry in r.json()["data"]:
resp = httpx.post(
"https://api.agentphone.ai/v1/numbers",
json={"phoneNumber": entry["phoneNumber"]},
headers=H,
)
if resp.status_code == 409 and "NUMBER_UNAVAILABLE" in resp.text:
continue # a confirmed inventory conflict; try the next candidate
resp.raise_for_status()
return resp.json()
raise RuntimeError(f"every candidate in {area_code} was taken")

Search is rate limited to 30 requests per minute per account, and purchase to 20.

Create number

Provision a standard SMS/voice number, or use type: "imessage" for an iMessage line. Standard numbers are available in the US and Canada. UK numbers (UK or GB) are available only on enabled accounts and are SMS-only; US/CA location filters do not apply to them.

POST /v1/numbers

Request body

FieldTypeRequiredDefaultDescription
typestringNo"sms""sms" for standard numbers or "imessage" for iMessage provisioning.
imessageTypestringNo"outbound"For iMessage: "inbound" or "outbound". See provisioning responses and charges.
phoneNumberstring or nullNonullExact number returned by the availability search. Search and purchase in the same account scope.
countrystringNo"US"Two-letter country code for the number (e.g., "US", "CA")
areaCodestring or nullNonullPreferred area code (US/CA only, e.g., "415"). If none are available in that area code, you’ll get another number in the same country.
agentIdstring or nullNonullOptionally attach the number to an agent immediately
externalIdstring or nullNonullYour own identifier for the number, unique within your account. Echoed on every number response and webhook; usable for lookup as ext:<externalId>.
expiresAtdatetime or nullNonullHard end date (ISO 8601, at least 5 minutes out, at most 10 years). The number is released automatically once it passes and you receive a number.released webhook. Move or clear it later with PATCH /v1/numbers/{number_id}.

Example

curl -X POST "https://api.agentphone.ai/v1/numbers" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"country": "US",
"areaCode": "415",
"agentId": "agt_abc123"
}'
{
"id": "num_xyz789",
"phoneNumber": "+15551234567",
"country": "US",
"status": "active",
"agentId": "agt_abc123",
"outboundSms": "activating",
"createdAt": "2025-01-15T10:45:00Z"
}

Wait for outbound SMS to activate

A new US or CA number is attached to your messaging campaign right after purchase. That attachment takes a couple of minutes to confirm, and sends from the number are rejected with a 403 until it does. The outboundSms field on every number response tells you where the number stands:

ValueMeaning
activatingThe campaign attachment is in progress. Sends return 403 until it completes, typically within 2 to 3 minutes of purchase.
enabledThe attachment is confirmed. Sends usually go through; a send in the first few minutes can fail with 4773 (see below).
registration_requiredThis number needs registration or attachment to an approved campaign. Check the exact number and registration account scope.

After POST /v1/numbers returns, poll GET /v1/numbers/{number_id} until outboundSms is "enabled" before sending messages from the number:

curl -X GET "https://api.agentphone.ai/v1/numbers/num_xyz789" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"id": "num_xyz789",
"phoneNumber": "+15551234567",
"status": "active",
"outboundSms": "enabled"
}

Carriers pick up the new campaign mapping on their own schedule once the attachment is confirmed. This usually takes seconds, but a send fired in the first few minutes after enabled can occasionally fail with carrier error 4773 (campaign inactive for this destination). Retry that message once after about 30 seconds; it does not mean the number or campaign is misconfigured. Numbers outside the US and CA have no campaign requirement and are enabled from purchase.

List numbers

List all phone numbers for this project.

GET /v1/numbers

Query parameters

ParameterTypeRequiredDefaultDescription
limitintegerNo20Number of results to return (max 100)
offsetintegerNo0Number of results to skip (min 0)

Example

curl -X GET "https://api.agentphone.ai/v1/numbers?limit=10&offset=0" \
-H "Authorization: Bearer YOUR_API_KEY"

Look up a number

Check line-type intelligence for any phone number before you message it: whether it is a mobile, landline, or VoIP line, its country, and whether the handset supports RCS. Useful for keeping agents from spending messages on numbers that can never receive them.

GET /v1/numbers/lookup

Billed at $0.009 per number looked up.

Query parameters

ParameterTypeRequiredDescription
phoneNumbersstringYesComma-separated E.164 numbers to look up, up to 25 per request

Example

curl -X GET "https://api.agentphone.ai/v1/numbers/lookup?phoneNumbers=%2B14155551234,%2B15551230000" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{
"phoneNumber": "+14155551234",
"lineType": "mobile",
"country": "US",
"rcsEnabled": true
},
{
"phoneNumber": "+15551230000",
"lineType": "landline",
"country": "US",
"rcsEnabled": false
}
]
}

lineType is "mobile", "landline", or "voip"; fields are null when a number cannot be resolved. Accounts with a negative balance receive a 402 before any lookup runs.

Update number

Change a number’s own fields. Only the fields you send are changed; null clears a field, omitting it leaves it alone. Unsupported fields are rejected with 422.

PATCH /v1/numbers/{number_id}

Request body

FieldTypeDescription
externalIdstring or nullYour own identifier for the number, unique within your account.
expiresAtdatetime or nullHard end date. Set it to schedule an automatic release, move it later to extend the number, or send null to keep the number indefinitely. Must be at least 5 minutes and at most 10 years in the future.

Expiring numbers

If you sell or lease numbers for a fixed period, set expiresAt when you buy the number and extend it on each renewal. You don’t need to remember to call DELETE:

  1. Every few minutes a sweep releases any active number whose expiresAt has passed, exactly as DELETE /v1/numbers/{number_id} would.
  2. A number.released webhook with reason: "expired" is sent to the number’s webhook (the agent’s if it has one, otherwise your project default).
  3. Messages and conversation history are kept; the number itself is gone and cannot be recovered.

Monthly billing is unchanged: a number with an expiry bills like any other until it is released. Expiry is not available on iMessage area-code orders, which are fulfilled later; set it with PATCH once the number is delivered.

Example

# Set the lease end to 2026-12-15 (send a later date to extend, null to remove)
curl -X PATCH "https://api.agentphone.ai/v1/numbers/num_xyz789" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"expiresAt": "2026-12-15T00:00:00Z"}'

Enable or disable inbound calls

Turn incoming calls on or off for one number. With inbound calls disabled the number rejects calls before they are answered: no call record is created and nothing is billed. Messaging in both directions and outbound calls are unaffected.

PATCH /v1/numbers/{number_id}/inbound-calls

Request body

FieldTypeRequiredDescription
enabledbooleanYesfalse rejects inbound calls on this number, true answers them again

Example

curl -X PATCH "https://api.agentphone.ai/v1/numbers/num_xyz789/inbound-calls" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
{
"success": true,
"inboundCallsEnabled": false
}

The setting survives agent changes. Attaching the number to an agent, or editing that agent, keeps inbound calls off, so you can hold an agent assignment on a number for messaging only. Disable before you attach and there is no window in which a call can be answered.

Use the number’s id as returned by the API in the path, not the phone number itself. Numbers that cannot enforce the setting return 422: messaging-only lines, and most numbers that route calls to your own SIP trunk, which manage inbound calls on your platform instead. Numbers under a sub-account need the X-Sub-Account-Id header alongside your main API key.

Delete number (release)

Release (delete) a phone number.

For a standard number, this action:

  1. Releases the number back to the carrier pool
  2. Marks the number as "released" in the database
  3. Keeps all messages and conversation history for audit purposes

This action is irreversible. The number cannot be recovered once released.

DELETE /v1/numbers/{number_id}

Example

curl -X DELETE "https://api.agentphone.ai/v1/numbers/num_xyz789" \
-H "Authorization: Bearer YOUR_API_KEY"

For iMessage, release deactivates the line immediately; provider teardown is handled separately. Check Billing for the subscription state and contact support if a released line still appears billable. Deleting or detaching an agent does not release its number.

Get messages for number

Get messages for a specific phone number. Supports cursor-based pagination via before/after timestamps. This endpoint uses from_ for the sender, unlike the webhook field data.from or conversation-history field fromNumber.

GET /v1/numbers/{number_id}/messages

Query parameters

ParameterTypeRequiredDefaultDescription
limitintegerNo50Number of messages to return (max 200)
beforestring (datetime) or nullNonullReturn messages before this timestamp (ISO 8601)
afterstring (datetime) or nullNonullReturn messages after this timestamp (ISO 8601)

Example

curl -X GET "https://api.agentphone.ai/v1/numbers/num_xyz789/messages?limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{
"id": "msg_001",
"from_": "+15559876543",
"to": "+15551234567",
"body": "Hi, I need help with my order",
"receivedAt": "2025-01-15T12:00:00Z"
}
],
"hasMore": false
}