Twilio Subaccount Provider
🎯 Overview
The Subaccount API Provider (Providers/subaccount-api.js) manages Twilio subaccounts, enabling multi-tenant architecture where each DashClicks client has their own isolated Twilio resources.
Source: external/Integrations/Twilio/Providers/subaccount-api.js
Key Capabilities:
- Create new subaccounts
- Fetch subaccount details
- Search/list subaccounts
- Change subaccount status (active, suspended, closed)
- Close (delete) subaccounts
Multi-Tenant Benefits:
- Isolation: Each client has separate phone numbers, messages, and billing
- Security: Credentials isolated per client
- Billing: Track usage and costs per client
- Management: Suspend/close individual client accounts
🔌 Provider Functions
create()
Create a new Twilio subaccount.
Signature:
exports.create = async(accountID);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
accountID | String | ✅ | Friendly name for subaccount (typically DashClicks account ID) |
Returns:
Promise<{
sid: string; // Subaccount SID (AC...)
friendlyName: string; // Account display name
status: string; // "active"
type: string; // "Full"
authToken: string; // Unique auth token for subaccount
dateCreated: Date;
dateUpdated: Date;
ownerAccountSid: string; // Master account SID
uri: string;
}>;
Business Logic:
- Create Twilio Client (Master account)
- Create Subaccount
- Uses accountID as friendlyName
- Twilio generates unique SID and authToken
- Return Subaccount Details
- SID and authToken used for all subsequent operations
Example Usage:
const subaccountProvider = require('./Providers/subaccount-api');
// Create subaccount for DashClicks client
const subaccount = await subaccountProvider.create('65a1b2c3d4e5f6789abcdef0');
console.log('Subaccount SID:', subaccount.sid); // AC1234567890abcdef
console.log('Auth Token:', subaccount.authToken); // Unique token
console.log('Status:', subaccount.status); // active
// Store credentials for client
await Account.updateOne(
{ _id: '65a1b2c3d4e5f6789abcdef0' },
{
$set: {
twilio_account: {
sid: subaccount.sid,
authToken: subaccount.authToken,
status: subaccount.status,
createdAt: subaccount.dateCreated,
},
},
},
);
get()
Fetch details of a specific subaccount.
Signature:
exports.get = async(SID);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
SID | String | ✅ | Subaccount SID (AC...) |
Returns:
Promise<TwilioAccountInstance>;
Example Usage:
// Get subaccount details
const subaccount = await subaccountProvider.get('AC1234567890abcdef1234567890abcdef');
console.log('Name:', subaccount.friendlyName);
console.log('Status:', subaccount.status); // active, suspended, closed
console.log('Owner:', subaccount.ownerAccountSid); // Master account SID
console.log('Created:', subaccount.dateCreated);
search()
Search or list subaccounts with optional filters.
Signature:
exports.search = async(searchTerms);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
searchTerms | Object | ❌ | Search filters (optional) |
searchTerms Object:
| Field | Type | Description |
|---|---|---|
friendlyName | String | Filter by friendly name (partial match) |
status | String | Filter by status (active, suspended, closed) |
limit | Number | Maximum results to return |
pageSize | Number | Page size for pagination |
Returns:
Promise<Array<TwilioAccountInstance>>;
Example Usage:
// List all subaccounts
const subaccounts = await subaccountProvider.search({});
console.log('Total subaccounts:', subaccounts.length);
// List active subaccounts only
const activeSubaccounts = await subaccountProvider.search({ status: 'active' });
console.log('Active subaccounts:', activeSubaccounts.length);
// Search by friendly name
const clientSubaccounts = await subaccountProvider.search({
friendlyName: '65a1b2c3d4e5f6789abcdef0',
});
// List with limit
const recentSubaccounts = await subaccountProvider.search({ limit: 10 });
changeStatus()
Change the status of a subaccount.
Signature:
exports.changeStatus = async(SID, status);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
SID | String | ✅ | Subaccount SID |
status | String | ✅ | New status: "active", "suspended", "closed" |
Returns:
Promise<TwilioAccountInstance>;
Status Values:
| Status | Description |
|---|---|
active | Account is active and can make/receive calls/SMS |
suspended | Account is temporarily disabled, can be reactivated |
closed | Account is permanently closed, cannot be reactivated |
Example Usage:
// Suspend subaccount (temporary)
const suspended = await subaccountProvider.changeStatus('AC1234567890abcdef', 'suspended');
console.log('Status:', suspended.status); // suspended
// Reactivate suspended account
const reactivated = await subaccountProvider.changeStatus('AC1234567890abcdef', 'active');
console.log('Status:', reactivated.status); // active
// Close account permanently
const closed = await subaccountProvider.changeStatus('AC1234567890abcdef', 'closed');
console.log('Status:', closed.status); // closed
delete()
Close (delete) a subaccount permanently.
Signature:
exports.delete = async(SID);
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
SID | String | ✅ | Subaccount SID |
Returns:
Promise<TwilioAccountInstance>; // With status: "closed"
Business Logic:
- Internally calls
changeStatus(SID, 'closed') - Permanently closes the subaccount
- Cannot be undone - account cannot be reactivated
Example Usage:
// Delete subaccount
const deleted = await subaccountProvider.delete('AC1234567890abcdef1234567890abcdef');
console.log('Status:', deleted.status); // closed
⚠️ WARNING: This permanently closes the subaccount. All phone numbers, messages, and resources become inaccessible.
💡 Integration Examples
Client Onboarding Flow
async function onboardClientWithTwilio(accountId) {
// 1. Create Twilio subaccount
const subaccount = await subaccountProvider.create(accountId.toString());
// 2. Store credentials in DashClicks account
await Account.updateOne(
{ _id: accountId },
{
$set: {
twilio_account: {
sid: subaccount.sid,
authToken: subaccount.authToken,
status: subaccount.status,
createdAt: new Date(),
},
},
},
);
// 3. Purchase initial phone number (optional)
const numberProvider = require('./number-api');
const numbers = await numberProvider.getList(
subaccount.sid,
subaccount.authToken,
'US',
null,
{},
'local',
);
if (numbers.length > 0) {
const number = await numberProvider.addProvisionNumber(subaccount.sid, subaccount.authToken, {
phoneNumber: numbers[0].phoneNumber,
friendlyName: 'Primary Number',
smsUrl: `${process.env.API_URL}/webhooks/sms/${accountId}`,
voiceUrl: `${process.env.API_URL}/webhooks/voice/${accountId}`,
});
await Account.updateOne(
{ _id: accountId },
{ $set: { 'twilio_account.primaryNumber': number.phoneNumber } },
);
}
return {
subaccount,
accountId,
status: 'ready',
};
}
Manage Client Subscription
async function handleSubscriptionChange(accountId, subscriptionStatus) {
const account = await Account.findById(accountId);
if (!account?.twilio_account?.sid) {
throw new Error('No Twilio subaccount found');
}
if (subscriptionStatus === 'canceled') {
// Suspend Twilio subaccount
await subaccountProvider.changeStatus(account.twilio_account.sid, 'suspended');
await Account.updateOne({ _id: accountId }, { $set: { 'twilio_account.status': 'suspended' } });
console.log(`Suspended Twilio subaccount for ${accountId}`);
} else if (subscriptionStatus === 'active') {
// Reactivate Twilio subaccount
await subaccountProvider.changeStatus(account.twilio_account.sid, 'active');
await Account.updateOne({ _id: accountId }, { $set: { 'twilio_account.status': 'active' } });
console.log(`Reactivated Twilio subaccount for ${accountId}`);
}
}
Client Offboarding
async function offboardClientTwilio(accountId) {
const account = await Account.findById(accountId);
if (!account?.twilio_account?.sid) {
return { success: false, reason: 'No Twilio subaccount' };
}
// 1. Release all phone numbers
const numberProvider = require('./number-api');
const numbers = await TwilioNumber.find({ accountSid: account.twilio_account.sid });
for (const number of numbers) {
try {
await numberProvider.deleteProvisionNumber(
account.twilio_account.sid,
account.twilio_account.authToken,
number.sid,
);
await TwilioNumber.deleteOne({ _id: number._id });
} catch (error) {
console.error(`Failed to release ${number.phoneNumber}:`, error);
}
}
// 2. Close subaccount
await subaccountProvider.delete(account.twilio_account.sid);
// 3. Update DashClicks account
await Account.updateOne({ _id: accountId }, { $set: { 'twilio_account.status': 'closed' } });
return {
success: true,
numbersReleased: numbers.length,
subaccountClosed: true,
};
}
Subaccount Audit Report
async function generateSubaccountAuditReport() {
// Get all subaccounts from Twilio
const twilioSubaccounts = await subaccountProvider.search({});
// Get all DashClicks accounts with Twilio integration
const dashClicksAccounts = await Account.find({
'twilio_account.sid': { $exists: true },
}).lean();
const report = {
total: twilioSubaccounts.length,
active: 0,
suspended: 0,
closed: 0,
orphaned: [], // Twilio subaccounts with no DashClicks account
mismatched: [], // Status mismatch between Twilio and DashClicks
};
for (const twilioAccount of twilioSubaccounts) {
// Count by status
report[twilioAccount.status]++;
// Find matching DashClicks account
const dashClicksAccount = dashClicksAccounts.find(
acc => acc.twilio_account.sid === twilioAccount.sid,
);
if (!dashClicksAccount) {
report.orphaned.push({
sid: twilioAccount.sid,
friendlyName: twilioAccount.friendlyName,
status: twilioAccount.status,
});
} else if (dashClicksAccount.twilio_account.status !== twilioAccount.status) {
report.mismatched.push({
sid: twilioAccount.sid,
dashClicksStatus: dashClicksAccount.twilio_account.status,
twilioStatus: twilioAccount.status,
});
}
}
return report;
}
🚨 Error Handling
Common Errors
1. Account Not Found
Error: 20404 - The requested resource was not found
- Cause: Invalid subaccount SID
- Solution: Verify SID is correct and subaccount exists
2. Cannot Reactivate Closed Account
Error: 20003 - Cannot update closed account
- Cause: Attempting to change status of closed account
- Solution: Closed accounts cannot be reactivated, create new subaccount
3. Master Account Authentication Failed
Error: 20003 - Authentication Error
- Cause: Invalid master account credentials
- Solution: Verify
TWILIO_ACCOUNT_SIDandTWILIO_AUTH_TOKENenvironment variables
⚡ Performance Considerations
- Subaccount Limit: No hard limit, but Twilio recommends organizing large fleets
- Creation Time: Subaccount creation is fast (< 1 second)
- Status Changes: Immediate effect
- Closed Accounts: Cannot be deleted from Twilio, will always appear in list
🔗 Related Documentation
- Twilio Integration Overview - Main Twilio integration
- Account Provider - Account information
- Phone Numbers - Number management per subaccount
- SMS Provider - SMS with subaccount credentials
External Resources: