Twilio Phone Number Provider
🎯 Overview
The Number API Provider (Providers/number-api.js) handles phone number operations including searching available numbers, purchasing (provisioning), updating configuration, and releasing numbers.
Source: external/Integrations/Twilio/Providers/number-api.js
Key Capabilities:
- Search available phone numbers by country/area code
- Purchase phone numbers (local, toll-free, mobile)
- Configure SMS/Voice URLs and capabilities
- Update phone number settings
- Release (delete) phone numbers
- Regulatory compliance support (bundle SID, address SID)
- Get phone number pricing by country
- List available countries
🔌 Provider Functions
getList()
Search for available phone numbers in a specific country and area code.
Signature:
exports.getList = async(
SID,
authToken,
countryCode,
areaCode,
(capabilities = {}),
(type = 'local'),
);
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
SID | String | ✅ | - | Twilio Account SID |
authToken | String | ✅ | - | Twilio Auth Token |
countryCode | String | ✅ | - | ISO 3166-1 alpha-2 country code (US, GB, CA, etc.) |
areaCode | String | ❌ | - | Area code for local/mobile numbers (e.g., '212', '415') |
capabilities | Object | ❌ | {} | Required capabilities (sms, voice, mms, fax) |
type | String | ❌ | 'local' | Number type: 'local', 'mobile', 'toll_free' |
Returns:
Promise<
Array<{
friendlyName: string; // "+1 (212) 555-1234"
phoneNumber: string; // "+12125551234" (E.164 format)
lata: string; // Local Access and Transport Area
locality: string; // City name
rateCenter: string; // Rate center location
latitude: string;
longitude: string;
region: string; // State/province code
postalCode: string;
isoCountry: string; // Country code
addressRequirements: string; // "none", "any", "local", "foreign"
beta: boolean;
capabilities: {
voice: boolean;
SMS: boolean;
MMS: boolean;
fax: boolean;
};
}>
>;
Business Logic:
-
Create Twilio Client
-
Verify Country Availability
- Fetches country resource to check supported number types
- Returns empty array if requested type not available
-
Search by Number Type
- Local: Area code optional, returns local numbers
- Mobile: Mobile numbers (area code optional)
- Toll-Free: 800, 888, 877, etc. numbers (no area code)
-
Return Available Numbers
- Maximum 100 results per request
Example Usage:
const numberProvider = require('./Providers/number-api');
// Search local numbers in New York (area code 212)
const numbers = await numberProvider.getList(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN,
'US',
'212',
{},
'local',
);
console.log('Found numbers:', numbers.length);
numbers.forEach(num => {
console.log(`${num.friendlyName} - ${num.locality}, ${num.region}`);
console.log(`Capabilities: SMS=${num.capabilities.SMS}, Voice=${num.capabilities.voice}`);
});
// Output:
// +1 (212) 555-1234 - New York, NY
// Capabilities: SMS=true, Voice=true
// +1 (212) 555-5678 - New York, NY
// Capabilities: SMS=true, Voice=true
Search Toll-Free Numbers:
// Search toll-free numbers (no area code needed)
const tollFreeNumbers = await numberProvider.getList(
accountSID,
authToken,
'US',
null, // No area code for toll-free
{},
'toll_free',
);
console.log('Toll-free:', tollFreeNumbers[0].phoneNumber);
// Output: +18005551234
Search Mobile Numbers:
// Search mobile numbers in Los Angeles (area code 310)
const mobileNumbers = await numberProvider.getList(
accountSID,
authToken,
'US',
'310',
{},
'mobile',
);
console.log('Mobile:', mobileNumbers[0].phoneNumber);
Data Flow:
sequenceDiagram
participant API as DashClicks API
participant Provider as Number Provider
participant Twilio as Twilio SDK
API->>Provider: getList(countryCode, areaCode, type)
Provider->>Twilio: availablePhoneNumbers(country).fetch()
Twilio-->>Provider: Country resource
alt Number type available
Provider->>Twilio: .local.list() / .mobile.list() / .tollFree.list()
Note over Provider,Twilio: limit: 100, areaCode (if provided)
Twilio-->>Provider: Available numbers array
Provider-->>API: Return numbers
else Type not available
Provider-->>API: Return empty array []
end
style Provider fill:#e3f2fd
style Twilio fill:#fff4e6
addProvisionNumber()
Purchase (provision) a phone number from Twilio.
Signature:
exports.addProvisionNumber = async(SID, authToken, dataToAdd);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
SID | String | ✅ | Twilio Account SID |
authToken | String | ✅ | Twilio Auth Token |
dataToAdd | Object | ✅ | Phone number configuration |
dataToAdd Object:
| Field | Type | Required | Description |
|---|---|---|---|
phoneNumber | String | ✅ | Phone number to purchase (E.164 format) |
friendlyName | String | ❌ | Display name for the number |
smsUrl | String | ❌ | Webhook URL for incoming SMS |
voiceUrl | String | ❌ | Webhook URL for incoming calls |
smsMethod | String | ❌ | HTTP method for SMS webhook (GET or POST, default POST) |
voiceMethod | String | ❌ | HTTP method for voice webhook (default POST) |
bundleSid | String | ❌ | Regulatory compliance bundle SID (required for some countries) |
addressSid | String | ❌ | Address SID for compliance (required for some numbers) |
emergencyEnabled | Boolean | ❌ | Enable E911 emergency services |
emergencyAddressSid | String | ❌ | E911 address SID |
Returns:
Promise<{
sid: string; // Phone number SID (PN...)
accountSid: string;
friendlyName: string;
phoneNumber: string;
voiceUrl: string;
smsUrl: string;
voiceMethod: string;
smsMethod: string;
capabilities: {
voice: boolean;
SMS: boolean;
MMS: boolean;
fax: boolean;
};
addressRequirements: string;
beta: boolean;
dateCreated: Date;
dateUpdated: Date;
emergencyStatus: string; // "Active" if emergency enabled
emergencyAddressSid: string;
bundleSid: string;
identitySid: string;
origin: string; // "twilio" or "hosted"
status: string; // "in-use"
uri: string;
}>;
Business Logic:
-
Create Twilio Client
-
Prepare Provision Data
- Copy dataToAdd object
- Handle regulatory compliance (bundleSid, addressSid)
- Remove internal parameters (countryCode)
-
Purchase Number via Twilio
- Calls
client.incomingPhoneNumbers.create() - Twilio provisions number immediately
- Calls
-
Error Handling
- Enhanced error messages for compliance failures
- Code 21452: Bundle validation failed
- Code 21453: Address validation failed
Example Usage:
// Purchase local number with SMS/Voice webhooks
const number = await numberProvider.addProvisionNumber(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN,
{
phoneNumber: '+12125551234',
friendlyName: 'Customer Support Line',
smsUrl: 'https://app.dashclicks.com/webhooks/sms',
voiceUrl: 'https://app.dashclicks.com/webhooks/voice',
smsMethod: 'POST',
voiceMethod: 'POST',
},
);
console.log('Purchased:', number.phoneNumber);
console.log('SID:', number.sid); // PN1234567890abcdef
console.log('Status:', number.status); // in-use
Purchase with Regulatory Compliance (Required for some countries):
// Purchase number with regulatory bundle (required for US A2P)
const number = await numberProvider.addProvisionNumber(accountSID, authToken, {
phoneNumber: '+12125551234',
bundleSid: 'BU1234567890abcdef', // From regulatory-compliance provider
addressSid: 'AD1234567890abcdef', // From address provider
smsUrl: 'https://app.dashclicks.com/webhooks/sms',
voiceUrl: 'https://app.dashclicks.com/webhooks/voice',
});
Purchase with Emergency Services:
// Purchase with E911 enabled
const number = await numberProvider.addProvisionNumber(accountSID, authToken, {
phoneNumber: '+12125551234',
emergencyEnabled: true,
emergencyAddressSid: 'AD1234567890abcdef',
voiceUrl: 'https://app.dashclicks.com/webhooks/voice',
});
updateProvisionNumber()
Update configuration of a purchased phone number.
Signature:
exports.updateProvisionNumber = async(accountSID, authToken, numberSID, dataToUpdate);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
accountSID | String | ✅ | Twilio Account SID |
authToken | String | ✅ | Twilio Auth Token |
numberSID | String | ✅ | Phone number SID (PN...) |
dataToUpdate | Object | ✅ | Fields to update |
dataToUpdate Object (all optional):
| Field | Type | Description |
|---|---|---|
friendlyName | String | Update display name |
smsUrl | String | Update SMS webhook URL |
voiceUrl | String | Update voice webhook URL |
smsMethod | String | Update SMS HTTP method |
voiceMethod | String | Update voice HTTP method |
emergencyEnabled | Boolean | Enable/disable E911 |
emergencyAddressSid | String | Update E911 address |
Returns:
Promise<TwilioPhoneNumberInstance>;
Example Usage:
// Update SMS webhook URL
const updated = await numberProvider.updateProvisionNumber(
accountSID,
authToken,
'PN1234567890abcdef',
{
smsUrl: 'https://app.dashclicks.com/v2/webhooks/sms',
voiceUrl: 'https://app.dashclicks.com/v2/webhooks/voice',
},
);
console.log('Updated:', updated.phoneNumber);
console.log('New SMS URL:', updated.smsUrl);
Update Friendly Name:
// Change display name
await numberProvider.updateProvisionNumber(accountSID, authToken, numberSID, {
friendlyName: 'Sales Line - East Coast',
});
deleteProvisionNumber()
Release (delete) a purchased phone number.
Signature:
exports.deleteProvisionNumber = async(accountSID, authToken, numberSID);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
accountSID | String | ✅ | Twilio Account SID |
authToken | String | ✅ | Twilio Auth Token |
numberSID | String | ✅ | Phone number SID to delete |
Returns:
Promise<true>;
Example Usage:
// Release phone number
const result = await numberProvider.deleteProvisionNumber(
accountSID,
authToken,
'PN1234567890abcdef',
);
console.log('Number released:', result); // true
⚠️ WARNING: This permanently releases the number back to Twilio's pool. It may be purchased by another customer immediately.
getPricing()
Get phone number pricing for a specific country.
Signature:
exports.getPricing = async(SID, authToken, countryCode);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
SID | String | ✅ | Twilio Account SID |
authToken | String | ✅ | Twilio Auth Token |
countryCode | String | ✅ | ISO 3166-1 alpha-2 country code |
Returns:
Promise<{
country: string;
isoCountry: string;
phoneNumberPrices: Array<{
type: string; // "local", "mobile", "toll free"
basePrice: string;
currentPrice: string;
}>;
priceUnit: string; // Currency code (USD, GBP, etc.)
url: string;
}>;
Example Usage:
// Get US phone number pricing
const pricing = await numberProvider.getPricing(accountSID, authToken, 'US');
console.log('Country:', pricing.country); // United States
console.log('Currency:', pricing.priceUnit); // USD
// Local number price
const localPrice = pricing.phoneNumberPrices.find(p => p.type === 'local');
console.log('Local number:', localPrice.currentPrice); // 1.00 (per month)
// Toll-free price
const tollFreePrice = pricing.phoneNumberPrices.find(p => p.type === 'toll free');
console.log('Toll-free:', tollFreePrice.currentPrice); // 2.00 (per month)
Pricing by Country:
// Get UK pricing
const ukPricing = await numberProvider.getPricing(accountSID, authToken, 'GB');
console.log('UK Local:', ukPricing.phoneNumberPrices[0].currentPrice); // GBP
// Get Canada pricing
const caPricing = await numberProvider.getPricing(accountSID, authToken, 'CA');
console.log('CA Local:', caPricing.phoneNumberPrices[0].currentPrice); // CAD
availableCountries()
List all countries where Twilio offers phone numbers.
Signature:
exports.availableCountries = async(accountSID, authToken, limit);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
accountSID | String | ✅ | Twilio Account SID |
authToken | String | ✅ | Twilio Auth Token |
limit | Number | ❌ | Maximum countries to return |
Returns:
Promise<
Array<{
country: string; // "United States"
countryCode: string; // "US"
uri: string;
beta: boolean;
subresourceUris: {
local: string; // URI if local numbers available
mobile: string; // URI if mobile numbers available
tollFree: string; // URI if toll-free available
};
}>
>;
Example Usage:
// Get all available countries
const countries = await numberProvider.availableCountries(accountSID, authToken, 100);
console.log('Countries:', countries.length);
countries.forEach(country => {
console.log(`${country.country} (${country.countryCode})`);
console.log(' Local:', !!country.subresourceUris.local);
console.log(' Mobile:', !!country.subresourceUris.mobile);
console.log(' Toll-Free:', !!country.subresourceUris.tollFree);
});
// Output:
// United States (US)
// Local: true
// Mobile: true
// Toll-Free: true
// United Kingdom (GB)
// Local: true
// Mobile: true
// Toll-Free: false
💡 Integration Examples
Phone Number Purchase Flow
async function purchasePhoneNumber(countryCode, areaCode) {
// 1. Search available numbers
const available = await numberProvider.getList(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN,
countryCode,
areaCode,
{},
'local',
);
if (available.length === 0) {
throw new Error('No available numbers found');
}
// 2. Purchase first available
const number = await numberProvider.addProvisionNumber(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN,
{
phoneNumber: available[0].phoneNumber,
friendlyName: `${countryCode} ${areaCode} Number`,
smsUrl: process.env.TWILIO_SMS_URL,
voiceUrl: process.env.TWILIO_VOICE_URL,
},
);
// 3. Store in database
await TwilioNumber.create({
sid: number.sid,
phoneNumber: number.phoneNumber,
friendlyName: number.friendlyName,
accountSid: number.accountSid,
smsUrl: number.smsUrl,
voiceUrl: number.voiceUrl,
capabilities: number.capabilities,
});
return number;
}
Number Search UI
async function searchNumbers(filters) {
const { countryCode, areaCode, type, capabilities } = filters;
// Search numbers
const numbers = await numberProvider.getList(
accountSID,
authToken,
countryCode,
areaCode,
capabilities,
type,
);
// Format for UI
return numbers.map(num => ({
phoneNumber: num.phoneNumber,
friendlyName: num.friendlyName,
location: `${num.locality}, ${num.region}`,
capabilities: {
sms: num.capabilities.SMS,
voice: num.capabilities.voice,
mms: num.capabilities.MMS,
},
addressRequirements: num.addressRequirements,
}));
}
Update Number Configuration
async function updateNumberWebhooks(numberSID, smsUrl, voiceUrl) {
const updated = await numberProvider.updateProvisionNumber(accountSID, authToken, numberSID, {
smsUrl,
voiceUrl,
});
// Update database
await TwilioNumber.updateOne({ sid: numberSID }, { smsUrl, voiceUrl });
return updated;
}
Release Unused Numbers
async function releaseUnusedNumbers() {
// Find numbers not used in last 30 days
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const unusedNumbers = await TwilioNumber.find({
lastUsed: { $lt: thirtyDaysAgo },
});
const released = [];
for (const number of unusedNumbers) {
try {
await numberProvider.deleteProvisionNumber(accountSID, authToken, number.sid);
await TwilioNumber.deleteOne({ _id: number._id });
released.push(number.phoneNumber);
} catch (error) {
console.error(`Failed to release ${number.phoneNumber}:`, error);
}
}
return released;
}
Cost Calculator
async function calculateMonthlyCost(countryCode, localCount, tollFreeCount) {
// Get pricing
const pricing = await numberProvider.getPricing(accountSID, authToken, countryCode);
// Find prices
const localPrice = parseFloat(
pricing.phoneNumberPrices.find(p => p.type === 'local')?.currentPrice || 0,
);
const tollFreePrice = parseFloat(
pricing.phoneNumberPrices.find(p => p.type === 'toll free')?.currentPrice || 0,
);
// Calculate
const localCost = localPrice * localCount;
const tollFreeCost = tollFreePrice * tollFreeCount;
const totalCost = localCost + tollFreeCost;
return {
currency: pricing.priceUnit,
localCount,
localPrice,
localCost,
tollFreeCount,
tollFreePrice,
tollFreeCost,
totalCost,
};
}
// Usage
const cost = await calculateMonthlyCost('US', 10, 2);
console.log(`Monthly cost: $${cost.totalCost}`);
// Output: Monthly cost: $14.00 (10 local @ $1 + 2 toll-free @ $2)
🚨 Error Handling
Common Errors
1. Number Already Purchased
Error: 21452 - Phone number is already owned by another account
- Cause: Number was purchased by another customer
- Solution: Search for new available numbers
2. Regulatory Compliance Required
Error: 21452 - Regulatory bundle validation failed
- Cause: Country requires regulatory bundle (e.g., US A2P)
- Solution: Create regulatory bundle first, provide bundleSid
3. Address Validation Failed
Error: 21453 - Address validation failed for compliance
- Cause: Invalid or missing address SID for compliance
- Solution: Validate address via address provider first
4. Number Not Found
Error: PHONE_NUMBERS_NOT_FOUND - No available numbers found for this country
- Cause: No numbers available for country/area code combination
- Solution: Try different area code or country
5. Invalid Country Code
Error: 21401 - Invalid Country Code
- Cause: Country code not in ISO 3166-1 alpha-2 format
- Solution: Use 2-letter codes (US, GB, CA, not USA, UK, CAN)
⚡ Performance Considerations
Search Optimization
- Limit: Twilio returns max 100 numbers per request
- Caching: Consider caching available numbers for 5-10 minutes
- Fallback: If specific area code unavailable, try nearby area codes
Purchase Flow
- Immediate: Number provisioning is synchronous (< 1 second)
- Atomic: Either succeeds completely or fails (no partial success)
- Verification: Always verify purchase success before storing in DB
🔗 Related Documentation
- Twilio Integration Overview - Main Twilio integration
- SMS Provider - SMS messaging
- Regulatory Compliance - Compliance bundles
- Address Provider - Address validation
External Resources: