Skip to main content

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:

ParameterTypeRequiredDescription
accountIDStringFriendly 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:

  1. Create Twilio Client (Master account)
  2. Create Subaccount
    • Uses accountID as friendlyName
    • Twilio generates unique SID and authToken
  3. 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:

ParameterTypeRequiredDescription
SIDStringSubaccount 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 or list subaccounts with optional filters.

Signature:

exports.search = async(searchTerms);

Parameters:

ParameterTypeRequiredDescription
searchTermsObjectSearch filters (optional)

searchTerms Object:

FieldTypeDescription
friendlyNameStringFilter by friendly name (partial match)
statusStringFilter by status (active, suspended, closed)
limitNumberMaximum results to return
pageSizeNumberPage 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:

ParameterTypeRequiredDescription
SIDStringSubaccount SID
statusStringNew status: "active", "suspended", "closed"

Returns:

Promise<TwilioAccountInstance>;

Status Values:

StatusDescription
activeAccount is active and can make/receive calls/SMS
suspendedAccount is temporarily disabled, can be reactivated
closedAccount 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:

ParameterTypeRequiredDescription
SIDStringSubaccount 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_SID and TWILIO_AUTH_TOKEN environment 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

External Resources:

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM