Assign a phone number
Buy a phone number and attach it to one agent. This save completes the "Assign Number" task.
Every success buys a real number from the carrier and bills for it. You cannot choose the area code or the country: the number is the first available local US number that can take voice and SMS, so it is not matched to the rooftop's location.
Auth: Bearer token of a signed-in console user, checked with the identity service. The token must carry a dealer group, and that dealer group must match the body's enterpriseId. Rate limit: 5 requests per dealer group and 5 per user in each fixed one-hour window aligned to the clock hour. Processing: synchronous. The call waits for the number purchase, the messaging-service attach, the save and the call routing.
Before you call it
- The agent must exist (Create an agent). Get
teamAgentMappingIdfrom List agents. - In the console UI, the "Assign Number" task stays locked until "Agent Customization" is done.
- Check
phoneNumberin List agents first. For an inbound agent that already has an active number, the call returns that number and buys nothing. For an outbound agent there is no such check: every call buys a new number. - Both rate limits must pass. Every authenticated request counts, even one later rejected by the dealer-group check or the lock. When the limit store is unavailable, every request is rejected with 429.
- Send
Authorization: Bearer YOUR_TOKENandContent-Type: application/json.
What the console checks
- Token check.
- Rate limit.
- The body's
enterpriseIdmust match the token's dealer group. - The rooftop must belong to that dealer group and be active.
- A 60-second lock on this agent blocks a second request for the same agent while one is running.
The console then calls the agent backend with only enterpriseId, teamId and teamAgentMappingId (30-second timeout). The agent backend checks that the agent is active and belongs to this dealer group and rooftop, buys the number from Twilio, and then:
- attaches the number to the messaging service;
- saves the number record;
- routes calls to the rooftop's call provider.
The number is not written to the configuration store's agent record.
Request body
- Scope it: send
enterpriseId(the token's own dealer group),teamIdandteamAgentMappingId. - After a 424 timeout: do not retry until you have checked whether a number was attached.
| Parameter | Type | Required | Description |
|---|---|---|---|
enterpriseId | string | Required | Dealer group. Must match the token. |
teamId | string | Required | Rooftop. Must belong to the dealer group and be active. |
teamAgentMappingId | string | Required | Agent the number is for. |
Sample request
curl -X POST "https://api.spyne.ai/console/v1/telephony/phone-numbers" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enterpriseId":"<enterpriseId>","teamId":"<teamId>","teamAgentMappingId":"<teamAgentMappingId>"}'import requests
res = requests.post(
"https://api.spyne.ai/console/v1/telephony/phone-numbers",
headers={"Authorization": "Bearer YOUR_TOKEN"},
json={
"enterpriseId": "<enterpriseId>",
"teamId": "<teamId>",
"teamAgentMappingId": "<teamAgentMappingId>",
},
)
print(res.status_code, res.json())const res = await fetch('https://api.spyne.ai/console/v1/telephony/phone-numbers', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
enterpriseId: '<enterpriseId>',
teamId: '<teamId>',
teamAgentMappingId: '<teamAgentMappingId>',
}),
});
console.log(res.status, await res.json());Sample response
{
"message": "Phone number provisioned successfully",
"error": false,
"code": "",
"details": null,
"data": {
"message": "Phone number created and attached successfully",
"status": "success",
"provider": "twilio",
"country": "US",
"areaCode": null,
"phoneNumber": "+1XXXXXXXXXX",
"phoneNumberSid": "<phoneNumberSid>",
"enterpriseId": "<enterpriseId>",
"teamId": "<teamId>",
"teamAgentMappingId": "<teamAgentMappingId>",
"callProvider": "<callProvider>",
"webhookOwner": "<webhookOwner>",
"webhookUrl": "https://example.com/webhook",
"messagingServiceSid": "<messagingServiceSid>",
"vapiPhoneNumberId": null
}
}{
"message": "Phone number provisioned successfully",
"error": false,
"code": "",
"details": null,
"data": {
"message": "Phone number is already attached to this teamAgentMappingId",
"status": "success",
"alreadyExists": true,
"created": false,
"phoneNumber": "+1XXXXXXXXXX",
"teamAgentMappingId": "<teamAgentMappingId>"
}
}{ "message": "No available phone numbers found in US", "error": true, "code": "ERROR_BAD_REQ_400", "details": null, "data": null }{ "message": "Invalid Token", "error": true, "code": "ERROR_AUTH_401", "details": "", "data": null }{ "message": "You cannot purchase a phone number for another enterprise.", "error": true, "code": "ERROR_FORBIDDEN_403", "details": null, "data": null }{ "message": "That rooftop does not exist on your enterprise.", "error": true, "code": "ERROR_NOT_FOUND_404", "details": null, "data": null }{ "message": "A phone number request for this agent is already in progress. Please wait for it to finish.", "error": true, "code": "ERROR_CONFLICT_409", "details": null, "data": null }{ "message": "The phone number request did not complete in time. Do NOT retry immediately — check whether a number was assigned before trying again.", "error": true, "code": "ERROR_FAILED_DEPENDENCY_424", "details": null, "data": null }{ "message": "This enterprise has purchased too many phone numbers in a short period. Please try again later.", "error": true, "code": "ERROR_TOO_MANY_REQUESTS_429", "details": null, "data": null }{ "message": "Could not provision a phone number right now. Please try again.", "error": true, "code": "ERROR_SERVER_500", "details": null, "data": null }Response parameters (200)
| Parameter | Type | Description |
|---|---|---|
message | string | Phone number provisioned successfully. Also returned when an existing number is reused. |
error | boolean | false on success. |
code, details | string or null | Empty on success. |
data.phoneNumber | string | The E.164 number. The console UI reads this field. |
data.alreadyExists | boolean | true when an existing number was returned and nothing was bought. |
data.phoneNumberSid | string | Carrier id of the number. |
data.provider | string | twilio. |
data.country | string | Country of the number. |
data.areaCode | null | Always null on this path, because no area code is sent. |
data.callProvider, webhookOwner, webhookUrl, vapiPhoneNumberId | string or null | Where incoming calls are routed. |
data.messagingServiceSid | string or null | Messaging service the number was attached to. |
data.enterpriseId, teamId, teamAgentMappingId | string | Echo of the scope. |
Errors
| Status | Reason | When it happens |
|---|---|---|
400 | Message from the agent backend | No number available (No available phone numbers found in US); enterpriseId does not match teamAgentMappingId; teamId does not match teamAgentMappingId; Active teamAgentMapping not found for .... |
400 | Missing input | enterpriseId, teamId or teamAgentMappingId is missing. The framework rejects the call. The body for this case is not documented. |
401 | Invalid token | Missing or rejected token. |
403 | Your session is not scoped to an enterprise ... | The token carries no dealer group or no user id. |
403 | You cannot purchase a phone number for another enterprise. | The body's enterpriseId is not the token's dealer group. |
404 | That rooftop does not exist on your enterprise. | The rooftop is not found, or belongs to another dealer group. |
408 | Request timeout | The console could not reach the identity service to check the token. |
409 | That rooftop is deactivated ... | The rooftop is inactive. |
409 | A phone number request for this agent is already in progress ... | Another request for the same agent holds the lock. |
424 | Could not verify the rooftop right now. | The rooftop lookup failed. |
424 | Phone number provisioning is temporarily unavailable. | The lock store is unavailable, so the console refuses to buy. |
424 | The phone number request did not complete in time ... | No answer from the agent backend within 30 seconds. A number may have been bought. |
429 | Too many requests | More than 5 calls this hour for the dealer group or the user, or the limit store is unavailable. |
500 | Could not provision a phone number right now. | The agent backend returned a 5xx, for example when the carrier purchase failed. |
What happens next
- On success the console UI shows
data.phoneNumber. It then calls Mark a task done withtaskName: "Assign Number"and the tab'ssegmentName. That call is not awaited, and its failure is not shown. - The console UI also starts the receptionist trial clock. It does this on every tab, not only the receptionist.
- Read the number back from List agents. That is the record the rest of the console uses.
Updated about 1 hour ago
