Text Torrent API Documentation
Base URL
All API requests should be made to the following base URL:
https://api.texttorrent.com
Example Complete URL:
https://api.texttorrent.com/api/v1/user/auth/me
/api/v1
HTTP Response Codes
The Text Torrent API uses standard HTTP response codes to indicate the success or failure of an API request.
Success Codes (2xx)
| Code | Status | Description | Use Case |
|---|---|---|---|
200 |
OK | The request was successful | GET, PUT, PATCH requests that successfully retrieve or update data |
201 |
Created | The resource was successfully created | POST requests that create new resources (contacts, messages, sub-accounts) |
Client Error Codes (4xx)
| Code | Status | Description | Common Causes |
|---|---|---|---|
400 |
Bad Request | The request was malformed or invalid | Missing required parameters, invalid data format, malformed JSON |
401 |
Unauthorized | Authentication failed or credentials are missing | Missing or invalid X-API-SID or X-API-PUBLIC-KEY headers |
403 |
Forbidden | Valid authentication but insufficient permissions | Trying to access resources you don't have permission for, disabled API access |
404 |
Not Found | The requested resource does not exist | Invalid endpoint URL, resource ID doesn't exist, deleted resource |
422 |
Unprocessable Entity | The request was well-formed but contains semantic errors | Validation errors, insufficient credits, business logic violations |
429 |
Too Many Requests | Rate limit exceeded | More than 60 requests per minute from the same API key |
Server Error Codes (5xx)
| Code | Status | Description | What to Do |
|---|---|---|---|
500 |
Internal Server Error | An error occurred on the server | Retry the request after a few moments. Contact support if the issue persists |
503 |
Service Unavailable | The server is temporarily unavailable | Wait and retry. The service may be under maintenance |
Response Structure
All API responses follow a consistent JSON structure:
Success Response Format:
{
"code": 200,
"success": true,
"message": "Operation completed successfully",
"data": {
// Response data here
},
"errors": null
}
Error Response Format:
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"field_name": [
"Error message for this field"
]
}
}
Common Error Scenarios
1. Insufficient Credits (422):
{
"code": 422,
"success": false,
"message": "You do not have enough credits to perform this action.",
"data": null,
"errors": null
}
2. Resource Not Found (404):
{
"code": 404,
"success": false,
"message": "Sub Account Not Found",
"data": null,
"errors": []
}
3. Trial Limitation (422):
{
"code": 422,
"success": false,
"message": "You cannot purchase numbers while on a trial subscription. Please upgrade to a paid plan.",
"data": null,
"errors": null
}
4. Rate Limit Exceeded (429):
{
"code": 429,
"success": false,
"message": "Too Many Requests. Please try again in 60 seconds.",
"data": null,
"errors": null
}
Best Practices for Error Handling
- Always check the
successfield to determine if the request was successful - Use the
codefield for programmatic error handling - Display the
messagefield to users for user-friendly error messages - Parse the
errorsobject for detailed validation error messages - Implement exponential backoff for rate limit errors (429)
- Log server errors (5xx) and alert your team for investigation
- Handle network timeouts gracefully with retry logic
Authentication
The Text Torrent API uses API Key authentication to secure all API requests.
All API requests must be authenticated using your API credentials by including X-API-SID and
X-API-PUBLIC-KEY headers. Unauthenticated requests will receive a 401 Unauthorized
response.
Rate Limiting: There is a rate limit of 60 requests per minute. If you exceed this limit,
you will receive a 429 Too Many Requests status with a Retry-After header
indicating when you can try again.
API Key Authentication
All API requests must be authenticated using two headers: X-API-SID and
X-API-PUBLIC-KEY. These credentials are unique to your account and provide secure access to the
Text Torrent API.
Finding Your API Credentials
Your API credentials (SID and Public Key) are generated automatically when your account is created. You can find them in your account settings or API dashboard.
- X-API-SID: Your Account SID (e.g.,
SID....................................) - X-API-PUBLIC-KEY: Your Public API Key (e.g.,
PK.....................................)
Making Authenticated Requests
Include both X-API-SID and X-API-PUBLIC-KEY headers in all API requests:
Request Headers:
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Example: Get User Information (/me)
The /me endpoint returns information about the authenticated user.
Endpoint: GET https://api.texttorrent.com/api/v1/user/auth/me
Example cURL Request:
curl -X GET https://api.texttorrent.com/api/v1/user/auth/me \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json"
Success Response (200 OK):
{
"code": 200,
"success": true,
"message": "Successfully fetched user information",
"data": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"username": "johndoe",
"email": "john@example.com",
"credits": 1000,
"plan": {
"package_name": "pro",
"status": "Active"
}
}
}
Example in PHP:
$sid = 'SID....................................';
$publicKey = 'PK.....................................';
$ch = curl_init('https://api.texttorrent.com/api/v1/user/auth/me');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: ' . $sid,
'X-API-PUBLIC-KEY: ' . $publicKey,
'Content-Type: application/json',
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
Example in JavaScript:
const sid = 'SID....................................';
const publicKey = 'PK.....................................';
fetch('https://api.texttorrent.com/api/v1/user/auth/me', {
method: 'GET',
headers: {
'X-API-SID': sid,
'X-API-PUBLIC-KEY': publicKey,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
API Key Best Practices
- Store your API credentials in environment variables, not in your code
- Use different API keys for development and production environments
- Never expose your API credentials in client-side JavaScript or mobile apps
- Rotate your API credentials periodically for enhanced security
- Monitor your API usage regularly for any suspicious activity
- If you suspect your credentials have been compromised, regenerate them immediately in your account settings
Authentication Error Responses
If authentication fails, the API will return appropriate HTTP status codes and error messages:
| Status Code | Error | Description |
|---|---|---|
| 401 | Unauthorized | No authentication credentials provided or invalid credentials |
| 403 | Forbidden | Valid credentials but insufficient permissions for the requested resource |
| 429 | Too Many Requests | Rate limit exceeded (60 requests per minute) |
401 Unauthorized Response Example:
{
"status": false,
"message": "Unauthorized",
"errors": []
}
429 Rate Limit Response Example:
{
"status": false,
"message": "Too Many Requests. Please try again in 60 seconds.",
"errors": []
}
403 Forbidden Response Example:
{
"status": false,
"message": "Unauthorized",
"errors": []
}
Contact Module
The Contact Module provides a comprehensive set of APIs to manage all aspects of your contacts, including list management, individual contact operations, validation, folders, notes, and more. This module is the foundation for organizing and maintaining your contact database.
Module Capabilities:
- Contact List Management: Create, organize, and manage contact lists
- Contact Operations: Add, update, delete, and search individual contacts
- Contact Validation: Verify phone numbers and clean up invalid contacts
- Folder Organization: Group contacts into custom folders
- Notes & Annotations: Add notes and track contact interactions
- Blocked List: Manage blacklisted contacts
- Opt-Out Management: Configure and track opt-out preferences
3.1 Contact List Management
The Contact List Management API provides comprehensive endpoints to organize and manage your contact lists. Contact lists help you group contacts for better organization and targeted messaging campaigns.
Key Features:
- Create, update, and delete contact lists
- Bookmark/favorite important lists for quick access
- View all lists with contact counts
- Toggle bookmark status on lists
- Delete contacts in bulk by list type
Get All Contact Lists
Retrieve all contact lists associated with your account, including the total count of non-blacklisted contacts in each list.
/api/v1/contact/list
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Query Parameters
This endpoint does not require any query parameters.
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/contact/list" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact list retrieved",
"data": [
{
"id": 45,
"user_id": 1,
"name": "VIP Customers",
"bookmarked": 1,
"created_at": "2025-01-15T10:30:00.000000Z",
"total_contacts": 150
},
{
"id": 44,
"user_id": 1,
"name": "Newsletter Subscribers",
"bookmarked": 0,
"created_at": "2025-01-14T14:22:00.000000Z",
"total_contacts": 523
},
{
"id": 43,
"user_id": 1,
"name": "Event Attendees",
"bookmarked": 0,
"created_at": "2025-01-10T09:15:00.000000Z",
"total_contacts": 89
}
],
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the contact list |
user_id |
integer | ID of the user who owns this list |
name |
string | Name of the contact list (max 15 characters) |
bookmarked |
boolean | Whether the list is bookmarked/favorited (1 = true, 0 = false) |
created_at |
string | Timestamp when the list was created (ISO 8601 format) |
total_contacts |
integer | Total number of non-blacklisted contacts in this list |
Notes
- Lists are ordered by ID in descending order (newest first)
- Only non-blacklisted contacts are counted in
total_contacts - Sub-accounts will only see their own lists
Create Contact List
Create a new contact list to organize your contacts. List names must be unique within your account and cannot exceed 15 characters.
/api/v1/contact/list/add/bookmark
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Name of the contact list (max 15 characters, must be unique) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/list/add/bookmark" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "New Leads 2025"
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact list created successfully",
"data": {
"id": 46,
"user_id": 1,
"name": "New Leads 2025",
"bookmarked": 0,
"created_at": "2025-10-17T12:34:56.000000Z",
"updated_at": "2025-10-17T12:34:56.000000Z"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"name": [
"The name field is required."
]
}
}
Common Validation Errors
- Required: "The name field is required."
- Duplicate: "The name has already been taken."
- Max Length: "The name must not be greater than 15 characters."
Update Contact List
Update the name of an existing contact list. The new name must be unique within your account and cannot exceed 15 characters.
/api/v1/contact/list/update/bookmark
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
list_id |
integer | Yes | ID of the contact list to update (must exist) |
name |
string | Yes | New name for the contact list (max 15 characters, must be unique) |
Example Request
curl -X PUT "https://api.texttorrent.com/api/v1/contact/list/update/bookmark" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"list_id": 46,
"name": "Hot Leads 2025"
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact list updated successfully",
"data": {
"id": 46,
"user_id": 1,
"name": "Hot Leads 2025",
"bookmarked": 0,
"created_at": "2025-10-17T12:34:56.000000Z",
"updated_at": "2025-10-17T13:45:22.000000Z"
},
"errors": null
}
Error Response - List Not Found (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"list_id": [
"The selected list id is invalid."
]
}
}
Common Validation Errors
- Required Fields: "The list_id field is required." / "The name field is required."
- Invalid ID: "The selected list id is invalid."
- Duplicate Name: "The name has already been taken."
- Max Length: "The name must not be greater than 15 characters."
Toggle Bookmark Status
Toggle the bookmark/favorite status of a contact list. Bookmarked lists can be displayed prominently in your application for quick access to frequently used lists.
/api/v1/contact/list/toggle/bookmark/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | ID of the contact list to toggle |
Example Request - Bookmark a List
curl -X PUT "https://api.texttorrent.com/api/v1/contact/list/toggle/bookmark/46" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact list updated successfully",
"data": {
"id": 46,
"user_id": 1,
"name": "Hot Leads 2025",
"bookmarked": 1,
"created_at": "2025-10-17T12:34:56.000000Z",
"updated_at": "2025-10-17T13:45:22.000000Z"
},
"errors": null
}
Error Response - List Not Found (200 with error)
{
"code": 400,
"success": false,
"message": "Failed to update contact list",
"data": null,
"errors": []
}
Notes
- If
bookmarkedis 0, it will be set to 1 (bookmarked) - If
bookmarkedis 1, it will be set to 0 (not bookmarked) - This is a toggle operation - calling it twice will return to the original state
Delete Contact List or Contacts
Delete a specific contact list and all its associated contacts, or delete all contacts of a specific type (default, blacklisted, or unlisted). This operation is irreversible.
/api/v1/contact/list/delete/bookmark
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
list_id |
integer | Conditional | ID of the contact list to delete (required if type is not provided) |
type |
string | Conditional | Type of contacts to delete: default, blacklisted, or
unlisted
|
Delete Types Explained
- Specific List: Provide
list_idto delete a specific list and all its contacts - default: Deletes all contacts in the default list (list_id = 0)
- blacklisted: Deletes all blacklisted contacts across all lists
- unlisted: Deletes all contacts not assigned to any list
Example 1: Delete Specific Contact List
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"list_id": 46
}'
Success Response - Specific List (200 OK)
{
"code": 200,
"success": true,
"message": "Contact list deleted successfully",
"data": null,
"errors": null
}
Example 2: Delete All Default Contacts
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"type": "default"
}'
Success Response - Default Contacts (200 OK)
{
"code": 200,
"success": true,
"message": "All default contacts deleted successfully",
"data": null,
"errors": null
}
Example 3: Delete All Blacklisted Contacts
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"type": "blacklisted"
}'
Success Response - Blacklisted Contacts (200 OK)
{
"code": 200,
"success": true,
"message": "All blacklisted contacts deleted successfully",
"data": null,
"errors": null
}
Example 4: Delete All Unlisted Contacts
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"type": "unlisted"
}'
Success Response - Unlisted Contacts (200 OK)
{
"code": 200,
"success": true,
"message": "All unlisted contacts deleted successfully",
"data": null,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"list_id": [
"The selected list id is invalid."
]
}
}
Common Validation Errors
- Invalid List ID: "The selected list id is invalid."
- Invalid Type: "The selected type is invalid." (must be: default, blacklisted, or unlisted)
Important Notes
- You must provide either
list_idORtype, but not necessarily both - When using
type = "blacklisted", contacts blacklisted by you or blacklisted by your sub-accounts will be deleted - Deleting a list permanently removes the list and all contacts associated with it
- This operation cannot be undone - consider implementing a confirmation step in your application
Get Contacts in a List
Retrieve all contacts within a specific contact list with support for pagination, search, and filtering. This endpoint is perfect for displaying contacts in your application with advanced query capabilities.
/api/v1/contact/{listId}/contacts
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
listId |
integer | Yes | ID of the contact list to retrieve contacts from |
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit |
integer | No | 10 | Number of contacts per page (pagination) |
search |
string | No | - | Search term to filter contacts by first name, last name, or phone number |
sort_list_id |
string/integer | No | - | Filter by list type: specific list ID, unlisted, or blacklisted
|
page |
integer | No | 1 | Page number for pagination |
Example 1: Basic Request
curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example 2: With Pagination
curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts?limit=25&page=2" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example 3: With Search
curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts?search=john" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example 4: Filter by Unlisted Contacts
curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts?sort_list_id=unlisted" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example 5: Filter by Blacklisted Contacts
curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts?sort_list_id=blacklisted" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contacts retrieved",
"data": {
"current_page": 1,
"data": [
{
"id": 1234,
"first_name": "John",
"last_name": "Doe",
"number": "+19292223344",
"email": "john.doe@example.com",
"company": "Tech Solutions Inc",
"valid": 1,
"validation_process": 1,
"folder_id": null
},
{
"id": 1235,
"first_name": "Jane",
"last_name": "Smith",
"number": "+19293334455",
"email": "jane.smith@example.com",
"company": "Marketing Pro",
"valid": 1,
"validation_process": 1,
"folder_id": 5
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=1",
"from": 1,
"last_page": 5,
"last_page_url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=5",
"links": [
{
"url": null,
"label": "« Previous",
"active": false
},
{
"url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=1",
"label": "1",
"active": true
},
{
"url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=2",
"label": "2",
"active": false
}
],
"next_page_url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=2",
"path": "https://api.texttorrent.com/api/v1/contact/46/contacts",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 50
},
"errors": null
}
Contact Response Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the contact |
first_name |
string | Contact's first name |
last_name |
string | Contact's last name |
number |
string | Contact's phone number |
email |
string | Contact's email address (nullable) |
company |
string | Contact's company name (nullable) |
valid |
boolean | Whether the contact's phone number is valid (1 = valid, 0 = invalid) |
validation_process |
boolean | Whether the contact has been through validation (1 = yes, 0 = no) |
folder_id |
integer | ID of the folder this contact belongs to (nullable) |
Search Behavior
- Search is case-insensitive and uses partial matching (LIKE query)
- Searches across:
first_name,last_name, andnumberfields - Example: searching "john" will match "John Doe", "Johnny", "+1234567890" if it contains "john"
Sort List ID Behavior
- Integer: Filter by specific list ID
- "unlisted": Shows only contacts without a list assignment (list_id is null)
- "blacklisted": Shows only blacklisted contacts (blacklisted = 1)
Pagination Metadata
current_page: Current page numberlast_page: Total number of pagesper_page: Number of items per pagetotal: Total number of contacts matching the queryfromandto: Range of items on current page
3.2 Contact Number Management
The Contact Number Management API provides endpoints to validate phone numbers, verify contact information, and retrieve detailed contact data. These endpoints help ensure data quality and maintain accurate contact records.
Key Features:
- Verify phone numbers before validation
- Validate phone numbers (mobile vs landline detection)
- Retrieve detailed contact information
- Credit-based validation system
- Automatic credit deduction and logging
3.2.1 Verify Numbers (Pre-validation Check)
This endpoint performs a pre-validation check to calculate how many credits will be required to validate the selected contact numbers. It identifies which contacts have already been validated and calculates the cost before proceeding with actual validation.
/api/v1/contact/number/verify/confirmation
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id |
array | Yes | Array of contact IDs to verify (each ID must exist in contacts table) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/number/verify/confirmation" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_id": [1234, 1235, 1236, 1237, 1238]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Number validation result",
"data": {
"total_numbers_selected": 5,
"already_validated": 2,
"available_validation": 3,
"credits": 997
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
total_numbers_selected |
integer | Total number of contacts selected for verification |
already_validated |
integer | Count of contacts that have already been validated |
available_validation |
integer | Count of contacts that need validation (not yet validated) |
credits |
integer | Your remaining credits after validation (each validation costs 1 credit) |
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_id": [
"The contact id field is required."
]
}
}
Common Validation Errors
- Required: "The contact id field is required."
- Invalid Format: "The contact id must be an array."
- Invalid IDs: "The selected contact id.0 is invalid." (when contact doesn't exist)
Important Notes
- Each validation costs 1 credit per contact
- Already validated contacts don't count toward credit deduction
- Use this endpoint before calling the validate endpoint to confirm costs
- Sub-accounts use their parent account's credits
- Response shows credits after deduction (what you'll have left)
3.2.2 Validate Phone Numbers
Validates selected phone numbers to verify their validity, detect mobile vs landline, and clean up your contact database. This process requires an active subscription and sufficient credits.
/api/v1/contact/number/validate
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_ids |
array | Yes | Array of contact IDs to validate (each ID must exist) |
validator_credits |
number | Yes | Total credits required for validation (minimum: 0) |
available_validation |
integer | Yes | Number of contacts available for validation (from verify endpoint) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/number/validate" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_ids": [1234, 1235, 1236],
"validator_credits": 3,
"available_validation": 3
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Number validation started successfully",
"data": {
"total_numbers_selected": 3,
"available_validation": 3,
"credits": 997
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
total_numbers_selected |
integer | Total number of contacts selected for validation |
available_validation |
integer | Number of contacts that will be validated |
credits |
integer | Your remaining credits after validation |
Error Response - Insufficient Credits (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "You do not have enough credits to perform this action.",
"data": null,
"errors": []
}
Error Response - No Active Subscription (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "You do not have an active subscription to perform this action.",
"data": null,
"errors": []
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_ids": [
"The contact ids field is required."
]
}
}
Common Validation Errors
- Required Fields: "The contact ids field is required." / "The validator credits field is required." / "The available validation field is required."
- Invalid Format: "The contact ids must be an array." / "The validator credits must be a number."
- Invalid Value: "The validator credits must be at least 0."
- Invalid IDs: "The selected contact ids.0 is invalid." (when contact doesn't exist)
Validation Process
- Pre-validation: Call
/verify/confirmationendpoint to get credit calculation - Credit Check: System checks if you have enough credits
- Auto-Recharge: If enabled and credits are low (below 1000), auto-recharge is triggered
- Validation Job: Validation is queued and processed asynchronously
- Credit Deduction: Credits are deducted immediately when validation starts
- Status Tracking: Each contact validation item is created with "Pending" status
Important Notes
- Requires an active subscription
- Each validation costs 1 credit per contact
- Sub-accounts use their parent account's credits
- Auto-recharge triggers when credits drop below 1000 (if enabled)
- Validation is processed asynchronously - results are available later
- Credit deduction is logged in the system for audit purposes
- All contacts must belong to a list (list_id cannot be null)
Workflow Example
// Step 1: Verify numbers and get cost estimate
const verifyResponse = await fetch('https://api.texttorrent.com/api/v1/contact/number/verify/confirmation', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json'
},
body: JSON.stringify({
contact_id: [1234, 1235, 1236]
})
});
const verifyData = await verifyResponse.json();
console.log('Credits after validation:', verifyData.data.credits);
console.log('Contacts to validate:', verifyData.data.available_validation);
// Step 2: If user confirms, proceed with validation
if (confirm(`Validate ${verifyData.data.available_validation} contacts for ${verifyData.data.available_validation} credits?`)) {
const validateResponse = await fetch('https://api.texttorrent.com/api/v1/contact/number/validate', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json'
},
body: JSON.stringify({
contact_ids: [1234, 1235, 1236],
validator_credits: verifyData.data.available_validation,
available_validation: verifyData.data.available_validation
})
});
const validateData = await validateResponse.json();
console.log('Validation started:', validateData.message);
}
3.2.3 Get Contact Details
Retrieve detailed information about a specific contact, including personal information, associated notes, and formatted display data. This endpoint provides a complete view of a contact's profile.
/api/v1/contact/number/{contactId}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contactId |
integer | Yes | Unique identifier of the contact to retrieve |
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/contact/number/1234" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact details retrieved successfully",
"data": {
"id": 1234,
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"imgWords": "JD",
"email": "john.doe@example.com",
"number": "+19292223344",
"company": "Tech Solutions Inc",
"notes": [
{
"id": 1,
"contact_id": 1234,
"note": "Follow up on product demo",
"created_at": "2025-10-15T10:30:00.000000Z",
"updated_at": "2025-10-15T10:30:00.000000Z"
},
{
"id": 2,
"contact_id": 1234,
"note": "Interested in enterprise plan",
"created_at": "2025-10-16T14:20:00.000000Z",
"updated_at": "2025-10-16T14:20:00.000000Z"
}
]
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the contact |
first_name |
string | Contact's first name |
last_name |
string | Contact's last name |
full_name |
string | Contact's full name (first + last) |
imgWords |
string | Initials for avatar display (first letter of first + last name) |
email |
string | Contact's email address (nullable) |
number |
string | Contact's phone number with country code |
company |
string | Contact's company name (nullable) |
notes |
array | Array of note objects associated with this contact |
Error Response - Contact Not Found (200 OK with null data)
{
"code": 200,
"success": true,
"message": "Contact not found",
"data": null,
"errors": null
}
Notes Array Structure
Each note object in the notes array contains:
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the note |
contact_id |
integer | ID of the contact this note belongs to |
note |
string | The note text content |
created_at |
string | Timestamp when note was created (ISO 8601) |
updated_at |
string | Timestamp when note was last updated (ISO 8601) |
Use Cases
- Display full contact profile in your application
- Show contact details modal/popup
- Retrieve contact information before messaging
- View contact history and associated notes
- Get avatar initials for UI display (
imgWordsfield)
Important Notes
- Returns
nulldata if contact doesn't exist (not a 404 error) imgWordsis pre-calculated for avatar display (e.g., "JD" for "John Doe")- Notes are automatically included in the response
- Phone number is returned from the
phonefield (notnumberfield in database) - Empty notes array if contact has no notes
3.3 Contact Notes
The Contact Notes API allows you to add, update, and delete notes associated with contacts. Notes help you track interactions, important information, and follow-up tasks for each contact.
Key Features:
- Add notes to any contact
- Update existing notes
- Delete notes when no longer needed
- Support for up to 2000 characters per note
- Notes are automatically included when retrieving contact details
3.3.1 Add Contact Note
Create a new note for a specific contact. Notes can contain important information, follow-up reminders, conversation summaries, or any other relevant details about the contact.
/api/v1/contact/number/note/add
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id |
integer | Yes | ID of the contact to add note to (must exist in contacts table) |
note |
string | Yes | The note content (max 2000 characters) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/number/note/add" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_id": 1234,
"note": "Follow up on product demo scheduled for next week. Customer interested in enterprise plan."
}'
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Note added successfully",
"data": {
"id": 567,
"contact_id": 1234,
"note": "Follow up on product demo scheduled for next week. Customer interested in enterprise plan.",
"created_at": "2025-10-17T14:30:22.000000Z",
"updated_at": "2025-10-17T14:30:22.000000Z"
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the note |
contact_id |
integer | ID of the contact this note belongs to |
note |
string | The note content |
created_at |
string | Timestamp when the note was created (ISO 8601 format) |
updated_at |
string | Timestamp when the note was last updated (ISO 8601 format) |
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_id": [
"The contact id field is required."
],
"note": [
"The note field is required."
]
}
}
Common Validation Errors
- Required Fields: "The contact id field is required." / "The note field is required."
- Invalid Contact: "The selected contact id is invalid." (contact doesn't exist)
- Max Length: "The note must not be greater than 2000 characters."
- Invalid Type: "The note must be a string."
Use Cases
- Record customer conversation summaries
- Add follow-up reminders
- Track customer preferences and interests
- Note important dates or deadlines
- Document customer issues or requests
Example in JavaScript
async function addContactNote(contactId, noteText) {
const response = await fetch('https://api.texttorrent.com/api/v1/contact/number/note/add', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
contact_id: contactId,
note: noteText
})
});
const data = await response.json();
if (data.success) {
console.log('Note added:', data.data);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
3.3.2 Update Contact Note
Update the content of an existing contact note. This allows you to modify or add information to previously created notes.
/api/v1/contact/number/note/update
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_note_id |
integer | Yes | ID of the note to update (must exist in contact_notes table) |
note |
string | Yes | The updated note content (max 2000 characters) |
Example Request
curl -X PUT "https://api.texttorrent.com/api/v1/contact/number/note/update" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_note_id": 567,
"note": "Product demo completed successfully. Customer confirmed interest in enterprise plan. Scheduled follow-up for contract review on Oct 25."
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Note updated successfully",
"data": null,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_note_id": [
"The selected contact note id is invalid."
]
}
}
Common Validation Errors
- Required Fields: "The contact note id field is required." / "The note field is required."
- Invalid Note ID: "The selected contact note id is invalid." (note doesn't exist)
- Max Length: "The note must not be greater than 2000 characters."
- Invalid Type: "The note must be a string."
Error Response - Update Failed (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Failed to update note",
"data": null,
"errors": []
}
Important Notes
- The response does not return the updated note data (data is null)
- To see the updated note, retrieve the contact details using
GET /contact/number/{contactId} - Only the note content can be updated; the contact_id remains unchanged
- The
updated_attimestamp is automatically updated
Example in PHP
$noteId = 567;
$updatedNote = 'Product demo completed successfully...';
$ch = curl_init('https://api.texttorrent.com/api/v1/contact/number/note/update');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Content-Type: application/json',
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'contact_note_id' => $noteId,
'note' => $updatedNote
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
echo 'Note updated successfully';
}
3.3.3 Delete Contact Note
Permanently delete a contact note. This operation cannot be undone, so use it carefully.
/api/v1/contact/number/note/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | ID of the note to delete |
Example Request
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/number/note/567" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Note deleted successfully",
"data": null,
"errors": null
}
Error Response - Note Not Found or Delete Failed (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Failed to delete note",
"data": null,
"errors": []
}
Important Notes
- The note is permanently deleted and cannot be recovered
- No validation error is returned if the note doesn't exist; instead a 400 error is returned
- Consider implementing a confirmation step in your application before deletion
- The response does not return the deleted note data
Example in JavaScript
async function deleteContactNote(noteId) {
// Show confirmation dialog
if (!confirm('Are you sure you want to delete this note?')) {
return;
}
const response = await fetch(`https://api.texttorrent.com/api/v1/contact/number/note/${noteId}`, {
method: 'DELETE',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Note deleted successfully');
// Refresh the contact details to show updated notes list
await refreshContactDetails();
} else {
console.error('Error:', data.message);
alert('Failed to delete note');
}
}
// Usage
deleteContactNote(567);
Best Practices
- Always show a confirmation dialog before deleting notes
- Consider implementing soft deletes for better data recovery options
- Log deletions for audit purposes in your application
- Refresh the contact details view after successful deletion
- Handle error cases gracefully in your UI
Notes Management Best Practices
Organizing Notes Effectively
- Use Timestamps: Include dates in your notes for time-sensitive information
- Be Specific: Add actionable details rather than vague descriptions
- Keep Updated: Regularly update notes after customer interactions
- Tag Actions: Use prefixes like "TODO:", "FOLLOW-UP:", "COMPLETED:" for clarity
Note Character Limit
Each note supports up to 2000 characters. If you need to store more information:
- Create multiple notes for the same contact
- Summarize older conversations and keep detailed notes for recent interactions
- Use the contact's company field for organizational details
Retrieving Notes
Notes are automatically included when you call the Get Contact Details endpoint
(GET /api/v1/contact/number/{contactId}). The response includes all notes associated
with the contact in the notes array.
Example: Complete Note Workflow
// 1. Add a new note
const newNote = await addContactNote(1234, 'Initial contact - interested in demo');
// 2. Later, update the note with more details
await updateContactNote(newNote.id, 'Initial contact - demo scheduled for Oct 20, 2PM EST');
// 3. Retrieve all notes for the contact
const contact = await getContactDetails(1234);
console.log('All notes:', contact.notes);
// 4. If needed, delete outdated notes
await deleteContactNote(oldNoteId);
3.4 Contact Folders
The Contact Folders API provides endpoints to organize contacts into custom folders (also called tags in some responses). Folders help you categorize and group contacts for better organization and targeted communication.
Key Features:
- Create custom folders to organize contacts
- List all folders with search functionality
- Update folder names
- Delete folders when no longer needed
- Assign contacts to folders
- Search folders by name
3.4.1 List Contact Folders
Retrieve all contact folders associated with your account. Optionally filter folders using a search query to find specific folders by name.
/api/v1/contact/number/folder/list
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search |
string | No | Search term to filter folders by name (partial match) |
Example Request - Get All Folders
curl -X GET "https://api.texttorrent.com/api/v1/contact/number/folder/list" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Search Folders
curl -X GET "https://api.texttorrent.com/api/v1/contact/number/folder/list?search=VIP" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Folders retrieved successfully",
"data": [
{
"id": 5,
"name": "VIP Clients",
"created_at": "2025-09-10T08:30:00.000000Z"
},
{
"id": 8,
"name": "Hot Leads",
"created_at": "2025-10-01T14:22:00.000000Z"
},
{
"id": 12,
"name": "Newsletter Subscribers",
"created_at": "2025-10-15T09:15:00.000000Z"
}
],
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the folder |
name |
string | Name of the folder |
created_at |
string | Timestamp when the folder was created (ISO 8601 format) |
Error Response - Failed to Retrieve (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Failed to retrieve folders",
"data": null,
"errors": []
}
Search Behavior
- Search is case-insensitive
- Performs partial matching (LIKE query with wildcards)
- Example: searching "VIP" will match "VIP Clients", "Super VIP", "VIP Members"
- Returns empty array if no folders match the search
Important Notes
- Only folders belonging to your account are returned
- Sub-accounts see only their own folders
- Folders are returned in the order they were created
- No pagination - all matching folders are returned
3.4.2 Create Contact Folder
Create a new folder to organize your contacts. Folder names can be up to 255 characters and help you categorize contacts for better management.
/api/v1/contact/number/folder/create
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
folder_name |
string | Yes | Name of the folder to create (max 255 characters) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/number/folder/create" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"folder_name": "VIP Clients"
}'
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Folder created successfully",
"data": {
"id": 15,
"name": "VIP Clients",
"user_id": 1,
"created_at": "2025-10-17T15:30:45.000000Z",
"updated_at": "2025-10-17T15:30:45.000000Z"
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the newly created folder |
name |
string | Name of the folder |
user_id |
integer | ID of the user who owns this folder |
created_at |
string | Timestamp when the folder was created (ISO 8601 format) |
updated_at |
string | Timestamp when the folder was last updated (ISO 8601 format) |
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"folder_name": [
"The folder name field is required."
]
}
}
Common Validation Errors
- Required: "The folder name field is required."
- Max Length: "The folder name must not be greater than 255 characters."
- Invalid Type: "The folder name must be a string."
Error Response - Creation Failed (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Failed to create folder",
"data": null,
"errors": []
}
Example in JavaScript
async function createFolder(folderName) {
const response = await fetch('https://api.texttorrent.com/api/v1/contact/number/folder/create', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
folder_name: folderName
})
});
const data = await response.json();
if (data.success) {
console.log('Folder created:', data.data);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
createFolder('VIP Clients');
3.4.3 Update Contact Folder
Update the name of an existing contact folder. Note that folder responses may show "Tag" in messages (folders and tags are used interchangeably in the system).
/api/v1/contact/number/folder/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | ID of the folder to update |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
folder_name |
string | Yes | New name for the folder (max 255 characters) |
Example Request
curl -X PUT "https://api.texttorrent.com/api/v1/contact/number/folder/15" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"folder_name": "Premium VIP Clients"
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Tag updated successfully",
"data": {
"id": 15,
"name": "Premium VIP Clients",
"user_id": 1,
"created_at": "2025-10-17T15:30:45.000000Z",
"updated_at": "2025-10-17T16:20:30.000000Z"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"folder_name": [
"The folder name field is required."
]
}
}
Error Response - Update Failed (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Failed to update folder",
"data": null,
"errors": []
}
Important Notes
- The response message says "Tag updated successfully" (folders/tags are interchangeable)
- Returns the complete updated folder object
- The
updated_attimestamp is automatically updated - If folder doesn't exist, returns 400 error (not 404)
3.4.4 Delete Contact Folder
Permanently delete a contact folder. Contacts assigned to this folder will not be deleted, but their folder assignment will be removed.
/api/v1/contact/number/folder/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | ID of the folder to delete |
Example Request
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/number/folder/15" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Tag deleted successfully",
"data": null,
"errors": null
}
Error Response - Delete Failed (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Failed to delete tag",
"data": null,
"errors": []
}
Important Notes
- The response message says "Tag deleted successfully" (folders/tags are interchangeable)
- Deleting a folder does NOT delete contacts assigned to it
- Contacts in the deleted folder will have their
folder_idset to null - The operation is permanent and cannot be undone
- Returns 400 error if folder doesn't exist or deletion fails
Example in JavaScript
async function deleteFolder(folderId) {
if (!confirm('Are you sure you want to delete this folder? Contacts will not be deleted.')) {
return;
}
const response = await fetch(`https://api.texttorrent.com/api/v1/contact/number/folder/${folderId}`, {
method: 'DELETE',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Folder deleted successfully');
// Refresh folder list
await loadFolders();
} else {
console.error('Error:', data.message);
alert('Failed to delete folder');
}
}
// Usage
deleteFolder(15);
3.4.5 Assign Contact to Folder
Assign a contact to a specific folder for better organization. You can also use this endpoint to move a contact from one folder to another by providing a different folder ID.
/api/v1/contact/add/folder
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id |
integer | Yes | ID of the contact to assign (must exist in contacts table) |
folder_id |
integer | Yes | ID of the folder to assign contact to (must exist in contact_folders table) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/add/folder" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_id": 1234,
"folder_id": 15
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact added to folder successfully",
"data": {
"id": 1234,
"first_name": "John",
"last_name": "Doe",
"number": "+19292223344",
"email": "john.doe@example.com",
"company": "Tech Solutions Inc",
"list_id": 45,
"folder_id": 15,
"user_id": 1,
"blacklisted": 0,
"valid": 1,
"validation_process": 1,
"created_at": "2025-09-15T10:30:00.000000Z",
"updated_at": "2025-10-17T16:45:22.000000Z"
},
"errors": null
}
Response Fields
Returns the complete updated contact object with all fields including the new folder_id.
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_id": [
"The selected contact id is invalid."
],
"folder_id": [
"The selected folder id is invalid."
]
}
}
Common Validation Errors
- Required Fields: "The contact id field is required." / "The folder id field is required."
- Invalid Contact: "The selected contact id is invalid." (contact doesn't exist)
- Invalid Folder: "The selected folder id is invalid." (folder doesn't exist)
Error Response - Assignment Failed (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Contact removed from folder successfully",
"data": null,
"errors": []
}
Note: The error message says "Contact removed from folder successfully" even though it's an error response. This appears to be a bug in the implementation.
Use Cases
- Organize new contacts into appropriate folders
- Move contacts between folders (change folder_id)
- Tag contacts for specific campaigns
- Group contacts by category, status, or priority
Important Notes
- A contact can only be in one folder at a time
- Assigning a contact to a new folder automatically removes it from the previous folder
- Both contact and folder must exist and belong to your account
- Returns the complete updated contact object
- The contact's
updated_attimestamp is automatically updated
Example in PHP
function assignContactToFolder($contactId, $folderId) {
$ch = curl_init('https://api.texttorrent.com/api/v1/contact/add/folder');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Content-Type: application/json',
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'contact_id' => $contactId,
'folder_id' => $folderId
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
echo 'Contact assigned to folder successfully';
return $data['data'];
} else {
echo 'Error: ' . $data['message'];
return null;
}
}
// Usage
assignContactToFolder(1234, 15);
Folder Management Best Practices
Organizing with Folders
- Use Descriptive Names: Choose clear, meaningful folder names like "Hot Leads Q4" instead of "Folder 1"
- Keep It Simple: Don't create too many folders - aim for 5-10 main categories
- Combine with Lists: Use both lists and folders for multi-level organization
- Regular Cleanup: Delete unused folders to keep your workspace organized
Folder vs List
When to use Folders:
- Sub-categorization within a list (e.g., "VIP", "Premium", "Standard" within "Customers" list)
- Temporary groupings (e.g., "October Campaign", "Webinar Attendees")
- Status-based organization (e.g., "Active", "Inactive", "Follow-up Needed")
When to use Lists:
- Main categorization of contacts (e.g., "Customers", "Prospects", "Partners")
- Long-term organization
- Campaign targets
Common Folder Organization Patterns
List: Customers
├── Folder: VIP Clients
├── Folder: Regular Customers
└── Folder: Churned Customers
List: Leads
├── Folder: Hot Leads
├── Folder: Warm Leads
└── Folder: Cold Leads
List: Event Contacts
├── Folder: 2025 Q1 Webinar
├── Folder: 2025 Q2 Conference
└── Folder: 2025 Q3 Workshop
Workflow Example
// Complete folder workflow
async function organizeFolderWorkflow() {
// 1. Create folders for organization
const vipFolder = await createFolder('VIP Clients');
const hotLeadsFolder = await createFolder('Hot Leads');
// 2. List all folders to verify
const allFolders = await listFolders();
console.log('All folders:', allFolders);
// 3. Assign contacts to folders
await assignContactToFolder(1234, vipFolder.id);
await assignContactToFolder(1235, hotLeadsFolder.id);
// 4. Search for specific folder
const vipFolders = await listFolders('VIP');
console.log('VIP folders:', vipFolders);
// 5. Update folder name if needed
await updateFolder(vipFolder.id, 'Premium VIP Clients');
// 6. Clean up unused folders
// await deleteFolder(oldFolderId);
}
3.5 Contact CRUD Operations
The Contact CRUD (Create, Read, Update, Delete) API provides comprehensive endpoints to manage individual contacts in your account. These endpoints allow you to add new contacts, update existing contact information, delete contacts, and import contacts in bulk from CSV files.
Key Features:
- Add individual contacts with detailed information
- Update contact details (name, phone, email, company)
- Bulk delete multiple contacts at once
- Import contacts from CSV files with custom column mapping
- Support for blacklisted and unlisted contacts
- List size limit: 10,000 contacts per list
- Automatic US phone number formatting (+1 prefix)
3.5.1 Add Contact
Create a new contact in your account. Contacts can be added to a specific list, marked as blacklisted, or kept as unlisted. Phone numbers must be valid North American numbers and will be automatically formatted with the +1 prefix.
/api/v1/contact/add
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
first_name |
string | Yes | Contact's first name (max 50 characters) |
last_name |
string | No | Contact's last name (max 50 characters) |
number |
string | Yes | Phone number (7-14 digits, automatically prefixed with +1) |
email |
string | No | Email address (max 100 characters, must be valid email format) |
company |
string | No | Company name (max 100 characters) |
list_id |
integer | Yes | ID of the contact list (must exist in contact_lists table) |
type |
string | No | Contact type: default, blacklisted, or unlisted |
Contact Types
- default: Normal contact added to the specified list (default behavior)
- blacklisted: Contact is blacklisted and won't receive messages (list_id is ignored)
- unlisted: Contact exists but is not assigned to any list (list_id is ignored)
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/add" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"first_name": "John",
"last_name": "Doe",
"number": "2025551234",
"email": "john.doe@example.com",
"company": "Tech Solutions Inc",
"list_id": 45,
"type": "default"
}'
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Contact added successfully",
"data": {
"id": 5678,
"first_name": "John",
"last_name": "Doe",
"number": "+12025551234",
"email": "john.doe@example.com",
"company": "Tech Solutions Inc",
"list_id": 45,
"folder_id": null,
"user_id": 1,
"blacklisted": 0,
"valid": 0,
"validation_process": 0,
"created_at": "2025-10-17T18:30:45.000000Z",
"updated_at": "2025-10-17T18:30:45.000000Z"
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the contact |
number |
string | Phone number with +1 prefix automatically added |
list_id |
integer|null | ID of the list (null for blacklisted/unlisted contacts) |
blacklisted |
integer | 1 if blacklisted, 0 otherwise |
valid |
integer | 1 if number validated, 0 otherwise |
validation_process |
integer | 1 if validation completed, 0 otherwise |
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"first_name": [
"The first name field is required."
],
"number": [
"The number field is required.",
"The number format is invalid."
],
"list_id": [
"The selected list id is invalid."
]
}
}
Common Validation Errors
- Required Fields: "The first name field is required." / "The number field is required." / "The list id field is required."
- Invalid Format: "The number format is invalid." (must match phone number regex)
- Invalid List: "The selected list id is invalid." (list doesn't exist)
- Max Length: "The first name must not be greater than 50 characters."
- Email Format: "The email must be a valid email address."
- Invalid Type: "The selected type is invalid." (must be default, blacklisted, or unlisted)
Error Response - List Full (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "List can't have more than 10000 contacts.",
"data": null,
"errors": null
}
Error Response - Failed to Add (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Failed to add contact",
"data": null,
"errors": []
}
Example in JavaScript
async function addContact(contactData) {
const response = await fetch('https://api.texttorrent.com/api/v1/contact/add', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
first_name: contactData.firstName,
last_name: contactData.lastName,
number: contactData.phoneNumber,
email: contactData.email,
company: contactData.company,
list_id: contactData.listId,
type: contactData.type || 'default'
})
});
const data = await response.json();
if (data.success) {
console.log('Contact added:', data.data);
return data.data;
} else {
console.error('Error:', data.message);
if (data.errors) {
Object.entries(data.errors).forEach(([field, messages]) => {
console.error(`${field}:`, messages.join(', '));
});
}
throw new Error(data.message);
}
}
// Usage
addContact({
firstName: 'John',
lastName: 'Doe',
phoneNumber: '2025551234',
email: 'john.doe@example.com',
company: 'Tech Solutions Inc',
listId: 45
});
Important Notes
- Phone numbers are automatically prefixed with "+1" - do not include it in your request
- Each list is limited to 10,000 contacts maximum
- Blacklisted contacts ignore the list_id parameter and set blacklisted flag to 1
- Unlisted contacts ignore the list_id parameter but don't set blacklisted flag
- Phone number must match regex:
^\+?[0-9]{1,4}?[0-9]{7,14}$ - New contacts have valid=0 and validation_process=0 by default
3.5.2 Update Contact
Update an existing contact's information. You can modify the contact's name, phone number, email, and company details. The phone number will be automatically formatted with the +1 prefix.
/api/v1/contact/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | ID of the contact to update |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
first_name |
string | Yes | Contact's first name (max 50 characters) |
last_name |
string | No | Contact's last name (max 50 characters) |
mobile_number |
string | Yes | Phone number (max 20 characters, automatically prefixed with +1) |
email |
string | No | Email address (max 100 characters, must be valid email format) |
company |
string | No | Company name (max 100 characters) |
Example Request
curl -X PUT "https://api.texttorrent.com/api/v1/contact/5678" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"first_name": "John",
"last_name": "Smith",
"mobile_number": "2025559999",
"email": "john.smith@newcompany.com",
"company": "New Tech Corp"
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact updated successfully",
"data": {
"id": 5678,
"first_name": "John",
"last_name": "Smith",
"number": "+12025559999",
"email": "john.smith@newcompany.com",
"company": "New Tech Corp",
"list_id": 45,
"folder_id": null,
"user_id": 1,
"blacklisted": 0,
"valid": 0,
"validation_process": 0,
"created_at": "2025-10-17T18:30:45.000000Z",
"updated_at": "2025-10-17T19:15:22.000000Z"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"first_name": [
"The first name field is required."
],
"mobile_number": [
"The mobile number field is required."
],
"email": [
"The email must be a valid email address."
]
}
}
Error Response - Update Failed (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Failed to update contact",
"data": null,
"errors": []
}
Important Notes
- Field name is
mobile_number(notnumberlike in create) - Phone number automatically gets +1 prefix - do not include it in your request
- Only provided optional fields (last_name, email, company) are updated
- If optional fields are not provided, they remain unchanged
- The
updated_attimestamp is automatically updated - Returns the complete updated contact object
Example in PHP
function updateContact($contactId, $contactData) {
$ch = curl_init('https://api.texttorrent.com/api/v1/contact/' . $contactId);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Content-Type: application/json',
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'first_name' => $contactData['first_name'],
'last_name' => $contactData['last_name'] ?? null,
'mobile_number' => $contactData['mobile_number'],
'email' => $contactData['email'] ?? null,
'company' => $contactData['company'] ?? null
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
echo 'Contact updated successfully';
return $data['data'];
} else {
echo 'Error: ' . $data['message'];
return null;
}
}
// Usage
updateContact(5678, [
'first_name' => 'John',
'last_name' => 'Smith',
'mobile_number' => '2025559999',
'email' => 'john.smith@newcompany.com',
'company' => 'New Tech Corp'
]);
3.5.3 Delete Contacts
Delete one or more contacts from your account. This endpoint supports bulk deletion by accepting an array of contact IDs. Deleted contacts are permanently removed and cannot be recovered.
/api/v1/contact/
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_ids |
array | Yes | Array of contact IDs to delete (all must exist in contacts table) |
Example Request - Delete Single Contact
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_ids": [5678]
}'
Example Request - Bulk Delete
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_ids": [5678, 5679, 5680, 5681]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contacts deleted successfully",
"data": null,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_ids": [
"The contact ids field is required.",
"The contact ids must be an array."
],
"contact_ids.0": [
"The selected contact ids.0 is invalid."
]
}
}
Common Validation Errors
- Required: "The contact ids field is required."
- Invalid Type: "The contact ids must be an array."
- Invalid ID: "The selected contact ids.X is invalid." (contact doesn't exist)
- Invalid Format: Each element must be an integer
Error Response - Delete Failed (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Failed to delete contacts",
"data": null,
"errors": []
}
Example in JavaScript
async function deleteContacts(contactIds) {
// Confirm deletion
if (!confirm(`Are you sure you want to delete ${contactIds.length} contact(s)? This cannot be undone.`)) {
return;
}
const response = await fetch('https://api.texttorrent.com/api/v1/contact/', {
method: 'DELETE',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
contact_ids: contactIds
})
});
const data = await response.json();
if (data.success) {
console.log('Contacts deleted:', contactIds.length);
// Refresh contact list
await loadContacts();
} else {
console.error('Error:', data.message);
alert('Failed to delete contacts');
}
}
// Usage - Delete single contact
deleteContacts([5678]);
// Usage - Bulk delete
deleteContacts([5678, 5679, 5680, 5681]);
Important Notes
- Supports bulk deletion - provide multiple contact IDs in the array
- All provided contact IDs must exist and belong to your account
- Deletion is permanent and cannot be undone
- Associated data (notes, folder assignments) will also be removed
- If any contact ID is invalid, the entire operation fails
- No partial deletions - it's all or nothing
3.5.4 Import Contacts from CSV
Import contacts in bulk from CSV files. This endpoint supports custom column mapping, allowing you to specify which CSV columns contain first name, last name, phone number, email, company, and up to 3 additional custom fields. The import process is chunked for large files.
/api/v1/contact/import
GET /api/v1/contact/sample/download to see the expected format.
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: multipart/form-data
Accept: application/json
Request Parameters (multipart/form-data)
| Parameter | Type | Required | Description |
|---|---|---|---|
chunks[] |
file[] | Yes | Array of CSV file chunks (for large file uploads) |
file_names[] |
string[] | Yes | Array of file names corresponding to each chunk (max 255 chars each) |
total_records |
integer | Yes | Total number of records in the CSV file |
file_size |
numeric | Yes | Total file size in bytes |
list_id |
integer | No | ID of existing list to import contacts into (creates new list if omitted) |
first_name_column |
string | Yes | CSV column name containing first names (max 255 chars) |
last_name_column |
string | No | CSV column name containing last names (max 255 chars) |
email_address_column |
string | No | CSV column name containing email addresses (max 255 chars) |
company_column |
string | No | CSV column name containing company names (max 255 chars) |
phone_number_column |
string | Yes | CSV column name containing phone numbers (max 255 chars) |
additional_1_column |
string | No | CSV column name for additional field 1 (max 255 chars) |
additional_2_column |
string | No | CSV column name for additional field 2 (max 255 chars) |
additional_3_column |
string | No | CSV column name for additional field 3 (max 255 chars) |
List Creation Behavior
- If
list_idis provided and valid, contacts are added to that list - If
list_idis omitted, a new list is created using the filename (without extension) - If a list with the same name exists, "_X" is appended (where X is incremented ID)
- Example: "customers.csv" creates list "customers", or "customers_46" if "customers" exists
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/import" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-F "chunks[]=@contacts.csv" \
-F "file_names[]=contacts.csv" \
-F "total_records=150" \
-F "file_size=25600" \
-F "list_id=45" \
-F "first_name_column=First Name" \
-F "last_name_column=Last Name" \
-F "email_address_column=Email" \
-F "company_column=Company" \
-F "phone_number_column=Phone"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contacts imported successfully",
"data": {
"imported_count": 150,
"list_id": 45,
"list_name": "Customers"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"chunks": [
"The chunks field is required.",
"The chunks must be an array."
],
"first_name_column": [
"The first name column field is required."
],
"phone_number_column": [
"The phone number column field is required."
],
"list_id": [
"The selected list id is invalid."
]
}
}
Download Sample CSV
Get a sample CSV file to understand the expected format:
curl -X GET "https://api.texttorrent.com/api/v1/contact/sample/download" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Sample Download Response
{
"code": 200,
"success": true,
"message": "Sample file download",
"data": {
"url": "https://api.texttorrent.com/files/Sample Contact File.csv"
},
"errors": null
}
Example in JavaScript with File Upload
async function importContacts(file, columnMapping, listId = null) {
const formData = new FormData();
// Add file chunks (simplified - single file in this example)
formData.append('chunks[]', file);
formData.append('file_names[]', file.name);
formData.append('total_records', 100); // Calculate from CSV
formData.append('file_size', file.size);
// Add list ID if provided
if (listId) {
formData.append('list_id', listId);
}
// Add column mappings
formData.append('first_name_column', columnMapping.firstName);
formData.append('phone_number_column', columnMapping.phoneNumber);
// Optional columns
if (columnMapping.lastName) {
formData.append('last_name_column', columnMapping.lastName);
}
if (columnMapping.email) {
formData.append('email_address_column', columnMapping.email);
}
if (columnMapping.company) {
formData.append('company_column', columnMapping.company);
}
const response = await fetch('https://api.texttorrent.com/api/v1/contact/import', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
// Note: Don't set Content-Type for FormData, browser sets it automatically
},
body: formData
});
const data = await response.json();
if (data.success) {
console.log('Import successful:', data.data);
alert(`Successfully imported ${data.data.imported_count} contacts`);
return data.data;
} else {
console.error('Import failed:', data.message);
throw new Error(data.message);
}
}
// Usage with file input
document.getElementById('csvFile').addEventListener('change', (e) => {
const file = e.target.files[0];
importContacts(file, {
firstName: 'First Name',
lastName: 'Last Name',
phoneNumber: 'Phone',
email: 'Email',
company: 'Company'
}, 45);
});
Important Notes
- CSV files can be chunked for large uploads (send as array of chunks)
- Column names are case-sensitive and must match CSV headers exactly
- Only
first_name_columnandphone_number_columnare required - If no
list_idprovided, creates new list from filename - Supports up to 3 additional custom columns beyond standard fields
- Each list is limited to 10,000 contacts maximum
- Phone numbers are automatically formatted with +1 prefix
- Use
multipart/form-datacontent type for file uploads
CSV Column Mapping Tips
- Map exactly to your CSV column headers (case-sensitive)
- Example: If CSV has "First Name", use
first_name_column: "First Name" - Skip optional columns if they don't exist in your CSV
- Additional columns can store custom data (address, notes, etc.)
Contact CRUD Best Practices
Data Validation
- Phone Numbers: Always validate phone numbers client-side before sending
- Duplicate Prevention: Check for existing contacts before adding (by phone/email)
- List Limits: Check list size before adding contacts (10,000 max)
- Required Fields: Always provide first_name and number/mobile_number
Bulk Operations
- Use import endpoint for adding 50+ contacts (more efficient than individual adds)
- Use bulk delete endpoint to remove multiple contacts at once
- Implement progress indicators for long-running imports
- Handle partial failures gracefully (validate all IDs before bulk delete)
Import Optimization
- Validate CSV format client-side before uploading
- Show preview of column mappings to user before import
- Chunk large CSV files (10,000+ rows) for better performance
- Provide clear error messages when column mapping fails
Complete Workflow Example
// Complete contact management workflow
class ContactManager {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1';
}
async addContact(contact) {
// Validate before adding
if (!this.validatePhone(contact.number)) {
throw new Error('Invalid phone number format');
}
// Check list size
const listSize = await this.getListSize(contact.list_id);
if (listSize >= 10000) {
throw new Error('List is full (10,000 contacts max)');
}
// Add contact
return await this.apiCall('POST', '/contact/add', contact);
}
async updateContact(id, updates) {
return await this.apiCall('PUT', `/contact/${id}`, updates);
}
async deleteContacts(contactIds) {
if (!confirm(`Delete ${contactIds.length} contact(s)?`)) {
return;
}
return await this.apiCall('DELETE', '/contact/', { contact_ids: contactIds });
}
async importFromCSV(file, mapping, listId) {
const formData = new FormData();
formData.append('chunks[]', file);
formData.append('file_names[]', file.name);
formData.append('total_records', await this.countCSVRows(file));
formData.append('file_size', file.size);
if (listId) formData.append('list_id', listId);
Object.entries(mapping).forEach(([key, value]) => {
if (value) formData.append(key, value);
});
return await this.apiCall('POST', '/contact/import', formData, true);
}
validatePhone(number) {
return /^[0-9]{10}$/.test(number);
}
async getListSize(listId) {
const result = await this.apiCall('GET', `/contact/${listId}/contacts`);
return result.data.total || 0;
}
async countCSVRows(file) {
const text = await file.text();
return text.split('\n').length - 1; // Exclude header
}
async apiCall(method, endpoint, data = null, isFormData = false) {
const headers = {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
};
if (!isFormData) {
headers['Content-Type'] = 'application/json';
}
const options = {
method,
headers,
body: data ? (isFormData ? data : JSON.stringify(data)) : null
};
const response = await fetch(this.baseUrl + endpoint, options);
return await response.json();
}
}
// Usage
const manager = new ContactManager('SID....................................', 'PK.....................................');
// Add contact
await manager.addContact({
first_name: 'John',
last_name: 'Doe',
number: '2025551234',
email: 'john@example.com',
list_id: 45
});
// Update contact
await manager.updateContact(5678, {
first_name: 'John',
mobile_number: '2025559999',
email: 'john.new@example.com'
});
// Delete contacts
await manager.deleteContacts([5678, 5679]);
// Import from CSV
const fileInput = document.getElementById('csvFile');
await manager.importFromCSV(fileInput.files[0], {
first_name_column: 'First Name',
phone_number_column: 'Phone',
email_address_column: 'Email'
}, 45);
3.6 Contact Validator
The Contact Validator API allows you to view and manage phone number validation results. After validating numbers (using the validation endpoint in section 3.2.2), you can retrieve validation history, cleanup invalid numbers from lists, export validation results, and delete validation records.
Key Features:
- View validation history with detailed results
- Search validations by list name
- Process cleanup to remove non-mobile numbers (landline, invalid, etc.)
- Export validation results to Excel (.xlsx)
- Delete validation records and associated data
- Pagination support for large result sets
- Includes sub-account validation records
3.6.1 Get Validation History
Retrieve a paginated list of all phone number validation records for your account, including validation statistics such as total numbers, mobile numbers, landline numbers, invalid numbers, and credits used. Results include validations from both your account and any sub-accounts.
/api/v1/contact/validator/
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search |
string | No | Search term to filter by list name (partial match) |
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
Example Request - Get All Validations
curl -X GET "https://api.texttorrent.com/api/v1/contact/validator/" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - With Pagination and Search
curl -X GET "https://api.texttorrent.com/api/v1/contact/validator/?search=VIP&limit=20&page=1" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Validator Credits",
"data": {
"current_page": 1,
"data": [
{
"id": 123,
"list_name": "VIP Clients",
"total_credits": 150,
"total_number": 150,
"total_mobile_numbers": 120,
"total_landline_numbers": 25,
"total_other_numbers": 5,
"invalid_numbers": 0,
"created_at": "2025-10-15T14:30:00.000000Z",
"status": "Completed",
"cleaned": 0
},
{
"id": 122,
"list_name": "Hot Leads",
"total_credits": 200,
"total_number": 200,
"total_mobile_numbers": 185,
"total_landline_numbers": 10,
"total_other_numbers": 3,
"invalid_numbers": 2,
"created_at": "2025-10-14T10:15:00.000000Z",
"status": "Completed",
"cleaned": 1
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/contact/validator?page=1",
"from": 1,
"last_page": 5,
"last_page_url": "https://api.texttorrent.com/api/v1/contact/validator?page=5",
"next_page_url": "https://api.texttorrent.com/api/v1/contact/validator?page=2",
"path": "https://api.texttorrent.com/api/v1/contact/validator",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 48
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the validation record |
list_name |
string | Name of the contact list that was validated |
total_credits |
integer | Number of credits used for this validation |
total_number |
integer | Total number of phone numbers validated |
total_mobile_numbers |
integer | Count of valid mobile phone numbers |
total_landline_numbers |
integer | Count of landline phone numbers |
total_other_numbers |
integer | Count of other number types (VoIP, toll-free, etc.) |
invalid_numbers |
integer | Count of invalid or unverifiable numbers |
status |
string | Validation status (e.g., "Completed", "Processing") |
cleaned |
integer | 1 if cleanup processed (non-mobile numbers removed), 0 otherwise |
created_at |
string | Timestamp when validation was created (ISO 8601 format) |
Pagination Fields
- current_page: Current page number
- per_page: Number of items per page
- total: Total number of validation records
- last_page: Last available page number
- next_page_url: URL for next page (null if on last page)
- prev_page_url: URL for previous page (null if on first page)
Example in JavaScript
async function getValidationHistory(page = 1, limit = 10, search = '') {
const params = new URLSearchParams({
page: page,
limit: limit
});
if (search) {
params.append('search', search);
}
const response = await fetch(`https://api.texttorrent.com/api/v1/contact/validator/?${params}`, {
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Validation history:', data.data.data);
console.log(`Page ${data.data.current_page} of ${data.data.last_page}`);
console.log(`Total validations: ${data.data.total}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get first page
getValidationHistory();
// Usage - Search with pagination
getValidationHistory(1, 20, 'VIP');
Important Notes
- Results include validations from your account and all sub-accounts
- Search filters by list name (case-insensitive, partial match)
- Results are ordered by ID in descending order (newest first)
- Use
cleanedfield to identify if cleanup has been processed - Credits are consumed 1:1 (1 credit per number validated)
3.6.2 Process Validation Cleanup
Process cleanup for a validation record to remove all non-mobile numbers (landline, invalid, and other number types) from the contact list. This helps maintain a clean list of valid mobile numbers for SMS campaigns.
/api/v1/contact/validator/process-cleanup/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | ID of the validation record to cleanup |
What Gets Deleted
- Landline numbers: Traditional landline phone numbers
- VoIP numbers: Internet-based phone numbers
- Toll-free numbers: 800, 888, 877, etc.
- Invalid numbers: Numbers that failed validation
- Other types: Any non-mobile line types
What Gets Kept
- Mobile numbers: Valid mobile/cell phone numbers
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/validator/process-cleanup/123" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Cleanup Successful",
"data": null,
"errors": null
}
Example in JavaScript
async function processCleanup(validationId) {
// Confirm cleanup action
const confirmed = confirm(
'This will permanently delete all non-mobile numbers from the list. ' +
'Only mobile numbers will remain. Continue?'
);
if (!confirmed) {
return;
}
const response = await fetch(
`https://api.texttorrent.com/api/v1/contact/validator/process-cleanup/${validationId}`,
{
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Cleanup completed successfully');
alert('Non-mobile numbers have been removed from the list');
// Refresh validation history
await getValidationHistory();
} else {
console.error('Cleanup failed:', data.message);
alert('Failed to process cleanup');
}
}
// Usage
processCleanup(123);
Important Notes
- Cleanup is permanent and cannot be undone
- After cleanup, the validation record is marked as
cleaned: 1 - Only completed validations can be cleaned up
- Contacts with non-mobile numbers are permanently deleted from the database
- Associated validation items are also deleted
- Mobile numbers remain untouched in the list
Use Cases
- Preparing lists for SMS campaigns (mobile-only)
- Removing landlines after validation
- Cleaning up invalid numbers from imported data
- Ensuring compliance with SMS-only marketing
3.6.3 Export Validation Results
Export validation results to an Excel (.xlsx) file. You can export one or multiple validation records at once. The export includes detailed information about each validated number, including line type, carrier, and validation status.
/api/v1/contact/validator/export
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
validation_ids |
array | Yes | Array of validation IDs to export (all must exist in number_validations table) |
Example Request - Export Single Validation
curl -X POST "https://api.texttorrent.com/api/v1/contact/validator/export" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"validation_ids": [123]
}'
Example Request - Export Multiple Validations
curl -X POST "https://api.texttorrent.com/api/v1/contact/validator/export" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"validation_ids": [123, 124, 125]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Exported successfully",
"data": {
"url": "https://your-storage.digitaloceanspaces.com/validation/validation_export_1729180845.xlsx"
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
url |
string | Direct download URL for the exported Excel file |
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"validation_ids": [
"The validation ids field is required.",
"The validation ids must be an array."
],
"validation_ids.0": [
"The selected validation ids.0 is invalid."
]
}
}
Common Validation Errors
- Required: "The validation ids field is required."
- Invalid Type: "The validation ids must be an array."
- Invalid ID: "The selected validation ids.X is invalid." (validation doesn't exist)
- Invalid Format: Each element must be an integer
Example in JavaScript
async function exportValidations(validationIds) {
const response = await fetch('https://api.texttorrent.com/api/v1/contact/validator/export', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
validation_ids: validationIds
})
});
const data = await response.json();
if (data.success) {
console.log('Export successful, downloading...');
// Trigger download
window.open(data.data.url, '_blank');
return data.data.url;
} else {
console.error('Export failed:', data.message);
throw new Error(data.message);
}
}
// Usage - Export single validation
exportValidations([123]);
// Usage - Export multiple validations
exportValidations([123, 124, 125]);
Example in PHP
function exportValidations($validationIds) {
$ch = curl_init('https://api.texttorrent.com/api/v1/contact/validator/export');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Content-Type: application/json',
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'validation_ids' => $validationIds
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
echo 'Download URL: ' . $data['data']['url'];
return $data['data']['url'];
} else {
echo 'Error: ' . $data['message'];
return null;
}
}
// Usage
exportValidations([123, 124, 125]);
Export File Contents
The exported Excel file typically includes:
- Contact name (first name, last name)
- Phone number
- Line type (Mobile, Landline, VoIP, etc.)
- Carrier information
- Validation status
- Email address (if available)
- Company (if available)
Important Notes
- File is stored on DigitalOcean Spaces (cloud storage)
- Download URL is publicly accessible - use immediately
- File name includes timestamp:
validation_export_{timestamp}.xlsx - Supports bulk export of multiple validation records
- All validation IDs must exist and belong to your account
- If any validation ID is invalid, the entire operation fails
3.6.4 Delete Validation Record
Permanently delete a validation record and all associated validation item data. This removes the validation history but does not affect the contacts in the list (unless you've already processed cleanup).
/api/v1/contact/validator/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | ID of the validation record to delete |
What Gets Deleted
- Validation record from
number_validationstable - All associated validation items from
number_validation_itemstable - Detailed results for each validated number
What Does NOT Get Deleted
- Contacts in the list (they remain unchanged)
- The contact list itself
Example Request
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/validator/123" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Validation deleted successfully",
"data": null,
"errors": null
}
Example in JavaScript
async function deleteValidation(validationId) {
// Confirm deletion
if (!confirm('Delete this validation record? This cannot be undone.')) {
return;
}
const response = await fetch(
`https://api.texttorrent.com/api/v1/contact/validator/${validationId}`,
{
method: 'DELETE',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Validation record deleted');
alert('Validation record deleted successfully');
// Refresh validation history
await getValidationHistory();
} else {
console.error('Delete failed:', data.message);
alert('Failed to delete validation record');
}
}
// Usage
deleteValidation(123);
Important Notes
- Deletion is permanent and cannot be undone
- Deletes validation record and all detailed validation items
- Does NOT delete contacts - they remain in the list
- If you want to remove invalid numbers, use cleanup endpoint first
- Useful for cleaning up old validation history
Recommended Workflow
- Review validation results using GET endpoint
- Export data if needed for records using export endpoint
- Process cleanup to remove non-mobile numbers (optional)
- Delete validation record to clean up history
Contact Validator Best Practices
Validation Workflow
- Pre-validate: Use verify endpoint (3.2.1) to check credits needed
- Validate: Use validate endpoint (3.2.2) to process validation
- Review results: Use validator GET endpoint (3.6.1) to see statistics
- Export data: Export results to Excel for analysis (3.6.3)
- Cleanup: Remove non-mobile numbers if needed (3.6.2)
- Archive: Delete old validation records (3.6.4)
When to Use Cleanup
- SMS Campaigns: Remove landlines to ensure mobile-only lists
- Cost Optimization: Remove invalid numbers to reduce failed message attempts
- Compliance: Ensure you're only contacting mobile numbers
- Data Quality: Maintain clean, validated contact lists
When NOT to Use Cleanup
- If you need to contact landlines for voice calls
- If you want to keep complete contact records
- If validation identified useful landline numbers
Export Use Cases
- Generate reports for stakeholders
- Archive validation results
- Analyze validation patterns
- Compliance documentation
- Share results with team members
Complete Validator Workflow Example
// Complete validation management workflow
class ValidationManager {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1';
}
async getHistory(page = 1, search = '') {
const params = new URLSearchParams({ page, limit: 20 });
if (search) params.append('search', search);
return await this.apiCall('GET', `/contact/validator/?${params}`);
}
async processCleanup(validationId) {
const confirmed = confirm(
'Remove all non-mobile numbers? This cannot be undone.'
);
if (!confirmed) return;
return await this.apiCall('POST', `/contact/validator/process-cleanup/${validationId}`);
}
async exportResults(validationIds) {
const result = await this.apiCall('POST', '/contact/validator/export', {
validation_ids: validationIds
});
if (result.success) {
// Auto-download
window.open(result.data.url, '_blank');
}
return result;
}
async deleteRecord(validationId) {
const confirmed = confirm('Delete validation record?');
if (!confirmed) return;
return await this.apiCall('DELETE', `/contact/validator/${validationId}`);
}
async completeWorkflow(validationId) {
try {
// 1. Get validation details
const history = await this.getHistory();
const validation = history.data.data.find(v => v.id === validationId);
console.log('Validation stats:', {
total: validation.total_number,
mobile: validation.total_mobile_numbers,
landline: validation.total_landline_numbers,
invalid: validation.invalid_numbers
});
// 2. Export results
console.log('Exporting results...');
await this.exportResults([validationId]);
// 3. Cleanup if needed
if (validation.total_landline_numbers > 0) {
console.log('Processing cleanup...');
await this.processCleanup(validationId);
}
// 4. Delete old record
console.log('Cleaning up history...');
await this.deleteRecord(validationId);
console.log('Workflow completed successfully');
} catch (error) {
console.error('Workflow failed:', error);
}
}
async apiCall(method, endpoint, data = null) {
const headers = {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
};
if (data) {
headers['Content-Type'] = 'application/json';
}
const response = await fetch(this.baseUrl + endpoint, {
method,
headers,
body: data ? JSON.stringify(data) : null
});
return await response.json();
}
}
// Usage
const validator = new ValidationManager('SID....................................', 'PK.....................................');
// Get validation history
await validator.getHistory(1, 'VIP');
// Process cleanup
await validator.processCleanup(123);
// Export results
await validator.exportResults([123, 124]);
// Delete record
await validator.deleteRecord(123);
// Complete workflow
await validator.completeWorkflow(123);
Cost Management Tips
- Export validation results before cleanup for record-keeping
- Monitor validation statistics to identify data quality issues
- Set up alerts for high invalid number rates
- Regular cleanup helps reduce wasted message credits
- Archive old validation records to keep history clean
3.7 Contact Import History
The Contact Import History API provides endpoints to view and manage the history of contact imports performed via CSV file uploads. Track import status, view statistics, and download reports of skipped numbers during the import process.
Key Features:
- View complete import history with status tracking
- Monitor import progress (Pending, Processing, Completed, Failed)
- Track total records imported and skipped
- Download PDF reports of skipped numbers
- View remarks and error messages for failed imports
- Pagination support for large import history
3.7.1 Get Import History
Retrieve a paginated list of all contact import records for your account. The response includes detailed information about each import including status, file name, total records, skipped numbers, and timestamps.
/api/v1/contact/import
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
Example Request - Get All Imports
curl -X GET "https://api.texttorrent.com/api/v1/contact/import/" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - With Pagination
curl -X GET "https://api.texttorrent.com/api/v1/contact/import/?limit=20&page=1" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact imports fetched successfully",
"data": {
"current_page": 1,
"data": [
{
"id": 45,
"name": "customers.csv",
"file": "imports/customers_1729180845.csv",
"status": 2,
"status_text": "Completed",
"remarks": null,
"list_name": "Customers",
"total_records": 500,
"skipped": 12,
"skipped_file_url": "https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx",
"stored": 488,
"created_at": "2025-10-15T14:30:00.000000Z"
},
{
"id": 44,
"name": "leads.csv",
"file": "imports/leads_1729094400.csv",
"status": 2,
"status_text": "Completed",
"remarks": null,
"list_name": "Hot Leads",
"total_records": 250,
"skipped": 0,
"skipped_file_url": null,
"stored": 250,
"created_at": "2025-10-14T10:15:00.000000Z"
},
{
"id": 43,
"name": "prospects.csv",
"file": "imports/prospects_1729008000.csv",
"status": 3,
"status_text": "Failed",
"remarks": "Invalid CSV format",
"list_name": null,
"total_records": 0,
"skipped": 0,
"skipped_file_url": null,
"stored": 0,
"created_at": "2025-10-13T08:00:00.000000Z"
},
{
"id": 42,
"name": "contacts.csv",
"file": "imports/contacts_1728921600.csv",
"status": 1,
"status_text": "Processing",
"remarks": null,
"list_name": "Contacts",
"total_records": 1000,
"skipped": 0,
"skipped_file_url": null,
"stored": 450,
"created_at": "2025-10-12T06:00:00.000000Z"
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/contact/import?page=1",
"from": 1,
"last_page": 3,
"last_page_url": "https://api.texttorrent.com/api/v1/contact/import?page=3",
"next_page_url": "https://api.texttorrent.com/api/v1/contact/import?page=2",
"path": "https://api.texttorrent.com/api/v1/contact/import",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 28
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the import record |
name |
string | Original filename of the uploaded CSV |
file |
string | Stored file path in the system |
status |
integer | Status code: 0=Pending, 1=Processing, 2=Completed, 3=Failed |
status_text |
string | Human-readable status: "Pending", "Processing", "Completed", "Failed" |
remarks |
string|null | Error message or notes (populated for failed imports) |
list_name |
string|null | Name of the contact list where contacts were imported |
total_records |
integer | Total number of records in the CSV file |
skipped |
integer | Number of records that were skipped during import |
skipped_file_url |
string|null | URL to download Excel file containing skipped records (null if no skips) |
stored |
integer | Number of contacts successfully imported and stored |
created_at |
string | Timestamp when import was initiated (ISO 8601 format) |
Import Status Values
| Status Code | Status Text | Description |
|---|---|---|
0 |
Pending | Import queued but not yet started |
1 |
Processing | Import currently in progress |
2 |
Completed | Import finished successfully |
3 |
Failed | Import encountered an error and failed |
Understanding Skipped Records
Records may be skipped during import for various reasons:
- Duplicate Numbers: Phone number already exists in the list
- Invalid Format: Phone number doesn't match required format
- Missing Required Fields: First name or phone number is empty
- List Full: Target list has reached 10,000 contact limit
- Validation Errors: Data doesn't pass validation rules
Example in JavaScript
async function getImportHistory(page = 1, limit = 10) {
const params = new URLSearchParams({
page: page,
limit: limit
});
const response = await fetch(`https://api.texttorrent.com/api/v1/contact/import/?${params}`, {
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Import history:', data.data.data);
// Display summary
data.data.data.forEach(importRecord => {
console.log(`Import ${importRecord.id}: ${importRecord.name}`);
console.log(` Status: ${importRecord.status_text}`);
console.log(` Total: ${importRecord.total_records}, Stored: ${importRecord.stored}, Skipped: ${importRecord.skipped}`);
if (importRecord.skipped > 0) {
console.log(` Skipped file: ${importRecord.skipped_file_url}`);
}
if (importRecord.status === 3) {
console.log(` Error: ${importRecord.remarks}`);
}
});
console.log(`Page ${data.data.current_page} of ${data.data.last_page}`);
console.log(`Total imports: ${data.data.total}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get first page
getImportHistory();
// Usage - Get with pagination
getImportHistory(1, 20);
Important Notes
- Results are ordered by ID in descending order (newest first)
- Only imports belonging to your account are returned
- Import records are created immediately when CSV upload begins
- Status updates in real-time as import progresses
- Skipped file URL is only available if records were skipped
- Failed imports have error details in the
remarksfield
Monitoring Import Progress
// Poll import status until completion
async function monitorImport(importId) {
let status = 1; // Processing
while (status === 0 || status === 1) {
const history = await getImportHistory();
const importRecord = history.data.find(imp => imp.id === importId);
if (!importRecord) {
throw new Error('Import record not found');
}
status = importRecord.status;
console.log(`Import ${importId}: ${importRecord.status_text} - ${importRecord.stored}/${importRecord.total_records} records`);
if (status === 2) {
console.log('Import completed successfully');
if (importRecord.skipped > 0) {
console.log(`${importRecord.skipped} records were skipped`);
console.log(`Download skipped records: ${importRecord.skipped_file_url}`);
}
return importRecord;
} else if (status === 3) {
console.error('Import failed:', importRecord.remarks);
throw new Error(importRecord.remarks);
}
// Wait 5 seconds before checking again
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
// Usage
monitorImport(45);
3.7.2 Download Skipped Numbers PDF
Download a PDF report of contacts that were skipped during a CSV import. The report includes details about why each record was skipped, helping you identify and fix data issues for re-import.
/api/v1/contact/import/pdf/download
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/pdf
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | URL of the skipped file (obtained from import history skipped_file_url) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/import/pdf/download" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/pdf" \
-d '{
"url": "https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx"
}' \
--output skipped_numbers.pdf
Success Response (200 OK)
Returns a PDF file for download. Content-Type: application/pdf
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="skipped_numbers.pdf"
Content-Length: 45678
[PDF binary data]
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"url": [
"The url field is required.",
"The url must be a valid URL."
]
}
}
Common Validation Errors
- Required: "The url field is required."
- Invalid Format: "The url must be a valid URL."
PDF Report Contents
The generated PDF typically includes:
- Import Summary: Total records, imported, and skipped counts
- Skipped Records Table: List of all skipped contacts with details
- Skip Reasons: Why each record was skipped (duplicate, invalid format, etc.)
- Contact Information: Name, phone number, email, company (if available)
- Timestamp: When the import was performed
Example in JavaScript
async function downloadSkippedPDF(skippedFileUrl) {
const response = await fetch('https://api.texttorrent.com/api/v1/contact/import/pdf/download', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/pdf'
},
body: JSON.stringify({
url: skippedFileUrl
})
});
if (response.ok) {
// Convert response to blob
const blob = await response.blob();
// Create download link
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = `skipped_numbers_${Date.now()}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Clean up
window.URL.revokeObjectURL(downloadUrl);
console.log('PDF downloaded successfully');
} else {
const error = await response.json();
console.error('Download failed:', error.message);
throw new Error(error.message);
}
}
// Usage
downloadSkippedPDF('https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx');
Example in PHP
function downloadSkippedPDF($skippedFileUrl, $outputPath) {
$ch = curl_init('https://api.texttorrent.com/api/v1/contact/import/pdf/download');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Content-Type: application/json',
'Accept: application/pdf'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'url' => $skippedFileUrl
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$pdfContent = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
file_put_contents($outputPath, $pdfContent);
echo "PDF saved to: $outputPath";
return true;
} else {
$error = json_decode($pdfContent, true);
echo "Error: " . ($error['message'] ?? 'Unknown error');
return false;
}
}
// Usage
downloadSkippedPDF(
'https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx',
'skipped_numbers.pdf'
);
Workflow Example
// Complete workflow: Get import history and download skipped reports
async function reviewImportResults(importId) {
// 1. Get import history
const history = await getImportHistory();
const importRecord = history.data.find(imp => imp.id === importId);
if (!importRecord) {
throw new Error('Import not found');
}
console.log(`Import: ${importRecord.name}`);
console.log(`Status: ${importRecord.status_text}`);
console.log(`Total: ${importRecord.total_records}`);
console.log(`Stored: ${importRecord.stored}`);
console.log(`Skipped: ${importRecord.skipped}`);
// 2. Download skipped numbers PDF if any were skipped
if (importRecord.skipped > 0 && importRecord.skipped_file_url) {
console.log('Downloading skipped numbers report...');
await downloadSkippedPDF(importRecord.skipped_file_url);
console.log('Review the PDF to see why records were skipped');
} else {
console.log('No records were skipped - import was 100% successful!');
}
// 3. Handle failed imports
if (importRecord.status === 3) {
console.error('Import failed:', importRecord.remarks);
console.log('Please fix the issues and try importing again');
}
}
// Usage
reviewImportResults(45);
Important Notes
- URL must be a valid, accessible skipped file URL from import history
- PDF is generated on-the-fly from the Excel skipped file
- Response is a binary PDF file (not JSON)
- Set
Accept: application/pdfheader to receive PDF - Use
--outputor equivalent to save file in cURL/HTTP clients - PDF includes all details needed to fix and re-import skipped records
Use Cases
- Review why contacts were skipped during import
- Generate reports for data quality issues
- Share skipped records report with team members
- Identify patterns in import failures
- Document import issues for compliance
Import History Best Practices
Monitoring Import Progress
- Poll Regularly: Check import status every 5-10 seconds for large files
- Show Progress: Display stored/total_records ratio as progress percentage
- Handle Failures: Show remarks field to user if status is Failed
- Notify Completion: Alert user when import completes with success/skip counts
Handling Skipped Records
- Review Immediately: Download and review skipped PDF after import completes
- Fix Data Issues: Correct duplicates, invalid formats, missing fields in source CSV
- Re-import: Create new CSV with only skipped records (after fixing issues)
- Document Patterns: Track common skip reasons to improve data quality
Common Skip Reasons and Fixes
| Skip Reason | Fix |
|---|---|
| Duplicate Number | Remove duplicates from CSV or choose different list |
| Invalid Phone Format | Ensure numbers are 10 digits without +1 prefix |
| Missing First Name | Add first names or use placeholder like "Contact" |
| List Full (10,000 limit) | Create new list or cleanup existing list |
| Invalid Email Format | Fix email syntax or leave blank |
Complete Import Management Workflow
// Complete import management system
class ImportManager {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1';
}
async getHistory(page = 1, limit = 10) {
const params = new URLSearchParams({ page, limit });
return await this.apiCall('GET', `/contact/import/?${params}`);
}
async monitorImport(importId, onProgress) {
let status = 1; // Processing
let lastStored = 0;
while (status === 0 || status === 1) {
const history = await this.getHistory();
const imp = history.data.data.find(i => i.id === importId);
if (!imp) throw new Error('Import not found');
status = imp.status;
// Report progress if changed
if (imp.stored !== lastStored) {
lastStored = imp.stored;
const progress = (imp.stored / imp.total_records * 100).toFixed(1);
onProgress?.({
status: imp.status_text,
progress: progress,
stored: imp.stored,
total: imp.total_records,
skipped: imp.skipped
});
}
if (status === 2) {
// Completed
return {
success: true,
import: imp,
message: `Import completed: ${imp.stored} contacts imported, ${imp.skipped} skipped`
};
} else if (status === 3) {
// Failed
return {
success: false,
import: imp,
message: `Import failed: ${imp.remarks}`
};
}
// Wait 5 seconds before checking again
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
async downloadSkippedPDF(skippedUrl) {
const response = await fetch(`${this.baseUrl}/contact/import/pdf/download`, {
method: 'POST',
headers: {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Content-Type': 'application/json',
'Accept': 'application/pdf'
},
body: JSON.stringify({ url: skippedUrl })
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `skipped_${Date.now()}.pdf`;
link.click();
window.URL.revokeObjectURL(url);
return true;
}
throw new Error('Failed to download PDF');
}
async handleImportComplete(importId) {
const history = await this.getHistory();
const imp = history.data.find(i => i.id === importId);
if (!imp) throw new Error('Import not found');
console.log(`Import completed: ${imp.status_text}`);
console.log(`Stored: ${imp.stored}, Skipped: ${imp.skipped}`);
// Download skipped report if any
if (imp.skipped > 0 && imp.skipped_file_url) {
console.log('Downloading skipped records report...');
await this.downloadSkippedPDF(imp.skipped_file_url);
alert(`${imp.skipped} records were skipped. Check the downloaded PDF for details.`);
} else {
alert('Import completed successfully with no skipped records!');
}
return imp;
}
async apiCall(method, endpoint, data = null) {
const headers = {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
};
if (data) headers['Content-Type'] = 'application/json';
const response = await fetch(this.baseUrl + endpoint, {
method,
headers,
body: data ? JSON.stringify(data) : null
});
return await response.json();
}
}
// Usage
const importMgr = new ImportManager('SID....................................', 'PK.....................................');
// Monitor import with progress updates
await importMgr.monitorImport(45, (progress) => {
console.log(`${progress.status}: ${progress.progress}% (${progress.stored}/${progress.total})`);
// Update UI progress bar
updateProgressBar(progress.progress);
});
// Handle completion
await importMgr.handleImportComplete(45);
Performance Tips
- Don't poll too frequently (5-10 seconds is sufficient)
- Cache import history results to reduce API calls
- Only download skipped PDF when user requests it
- Show import progress in real-time using stored/total_records
- Archive old import records to keep history clean
Error Handling
- Always check
statusfield before assuming success - Display
remarksfield to user for failed imports - Handle missing skipped_file_url gracefully (means no skips)
- Provide clear guidance on fixing skipped records
3.8 Blocked List Management
The Blocked List Management API allows you to manage contacts who should not receive messages from your account. Add numbers to the blocked list, view blocked contacts, remove them from the list, export blocked contacts, or permanently delete blocked contact records.
Key Features:
- View all blocked contacts with search and filtering
- Add single or multiple numbers to blocked list
- Remove contacts from blocked list (unblock)
- Permanently delete blocked contact records
- Export blocked contacts to Excel
- Track total opt-out words count
- Filter by manually added vs auto-blocked contacts
added_by
filter to distinguish between the two.
3.8.1 Get Blocked List
Retrieve a paginated list of all blocked contacts for your account. Results can be searched by phone number and filtered by how the contact was blocked (auto-blocked via opt-out words or manually added).
/api/v1/contact/blocked-list/
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
search |
string | No | Search term to filter by phone number (partial match) |
added_by |
string | No | Filter by blocking method: auto (opt-out words) or manual
(manually added) |
Example Request - Get All Blocked Contacts
curl -X GET "https://api.texttorrent.com/api/v1/contact/blocked-list/" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Search and Filter
curl -X GET "https://api.texttorrent.com/api/v1/contact/blocked-list/?search=555&added_by=manual&limit=20&page=1" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Blocked list fetched successfully",
"data": {
"blocked_list": {
"current_page": 1,
"data": [
{
"id": 1234,
"first_name": "John",
"last_name": "Doe",
"number": "+12025551234",
"email": "john.doe@example.com",
"company": null,
"user_id": 1,
"list_id": null,
"folder_id": null,
"blacklisted": 1,
"blacklisted_by": 1,
"blacklisted_at": "2025-10-15T14:30:00.000000Z",
"valid": 0,
"validation_process": 0,
"created_at": "2025-10-15T14:30:00.000000Z",
"updated_at": "2025-10-15T14:30:00.000000Z"
},
{
"id": 1235,
"first_name": "Jane",
"last_name": "Smith",
"number": "+12025555678",
"email": null,
"company": null,
"user_id": 1,
"list_id": null,
"folder_id": null,
"blacklisted": 1,
"blacklisted_by": null,
"blacklisted_at": "2025-10-14T10:15:00.000000Z",
"valid": 0,
"validation_process": 0,
"created_at": "2025-10-14T10:15:00.000000Z",
"updated_at": "2025-10-14T10:15:00.000000Z"
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/contact/blocked-list?page=1",
"from": 1,
"last_page": 5,
"last_page_url": "https://api.texttorrent.com/api/v1/contact/blocked-list?page=5",
"next_page_url": "https://api.texttorrent.com/api/v1/contact/blocked-list?page=2",
"path": "https://api.texttorrent.com/api/v1/contact/blocked-list",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 48
},
"total": 48,
"total_otp_out": 12
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
blocked_list |
object | Paginated list of blocked contacts |
total |
integer | Total number of blocked contacts |
total_otp_out |
integer | Total number of opt-out words configured |
Blocked Contact Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the contact |
number |
string | Blocked phone number (with +1 prefix) |
blacklisted |
integer | Always 1 for blocked contacts |
blacklisted_by |
integer|null | User ID who manually blocked (null if auto-blocked via opt-out words) |
blacklisted_at |
string | Timestamp when contact was blocked (ISO 8601 format) |
list_id |
integer|null | Always null for blocked contacts (removed from lists) |
Filter Behavior
- added_by=auto: Returns only contacts auto-blocked via opt-out words (blacklisted_by is null)
- added_by=manual: Returns only contacts manually added to blocked list (blacklisted_by is not null)
- No filter: Returns all blocked contacts regardless of how they were blocked
Example in JavaScript
async function getBlockedList(options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
if (options.search) {
params.append('search', options.search);
}
if (options.addedBy) {
params.append('added_by', options.addedBy);
}
const response = await fetch(
`https://api.texttorrent.com/api/v1/contact/blocked-list/?${params}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Blocked contacts:', data.data.blocked_list.data);
console.log(`Total blocked: ${data.data.total}`);
console.log(`Opt-out words configured: ${data.data.total_otp_out}`);
// Categorize by blocking method
const manual = data.data.blocked_list.data.filter(c => c.blacklisted_by !== null);
const auto = data.data.blocked_list.data.filter(c => c.blacklisted_by === null);
console.log(`Manually blocked: ${manual.length}, Auto-blocked: ${auto.length}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get all blocked contacts
getBlockedList();
// Usage - Search for specific number
getBlockedList({ search: '555' });
// Usage - Get only manually blocked contacts
getBlockedList({ addedBy: 'manual', limit: 20 });
Important Notes
- Results are ordered by
blacklisted_atin descending order (newest first) - Search filters by phone number (partial match)
- Blocked contacts have
list_idset to null (not in any list) blacklisted_bynull = auto-blocked via opt-out wordsblacklisted_bynot null = manually added to blocked list- Pagination works the same as other list endpoints
3.8.2 Add to Blocked List
Add one or more phone numbers to the blocked list. Blocked numbers will not receive any messages from your account. This is useful for manually blocking contacts who request to opt-out or for compliance purposes.
/api/v1/contact/blocked-list/
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
numbers |
array | Yes | Array of phone numbers to block (10 digits, without +1 prefix) |
Example Request - Block Single Number
curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"numbers": ["2025551234"]
}'
Example Request - Block Multiple Numbers
curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"numbers": ["2025551234", "2025555678", "2025559999"]
}'
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Blocklist create",
"data": true,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"numbers": [
"The numbers field is required.",
"The numbers must be an array."
],
"numbers.0": [
"The numbers.0 must be a string."
]
}
}
Common Validation Errors
- Required: "The numbers field is required."
- Invalid Type: "The numbers must be an array."
- Invalid Format: "The numbers.X must be a string."
What Happens When Numbers Are Blocked
- Contact records are created with
blacklisted=1 - Numbers automatically get +1 prefix
blacklisted_byis set to your user ID (indicates manual blocking)blacklisted_atis set to current timestamp- Contact uses your name and email as default information
- Blocked numbers will not receive any messages
Example in JavaScript
async function addToBlockedList(phoneNumbers) {
// Validate phone numbers
const validNumbers = phoneNumbers.filter(num => /^\d{10}$/.test(num));
if (validNumbers.length === 0) {
throw new Error('No valid 10-digit phone numbers provided');
}
if (validNumbers.length !== phoneNumbers.length) {
console.warn('Some numbers were invalid and skipped');
}
const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
numbers: validNumbers
})
});
const data = await response.json();
if (data.success) {
console.log(`Successfully blocked ${validNumbers.length} number(s)`);
alert(`${validNumbers.length} number(s) added to blocked list`);
// Refresh blocked list
await getBlockedList();
return data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Block single number
addToBlockedList(['2025551234']);
// Usage - Block multiple numbers
addToBlockedList(['2025551234', '2025555678', '2025559999']);
Example in PHP
function addToBlockedList($phoneNumbers) {
$ch = curl_init('https://api.texttorrent.com/api/v1/contact/blocked-list/');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Content-Type: application/json',
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'numbers' => $phoneNumbers
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
echo 'Numbers blocked successfully';
return true;
} else {
echo 'Error: ' . $data['message'];
return false;
}
}
// Usage
addToBlockedList(['2025551234', '2025555678']);
Important Notes
- Phone numbers must be 10 digits without +1 prefix (system adds it automatically)
- Supports bulk blocking - add multiple numbers in one request
- Blocked numbers receive
blacklisted_by= your user ID - If number already exists in system, it will be updated to blocked status
- Blocked contacts are removed from any lists they were in
- Returns 201 Created status on success
Use Cases
- Honor manual opt-out requests from customers
- Block known spam or invalid numbers
- Comply with do-not-contact lists
- Block numbers that bounce or cause complaints
3.8.3 Remove from Blocked List
Remove one or more contacts from the blocked list (unblock them). This sets their blacklisted
status to 0, allowing them to receive messages again. The contact record remains in the system.
/api/v1/contact/blocked-list/remove
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_ids |
array | Yes | Array of contact IDs to unblock (must exist in contacts table) |
Example Request - Unblock Single Contact
curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/remove" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_ids": [1234]
}'
Example Request - Unblock Multiple Contacts
curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/remove" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_ids": [1234, 1235, 1236]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact(s) removed from blocked list successfully",
"data": null,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_ids": [
"The contact ids field is required.",
"The contact ids must be an array."
],
"contact_ids.0": [
"The selected contact ids.0 is invalid."
]
}
}
Common Validation Errors
- Required: "The contact ids field is required."
- Invalid Type: "The contact ids must be an array."
- Invalid ID: "The selected contact ids.X is invalid." (contact doesn't exist)
- Invalid Format: Each element must be an integer
What Happens When Contacts Are Unblocked
blacklistedis set to 0 (contact is no longer blocked)- Contact record remains in the system (not deleted)
- Contact can now receive messages again
blacklisted_byandblacklisted_atremain unchanged (for history)- Contact is NOT automatically added back to any list
Example in JavaScript
async function removeFromBlockedList(contactIds) {
// Confirm unblocking
if (!confirm(`Unblock ${contactIds.length} contact(s)? They will be able to receive messages again.`)) {
return;
}
const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/remove', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
contact_ids: contactIds
})
});
const data = await response.json();
if (data.success) {
console.log('Contacts unblocked successfully');
alert(`${contactIds.length} contact(s) removed from blocked list`);
// Refresh blocked list
await getBlockedList();
} else {
console.error('Error:', data.message);
alert('Failed to unblock contacts');
}
}
// Usage - Unblock single contact
removeFromBlockedList([1234]);
// Usage - Unblock multiple contacts
removeFromBlockedList([1234, 1235, 1236]);
Important Notes
- Unblocking sets
blacklisted=0but keeps contact record - Use DELETE endpoint (3.8.4) to permanently delete blocked contacts
- Unblocked contacts are NOT automatically re-added to lists
- You'll need to manually add them to a list if desired
- Supports bulk unblocking - multiple contacts in one request
- All contact IDs must exist and belong to your account
3.8.4 Delete Blocked Contacts
Permanently delete blocked contact records from the system. This removes the contacts entirely, including all their information. Use this to clean up old blocked contacts you no longer need to track.
/api/v1/contact/blocked-list/delete
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_ids |
array | Yes | Array of contact IDs to delete permanently (must exist in contacts table) |
Example Request
curl -X DELETE "https://api.texttorrent.com/api/v1/contact/blocked-list/delete" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_ids": [1234, 1235]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact(s) deleted successfully",
"data": null,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_ids": [
"The contact ids field is required."
],
"contact_ids.0": [
"The selected contact ids.0 is invalid."
]
}
}
Example in JavaScript
async function deleteBlockedContacts(contactIds) {
// Confirm deletion
if (!confirm(
`Permanently delete ${contactIds.length} blocked contact(s)? ` +
`This cannot be undone and all contact information will be lost.`
)) {
return;
}
const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/delete', {
method: 'DELETE',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
contact_ids: contactIds
})
});
const data = await response.json();
if (data.success) {
console.log('Blocked contacts deleted permanently');
alert(`${contactIds.length} blocked contact(s) deleted`);
// Refresh blocked list
await getBlockedList();
} else {
console.error('Error:', data.message);
alert('Failed to delete contacts');
}
}
// Usage
deleteBlockedContacts([1234, 1235]);
Important Notes
- Deletion is permanent and cannot be undone
- All contact information is completely removed from the database
- If the number contacts you again, they can be re-added to the system
- Use unblock endpoint if you want to keep contact record but allow messages
- Supports bulk deletion - multiple contacts in one request
When to Delete vs Unblock
- Delete: Clean up old blocked contacts you no longer need to track
- Unblock: Customer requested to opt back in, keep their information
3.8.5 Export Blocked List
Export blocked contacts to an Excel (.xlsx) file. You can export specific contacts or all blocked contacts. The export includes contact information, phone numbers, and blocking details.
/api/v1/contact/blocked-list/export
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_ids |
array | Yes | Array of contact IDs to export (must exist in contacts table) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/export" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contact_ids": [1234, 1235, 1236]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "File generated successfully",
"data": {
"url": "https://your-storage.digitaloceanspaces.com/blocked-list/blocked_list_export_1729180845.xlsx"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_ids": [
"The contact ids field is required."
],
"contact_ids.0": [
"The selected contact ids.0 is invalid."
]
}
}
Export File Contents
The exported Excel file typically includes:
- Contact name (first name, last name)
- Phone number
- Email address
- Company
- Blocked date (blacklisted_at)
- Blocked by (user ID or "Auto" if via opt-out words)
- Additional contact fields
Example in JavaScript
async function exportBlockedList(contactIds) {
const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/export', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
contact_ids: contactIds
})
});
const data = await response.json();
if (data.success) {
console.log('Export successful, downloading...');
// Trigger download
window.open(data.data.url, '_blank');
return data.data.url;
} else {
console.error('Export failed:', data.message);
throw new Error(data.message);
}
}
// Usage - Export specific contacts
exportBlockedList([1234, 1235, 1236]);
// Usage - Export all blocked contacts (get IDs from list first)
async function exportAllBlocked() {
const blockedData = await getBlockedList({ limit: 1000 });
const allIds = blockedData.blocked_list.data.map(contact => contact.id);
await exportBlockedList(allIds);
}
Important Notes
- File is stored on DigitalOcean Spaces (cloud storage)
- Download URL is publicly accessible - use immediately
- File name includes timestamp:
blocked_list_export_{timestamp}.xlsx - Supports bulk export of multiple contacts
- All contact IDs must exist and belong to your account
- If any contact ID is invalid, the entire operation fails
Use Cases
- Backup blocked contact list for compliance
- Generate reports for stakeholders
- Audit blocked contacts periodically
- Share blocked list with team members
- Archive blocked contacts before deletion
Blocked List Management Best Practices
Compliance and Legal
- Honor Opt-Outs: Always respect customer opt-out requests immediately
- Document Blocking: Export blocked list regularly for compliance records
- Automatic Blocking: Configure opt-out words (STOP, UNSUBSCRIBE, etc.) for auto-blocking
- Manual Review: Periodically review auto-blocked contacts for false positives
Managing Blocked Contacts
- Use
added_byfilter to distinguish manual vs auto-blocked contacts - Export blocked list before bulk deletions for backup
- Use unblock for customers who want to opt back in
- Use delete for cleaning up old blocked contacts you don't need to track
Workflow Examples
// Complete blocked list management workflow
class BlockedListManager {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1';
}
async getList(options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
if (options.search) params.append('search', options.search);
if (options.addedBy) params.append('added_by', options.addedBy);
return await this.apiCall('GET', `/contact/blocked-list/?${params}`);
}
async blockNumbers(phoneNumbers) {
return await this.apiCall('POST', '/contact/blocked-list/', {
numbers: phoneNumbers
});
}
async unblock(contactIds) {
return await this.apiCall('POST', '/contact/blocked-list/remove', {
contact_ids: contactIds
});
}
async delete(contactIds) {
return await this.apiCall('DELETE', '/contact/blocked-list/delete', {
contact_ids: contactIds
});
}
async export(contactIds) {
const result = await this.apiCall('POST', '/contact/blocked-list/export', {
contact_ids: contactIds
});
if (result.success) {
window.open(result.data.url, '_blank');
}
return result;
}
async auditBlockedList() {
// Get all blocked contacts
const data = await this.getList({ limit: 1000 });
const blocked = data.data.blocked_list.data;
console.log(`Total blocked contacts: ${data.data.total}`);
console.log(`Opt-out words configured: ${data.data.total_otp_out}`);
// Categorize
const manual = blocked.filter(c => c.blacklisted_by !== null);
const auto = blocked.filter(c => c.blacklisted_by === null);
console.log(`Manually blocked: ${manual.length}`);
console.log(`Auto-blocked: ${auto.length}`);
// Export for records
const allIds = blocked.map(c => c.id);
await this.export(allIds);
return { total: data.data.total, manual: manual.length, auto: auto.length };
}
async apiCall(method, endpoint, data = null) {
const headers = {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
};
if (data && method !== 'GET') {
headers['Content-Type'] = 'application/json';
}
const options = {
method,
headers,
body: data && method !== 'GET' ? JSON.stringify(data) : null
};
const response = await fetch(this.baseUrl + endpoint, options);
return await response.json();
}
}
// Usage
const blockedMgr = new BlockedListManager('SID....................................', 'PK.....................................');
// Block numbers
await blockedMgr.blockNumbers(['2025551234', '2025555678']);
// Get blocked list
await blockedMgr.getList({ addedBy: 'manual' });
// Unblock contacts
await blockedMgr.unblock([1234, 1235]);
// Delete contacts
await blockedMgr.delete([1236]);
// Export contacts
await blockedMgr.export([1234, 1235, 1236]);
// Run audit
await blockedMgr.auditBlockedList();
Performance Tips
- Use pagination for large blocked lists (don't load all at once)
- Cache blocked list data to reduce API calls
- Use bulk operations (block/unblock/delete multiple at once)
- Export periodically rather than fetching full list repeatedly
Common Mistakes to Avoid
- ❌ Deleting instead of unblocking when customer opts back in
- ❌ Not backing up before bulk deletions
- ❌ Ignoring auto-blocked contacts (review periodically)
- ❌ Not configuring opt-out words for automatic blocking
- ✅ Export blocked list for compliance documentation
- ✅ Use unblock to keep customer record intact
- ✅ Review and categorize blocked contacts regularly
3.9 Opt-Out Words Management
The Opt-Out Words Management API allows you to configure keywords that automatically block contacts when they reply with specific words or phrases. When a contact sends a message containing any of your opt-out words (like "STOP", "UNSUBSCRIBE", "CANCEL"), they are automatically added to the blocked list.
Key Features:
- Configure custom opt-out keywords and phrases
- Automatic contact blocking when opt-out words are detected
- Search and paginate through opt-out words
- Update existing opt-out words
- Bulk delete opt-out words
- Export opt-out word list to CSV
- Hierarchy support for master/main/sub accounts
- You configure opt-out words (e.g., "STOP", "UNSUBSCRIBE", "OPT OUT")
- When a contact replies with a message containing any opt-out word
- The system automatically sets their
blacklistedstatus to 1 - The contact appears in blocked list with
blacklisted_by=null(auto-blocked) - They will no longer receive messages from your account
Account Hierarchy
Opt-out words follow account hierarchy rules:
- Master Account: Can create opt-out words that apply to all accounts
- Main Account: Inherits master account words + can add their own
- Sub Account: Inherits both master and parent main account words + can add their own
- If a duplicate word exists in parent/master, it gets reassigned to prevent conflicts
3.9.1 Get Opt-Out Words
Retrieve a paginated list of all opt-out words configured for your account. Results can be searched by keyword and paginated for easy management.
/api/v1/contact/opt-out-word
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
search |
string | No | Search term to filter opt-out words (partial match) |
Example Request - Get All Opt-Out Words
curl -X GET "https://api.texttorrent.com/api/v1/contact/opt-out-word" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Search and Paginate
curl -X GET "https://api.texttorrent.com/api/v1/contact/opt-out-word?search=STOP&limit=20&page=1" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Opt-out words fetched successfully",
"data": {
"current_page": 1,
"data": [
{
"id": 1,
"user_id": 1,
"word": "STOP",
"created_at": "2025-01-15T10:30:00.000000Z",
"updated_at": "2025-01-15T10:30:00.000000Z"
},
{
"id": 2,
"user_id": 1,
"word": "UNSUBSCRIBE",
"created_at": "2025-01-15T10:31:00.000000Z",
"updated_at": "2025-01-15T10:31:00.000000Z"
},
{
"id": 3,
"user_id": 1,
"word": "CANCEL",
"created_at": "2025-01-15T10:32:00.000000Z",
"updated_at": "2025-01-15T10:32:00.000000Z"
},
{
"id": 4,
"user_id": 1,
"word": "END",
"created_at": "2025-01-15T10:33:00.000000Z",
"updated_at": "2025-01-15T10:33:00.000000Z"
},
{
"id": 5,
"user_id": 1,
"word": "QUIT",
"created_at": "2025-01-15T10:34:00.000000Z",
"updated_at": "2025-01-15T10:34:00.000000Z"
},
{
"id": 6,
"user_id": 1,
"word": "OPT OUT",
"created_at": "2025-01-15T10:35:00.000000Z",
"updated_at": "2025-01-15T10:35:00.000000Z"
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/contact/opt-out-word?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "https://api.texttorrent.com/api/v1/contact/opt-out-word?page=1",
"next_page_url": null,
"path": "https://api.texttorrent.com/api/v1/contact/opt-out-word",
"per_page": 10,
"prev_page_url": null,
"to": 6,
"total": 6
},
"errors": null
}
Opt-Out Word Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the opt-out word |
user_id |
integer | ID of the user who created this opt-out word |
word |
string | The opt-out keyword or phrase (case-insensitive) |
created_at |
string | Timestamp when opt-out word was created (ISO 8601 format) |
updated_at |
string | Timestamp when opt-out word was last updated (ISO 8601 format) |
Example in JavaScript
async function getOptOutWords(options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
if (options.search) {
params.append('search', options.search);
}
const response = await fetch(
`https://api.texttorrent.com/api/v1/contact/opt-out-word?${params}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Opt-out words:', data.data.data);
console.log(`Total opt-out words: ${data.data.total}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get all opt-out words
getOptOutWords();
// Usage - Search for specific word
getOptOutWords({ search: 'STOP' });
// Usage - Get with custom limit
getOptOutWords({ limit: 50 });
Important Notes
- Results are ordered by
created_atin descending order (newest first) - Search is case-insensitive and performs partial matching
- Default pagination shows 10 results per page
- Opt-out word matching is case-insensitive when processing incoming messages
3.9.2 Create Opt-Out Word
Add a new opt-out word or phrase to your account. When contacts reply with this word, they will be automatically blocked. The system prevents duplicate words across account hierarchies.
/api/v1/contact/opt-out-word
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
word |
string | Yes | Opt-out keyword or phrase (max 255 characters) |
Example Request - Create Single Word
curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"word": "STOP"
}'
Example Request - Create Phrase
curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"word": "DO NOT CONTACT ME"
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Opt-out word created successfully",
"data": {
"id": 7,
"user_id": 1,
"word": "STOP",
"created_at": "2025-10-17T14:30:00.000000Z",
"updated_at": "2025-10-17T14:30:00.000000Z"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"word": [
"The word field is required.",
"The word must be a string.",
"The word may not be greater than 255 characters."
]
}
}
Duplicate Word Error (422)
{
"code": 422,
"success": false,
"message": "This word already exists under your account.",
"data": null,
"errors": null
}
Hierarchy Conflict Error (422)
{
"code": 422,
"success": false,
"message": "This word already exists under your parent or master account.",
"data": null,
"errors": null
}
Common Validation Errors
- Required: "The word field is required."
- Invalid Type: "The word must be a string."
- Too Long: "The word may not be greater than 255 characters."
- Duplicate: "This word already exists under your account."
- Hierarchy Conflict: "This word already exists under your parent or master account."
Account Hierarchy Behavior
- Master Account: If duplicate exists in sub-accounts, reassigns it to master
- Main Account: If duplicate exists in sub-accounts, reassigns it to main; Checks master account for conflicts
- Sub Account: Checks both parent main and master accounts for conflicts
- This prevents the same word from existing multiple times in the hierarchy
Example in JavaScript
async function createOptOutWord(word) {
// Validate word
if (!word || word.trim().length === 0) {
throw new Error('Opt-out word cannot be empty');
}
if (word.length > 255) {
throw new Error('Opt-out word is too long (max 255 characters)');
}
const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
word: word.trim().toUpperCase() // Standardize to uppercase
})
});
const data = await response.json();
if (data.success) {
console.log(`Opt-out word "${word}" created successfully`);
alert(`Opt-out word added: ${word}`);
return data.data;
} else {
console.error('Error:', data.message);
alert(`Failed to add opt-out word: ${data.message}`);
throw new Error(data.message);
}
}
// Usage - Create single opt-out word
createOptOutWord('STOP');
// Usage - Create common opt-out words
const commonOptOutWords = ['STOP', 'UNSUBSCRIBE', 'CANCEL', 'END', 'QUIT', 'OPT OUT'];
for (const word of commonOptOutWords) {
try {
await createOptOutWord(word);
} catch (error) {
console.log(`Skipping "${word}": ${error.message}`);
}
}
Example in PHP
function createOptOutWord($word) {
$ch = curl_init('https://api.texttorrent.com/api/v1/contact/opt-out-word');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Content-Type: application/json',
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'word' => strtoupper(trim($word))
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
echo "Opt-out word created: " . $word;
return $data['data'];
} else {
echo 'Error: ' . $data['message'];
return false;
}
}
// Usage
createOptOutWord('STOP');
Important Notes
- Opt-out word matching is case-insensitive (STOP = stop = Stop)
- Supports multi-word phrases (e.g., "OPT OUT", "DO NOT CONTACT")
- Maximum length is 255 characters
- Leading/trailing whitespace is preserved
- System prevents duplicate words within account hierarchy
Recommended Opt-Out Words
For compliance and best practices, configure these standard opt-out words:
- STOP - Most common and legally required
- UNSUBSCRIBE - Email convention, widely understood
- CANCEL - Common alternative
- END - Simple and clear
- QUIT - Alternative to STOP
- OPT OUT - Explicit opt-out phrase
- REMOVE - Common request
- STOPALL - Used by some carriers
3.9.3 Update Opt-Out Word
Update an existing opt-out word. This allows you to correct typos or change the wording of an opt-out keyword. The system validates uniqueness within the account hierarchy.
/api/v1/contact/opt-out-word/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | ID of the opt-out word to update |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
word |
string | Yes | New opt-out keyword or phrase (max 255 characters, must be unique) |
Example Request
curl -X PUT "https://api.texttorrent.com/api/v1/contact/opt-out-word/7" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"word": "STOP ALL"
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Opt-out word updated successfully",
"data": {
"id": 7,
"user_id": 1,
"word": "STOP ALL",
"created_at": "2025-10-17T14:30:00.000000Z",
"updated_at": "2025-10-17T15:45:00.000000Z"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"word": [
"The word field is required.",
"The word has already been taken."
]
}
}
Not Found Error (404)
{
"code": 404,
"success": false,
"message": "Opt-out word not found",
"data": null,
"errors": null
}
Example in JavaScript
async function updateOptOutWord(id, newWord) {
const response = await fetch(`https://api.texttorrent.com/api/v1/contact/opt-out-word/${id}`, {
method: 'PUT',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
word: newWord.trim().toUpperCase()
})
});
const data = await response.json();
if (data.success) {
console.log('Opt-out word updated successfully');
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
updateOptOutWord(7, 'STOP ALL');
Important Notes
- The new word must be unique within your account hierarchy
- Maximum length is 255 characters
- Updated timestamp is automatically set
- Validation ensures no conflicts with parent/master accounts
3.9.4 Delete Opt-Out Words
Permanently delete one or more opt-out words from your account. Once deleted, incoming messages with these words will no longer trigger automatic blocking.
/api/v1/contact/opt-out-word/delete
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ids |
array | Yes | Array of opt-out word IDs to delete (must exist in opt_out_words table) |
Example Request - Delete Single Word
curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word/delete" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"ids": [7]
}'
Example Request - Delete Multiple Words
curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word/delete" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"ids": [7, 8, 9]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Opt-out word(s) deleted successfully",
"data": null,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"ids": [
"The ids field is required.",
"The ids must be an array."
],
"ids.0": [
"The selected ids.0 is invalid."
]
}
}
Common Validation Errors
- Required: "The ids field is required."
- Invalid Type: "The ids must be an array."
- Invalid ID: "The selected ids.X is invalid." (opt-out word doesn't exist)
Example in JavaScript
async function deleteOptOutWords(ids) {
// Confirm deletion
if (!confirm(`Delete ${ids.length} opt-out word(s)? This cannot be undone.`)) {
return;
}
const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word/delete', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
ids: ids
})
});
const data = await response.json();
if (data.success) {
console.log('Opt-out words deleted successfully');
alert(`${ids.length} opt-out word(s) deleted`);
// Refresh list
await getOptOutWords();
} else {
console.error('Error:', data.message);
alert('Failed to delete opt-out words');
}
}
// Usage - Delete single opt-out word
deleteOptOutWords([7]);
// Usage - Delete multiple opt-out words
deleteOptOutWords([7, 8, 9]);
Important Notes
- Deletion is permanent and cannot be undone
- Supports bulk deletion - multiple words in one request
- All IDs must exist and belong to your account
- After deletion, messages with those words will no longer trigger auto-blocking
- NEVER delete standard compliance words like "STOP" or "UNSUBSCRIBE"
3.9.5 Export Opt-Out Words
Export opt-out words to a CSV file for backup, audit, or reporting purposes. You can export specific words or all words configured for your account.
/api/v1/contact/opt-out-word/export
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ids |
array | No | Array of opt-out word IDs to export (if omitted, exports all words) |
Example Request - Export Specific Words
curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word/export" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"ids": [1, 2, 3]
}'
Example Request - Export All Words
curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word/export" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Opt-out words exported successfully",
"data": {
"message": "Opt-out words exported successfully",
"file": "https://your-storage.digitaloceanspaces.com/opt-out-words/opt-out-words-2025-10-17_14-30-45.csv"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"ids": [
"The ids must be an array."
],
"ids.0": [
"The ids.0 must be an integer.",
"The selected ids.0 is invalid."
]
}
}
Export File Format
The exported CSV file typically includes:
- ID - Unique identifier
- Word - The opt-out keyword or phrase
- Created At - When the word was added
- Updated At - Last modification date
Example in JavaScript
async function exportOptOutWords(ids = null) {
const body = ids ? { ids } : {};
const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word/export', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(body)
});
const data = await response.json();
if (data.success) {
console.log('Export successful, downloading...');
// Trigger download
window.open(data.data.file, '_blank');
return data.data.file;
} else {
console.error('Export failed:', data.message);
throw new Error(data.message);
}
}
// Usage - Export specific opt-out words
exportOptOutWords([1, 2, 3]);
// Usage - Export all opt-out words
exportOptOutWords();
// Usage - Export all with fetched IDs
async function exportAllOptOutWords() {
const words = await getOptOutWords({ limit: 1000 });
const allIds = words.data.map(word => word.id);
await exportOptOutWords(allIds);
}
Important Notes
- File is stored on DigitalOcean Spaces (cloud storage)
- Download URL is publicly accessible - use immediately
- File name includes timestamp:
opt-out-words-YYYY-MM-DD_HH-MM-SS.csv - If
idsis omitted or empty, all words are exported - If any ID is invalid, the entire operation fails
- Export format is CSV (comma-separated values)
Use Cases
- Backup opt-out word configuration
- Audit trail for compliance documentation
- Share opt-out words with team members
- Migrate opt-out words to another system
- Review and analyze opt-out word effectiveness
Opt-Out Words Best Practices
Compliance and Legal Requirements
- TCPA Compliance (USA): Must honor "STOP" requests immediately
- CAN-SPAM Act: Must provide clear opt-out mechanism
- Response Time: Auto-blocking should happen instantly (within seconds)
- Confirmation: Send confirmation message after opt-out is processed
- Record Keeping: Export opt-out words regularly for compliance documentation
Essential Opt-Out Words to Configure
| Word/Phrase | Priority | Reason |
|---|---|---|
STOP |
Critical | Legally required by TCPA, most common opt-out word |
STOPALL |
Critical | Used by carriers to stop all messages from account |
UNSUBSCRIBE |
Critical | Email convention, widely recognized |
CANCEL |
High | Common alternative to STOP |
END |
High | Simple and clear opt-out word |
QUIT |
High | Common opt-out request |
OPT OUT |
Medium | Explicit two-word phrase |
REMOVE |
Medium | Common list removal request |
Implementation Workflow
// Complete opt-out words management system
class OptOutWordsManager {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1';
}
async getAll(options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 50
});
if (options.search) params.append('search', options.search);
return await this.apiCall('GET', `/contact/opt-out-word?${params}`);
}
async create(word) {
return await this.apiCall('POST', '/contact/opt-out-word', { word });
}
async update(id, word) {
return await this.apiCall('PUT', `/contact/opt-out-word${id}`, { word });
}
async delete(ids) {
return await this.apiCall('POST', '/contact/opt-out-worddelete', { ids });
}
async export(ids = null) {
const result = await this.apiCall('POST', '/contact/opt-out-wordexport',
ids ? { ids } : {}
);
if (result.success) {
window.open(result.data.file, '_blank');
}
return result;
}
async setupCompliance() {
// Configure all essential opt-out words for compliance
const essentialWords = [
'STOP',
'STOPALL',
'UNSUBSCRIBE',
'CANCEL',
'END',
'QUIT',
'OPT OUT',
'REMOVE'
];
console.log('Setting up compliance opt-out words...');
for (const word of essentialWords) {
try {
await this.create(word);
console.log(`✓ Added: ${word}`);
} catch (error) {
console.log(` Skipped ${word}: ${error.message}`);
}
}
console.log('Compliance setup complete!');
// Verify all words are configured
const current = await this.getAll({ limit: 100 });
console.log(`Total opt-out words configured: ${current.data.total}`);
return current;
}
async apiCall(method, endpoint, data = null) {
const headers = {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
};
if (data && method !== 'GET') {
headers['Content-Type'] = 'application/json';
}
const options = {
method,
headers,
body: data && method !== 'GET' ? JSON.stringify(data) : null
};
const response = await fetch(this.baseUrl + endpoint, options);
const result = await response.json();
if (!result.success) {
throw new Error(result.message);
}
return result;
}
}
// Usage
const optOutMgr = new OptOutWordsManager('SID....................................', 'PK.....................................');
// Setup compliance words
await optOutMgr.setupCompliance();
// Get all opt-out words
await optOutMgr.getAll();
// Add custom word
await optOutMgr.create('DO NOT CONTACT');
// Update word
await optOutMgr.update(7, 'STOP MESSAGING');
// Delete words
await optOutMgr.delete([7, 8]);
// Export all words for backup
await optOutMgr.export();
Testing Your Opt-Out Words
- Send a test message to a phone number you control
- Reply with each opt-out word (STOP, UNSUBSCRIBE, etc.)
- Verify the contact appears in blocked list with
blacklisted_by=null - Confirm no further messages can be sent to that contact
- Test with different capitalization (stop, STOP, Stop)
- Test with extra spaces or punctuation
Common Mistakes to Avoid
- ❌ Not configuring "STOP" - legally required and most common
- ❌ Deleting standard opt-out words - compliance violation
- ❌ Using overly broad words that trigger false positives
- ❌ Not testing opt-out functionality before going live
- ❌ Ignoring carrier-specific words like "STOPALL"
- ✅ Configure all standard opt-out words on day one
- ✅ Export opt-out words regularly for audit trail
- ✅ Test opt-out functionality with real messages
- ✅ Monitor blocked list for auto-blocked contacts
Monitoring and Maintenance
- Review opt-out words monthly to ensure compliance
- Monitor blocked list for patterns (high auto-block rate may indicate messaging issues)
- Export opt-out words quarterly for compliance documentation
- Update documentation when adding custom industry-specific opt-out words
- Train team members on proper opt-out word management
4. Inbox & Messaging
The Inbox & Messaging API provides a complete solution for managing SMS conversations with your contacts. View conversation threads, send and receive messages, manage templates, and track message status in real-time.
Key Features:
- View all conversation threads with contacts
- Get last active chat for quick access
- Manage message templates for quick replies
- List active phone numbers for sending messages
- Get available receiver numbers for new conversations
- View full conversation history with pagination
- Delete conversation threads
- Search and filter conversations
- Track unread messages
- Filter by folder and time range
4.1 Get Inbox Messages
Retrieve a paginated list of all conversation threads in your inbox. Each conversation shows the contact details, last message, unread count, and timestamp. Results can be searched, filtered by folder, time range, and unread status.
/api/v1/inbox
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
search |
string | No | Search by contact name, number, email, company, or message content |
folder |
integer | No | Filter by folder ID |
time |
string | No | Filter by time: today, last_week, last_month,
last_year
|
unread |
boolean | No | Show only conversations with unread messages (default: false) |
Example Request - Get All Conversations
curl -X GET "https://api.texttorrent.com/api/v1/inbox" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Search and Filter
curl -X GET "https://api.texttorrent.com/api/v1/inbox?search=john&folder=5&time=last_week&unread=true&limit=20" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Chats retrieved successfully",
"data": {
"current_page": 1,
"data": [
{
"chat_id": 1234,
"contact_id": 567,
"first_name": "John",
"last_name": "Doe",
"number": "+12025551234",
"email": "john.doe@example.com",
"company": "Acme Corp",
"folder_id": 5,
"last_message": "Thanks for the update!",
"last_chat_time": "2025-10-17T14:30:00.000000Z",
"unread_count": 3,
"send_by": "contact",
"avatar_ltr": "JD"
},
{
"chat_id": 1235,
"contact_id": 568,
"first_name": "Jane",
"last_name": "Smith",
"number": "+12025555678",
"email": "jane.smith@example.com",
"company": null,
"folder_id": null,
"last_message": "Got it, will check tomorrow",
"last_chat_time": "2025-10-17T13:15:00.000000Z",
"unread_count": 0,
"send_by": "me",
"avatar_ltr": "JS"
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/inbox?page=1",
"from": 1,
"last_page": 5,
"last_page_url": "https://api.texttorrent.com/api/v1/inbox?page=5",
"next_page_url": "https://api.texttorrent.com/api/v1/inbox?page=2",
"path": "https://api.texttorrent.com/api/v1/inbox",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 48
},
"errors": null
}
Conversation Fields
| Field | Type | Description |
|---|---|---|
chat_id |
integer | Unique identifier for the conversation thread |
contact_id |
integer | ID of the contact in this conversation |
last_message |
string | Most recent message content in the conversation |
last_chat_time |
string | Timestamp of the last message (ISO 8601 format) |
unread_count |
integer | Number of unread messages in this conversation |
send_by |
string | Who sent the last message: me or contact |
avatar_ltr |
string | Contact initials for avatar display (e.g., "JD") |
Example in JavaScript
async function getInboxMessages(options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
if (options.search) params.append('search', options.search);
if (options.folder) params.append('folder', options.folder);
if (options.time) params.append('time', options.time);
if (options.unread) params.append('unread', 'true');
const response = await fetch(
`https://api.texttorrent.com/api/v1/inbox?${params}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Conversations:', data.data.data);
console.log(`Total conversations: ${data.data.total}`);
// Count unread messages
const totalUnread = data.data.data.reduce((sum, chat) => sum + chat.unread_count, 0);
console.log(`Total unread messages: ${totalUnread}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get all conversations
getInboxMessages();
// Usage - Get unread conversations only
getInboxMessages({ unread: true });
// Usage - Search conversations
getInboxMessages({ search: 'john' });
// Usage - Filter by folder and time
getInboxMessages({ folder: 5, time: 'last_week' });
Time Filter Options
- today: Conversations with messages from today
- last_week: Conversations from the past 7 days
- last_month: Conversations from the past 30 days
- last_year: Conversations from the past 365 days
Important Notes
- Results are ordered by
last_chat_timein descending order (most recent first) - Search performs partial matching across multiple fields
- Blocked contacts are automatically excluded from results
- Conversations are grouped by contact (one thread per contact)
send_byindicates who sent the most recent message
4.2 Get Last Chat
Retrieve the most recently active conversation. This is useful for quickly resuming the last conversation you were engaged in, or for showing a "Continue last conversation" feature in your UI.
/api/v1/inbox/last-chat
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/inbox/last-chat" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Last chat retrieved successfully",
"data": {
"chat_id": 1234,
"contact_id": 567,
"contact_name": "John Doe",
"contact_phone_number": "+12025551234",
"last_message": "Thanks for the update!",
"last_chat_time": "2025-10-17T14:30:00.000000Z"
},
"errors": null
}
Success Response - No Chats (200 OK)
{
"code": 200,
"success": true,
"message": "Last chat retrieved successfully",
"data": null,
"errors": null
}
Example in JavaScript
async function getLastChat() {
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/last-chat', {
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
if (data.data) {
console.log('Last active chat:', data.data);
console.log(`Continue conversation with ${data.data.contact_name}`);
return data.data;
} else {
console.log('No previous conversations found');
return null;
}
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
const lastChat = await getLastChat();
if (lastChat) {
// Navigate to the conversation
window.location.href = `/inbox/${lastChat.chat_id}`;
}
Important Notes
- Returns the conversation with the most recent
updated_attimestamp - Excludes conversations with blocked contacts
- Returns
nullif no conversations exist - Useful for "Resume last conversation" functionality
4.3 Get Message Templates
Retrieve all message templates configured for your account. Templates allow you to save frequently used messages for quick replies. Search templates by name to find specific templates quickly.
/api/v1/inbox/templates
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search |
string | No | Search templates by name (partial match) |
Example Request - Get All Templates
curl -X GET "https://api.texttorrent.com/api/v1/inbox/templates" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Search Templates
curl -X GET "https://api.texttorrent.com/api/v1/inbox/templates?search=welcome" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Templates retrieved successfully",
"data": [
{
"id": 1,
"user_id": 1,
"template_name": "Welcome Message",
"status": 1,
"preview_message": "Welcome to our service! We're excited to have you on board.",
"created_at": "2025-10-01T10:30:00.000000Z"
},
{
"id": 2,
"user_id": 1,
"template_name": "Follow Up",
"status": 1,
"preview_message": "Just following up on our previous conversation. Let me know if you have any questions!",
"created_at": "2025-10-05T14:20:00.000000Z"
},
{
"id": 3,
"user_id": 1,
"template_name": "Thank You",
"status": 1,
"preview_message": "Thank you for your business! We appreciate your support.",
"created_at": "2025-10-10T09:15:00.000000Z"
}
],
"errors": null
}
Template Fields
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier for the template |
template_name |
string | Name/title of the template |
status |
integer | Template status: 1 = active, 0 = inactive |
preview_message |
string | The template message content |
created_at |
string | Timestamp when template was created (ISO 8601 format) |
Example in JavaScript
async function getMessageTemplates(searchTerm = null) {
const params = new URLSearchParams();
if (searchTerm) params.append('search', searchTerm);
const url = searchTerm
? `https://api.texttorrent.com/api/v1/inbox/templates?${params}`
: 'https://api.texttorrent.com/api/v1/inbox/templates';
const response = await fetch(url, {
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Message templates:', data.data);
// Display templates in a dropdown
const activeTemplates = data.data.filter(t => t.status === 1);
console.log(`${activeTemplates.length} active templates available`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get all templates
const templates = await getMessageTemplates();
// Usage - Search templates
const welcomeTemplates = await getMessageTemplates('welcome');
// Usage - Create template selector UI
function createTemplateSelector(templates) {
const select = document.createElement('select');
select.innerHTML = '';
templates.forEach(template => {
if (template.status === 1) {
const option = document.createElement('option');
option.value = template.preview_message;
option.textContent = template.template_name;
select.appendChild(option);
}
});
select.addEventListener('change', (e) => {
if (e.target.value) {
document.getElementById('message-input').value = e.target.value;
}
});
return select;
}
Important Notes
- Templates are user-specific (only shows templates you created)
- Search is case-insensitive and performs partial matching on template name
- Active templates have
status=1, inactive havestatus=0 - Returns all templates (no pagination)
- Use templates to save time on frequently sent messages
4.4 Get Active Numbers
Retrieve all active phone numbers in your account that can be used to send messages. Each number includes details about who purchased it, when it was purchased, and its capabilities.
/api/v1/inbox/numbers/active
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/inbox/numbers/active?limit=20" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Active numbers retrieved successfully",
"data": {
"current_page": 1,
"data": [
{
"id": 1,
"user_id": 1,
"purchased_by_user": "John Admin",
"purchased_by": 1,
"status": 1,
"created_at": "2025-09-15T10:30:00.000000Z",
"number": "+12025551234",
"friendly_name": "Sales Line",
"region": "DC",
"country": "US",
"latitude": "38.895",
"longitude": "-77.036",
"postal_code": "20001",
"capabilities": {
"voice": true,
"sms": true,
"mms": true
},
"purchased_at": "2025-09-15T10:30:00.000000Z",
"twilio_number_sid": "PNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"twilio_service_sid": "MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"type": "local"
},
{
"id": 2,
"user_id": 1,
"purchased_by_user": "Jane Manager",
"purchased_by": 5,
"status": 1,
"created_at": "2025-09-20T14:20:00.000000Z",
"number": "+12025555678",
"friendly_name": "Support Line",
"region": "DC",
"country": "US",
"latitude": "38.895",
"longitude": "-77.036",
"postal_code": "20001",
"capabilities": {
"voice": true,
"sms": true,
"mms": true
},
"purchased_at": "2025-09-20T14:20:00.000000Z",
"twilio_number_sid": "PNyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"twilio_service_sid": "MGyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"type": "local"
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/inbox/numbers/active?page=1",
"from": 1,
"last_page": 2,
"last_page_url": "https://api.texttorrent.com/api/v1/inbox/numbers/active?page=2",
"next_page_url": "https://api.texttorrent.com/api/v1/inbox/numbers/active?page=2",
"path": "https://api.texttorrent.com/api/v1/inbox/numbers/active",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 15
},
"errors": null
}
Number Fields
| Field | Type | Description |
|---|---|---|
number |
string | Phone number in E.164 format (e.g., +12025551234) |
friendly_name |
string | Custom name for the number (e.g., "Sales Line") |
status |
integer | Number status: 1 = active, 0 = inactive |
purchased_by_user |
string | Full name of user who purchased the number |
capabilities |
object | Number capabilities: voice, sms, mms |
type |
string | Number type: local, toll-free, mobile |
twilio_number_sid |
string | Twilio number SID |
Example in JavaScript
async function getActiveNumbers(options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
const response = await fetch(
`https://api.texttorrent.com/api/v1/inbox/numbers/active?${params}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Active numbers:', data.data.data);
console.log(`Total active numbers: ${data.data.total}`);
// Create sender number dropdown
const smsCapableNumbers = data.data.data.filter(n => n.capabilities.sms);
console.log(`${smsCapableNumbers.length} numbers can send SMS`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
const numbers = await getActiveNumbers();
// Usage - Create from-number selector
function createFromNumberSelector(numbers) {
const select = document.createElement('select');
select.innerHTML = '';
numbers.data.forEach(number => {
if (number.status === 1 && number.capabilities.sms) {
const option = document.createElement('option');
option.value = number.number;
option.textContent = `${number.friendly_name} (${number.number})`;
select.appendChild(option);
}
});
return select;
}
Important Notes
- Only returns numbers with
status=1(active) - Numbers must belong to your account (
user_idmatches) - Check
capabilities.smsto verify SMS capability - Use
numberfield as the "from" number when sending messages - Results are paginated (default 10 per page)
4.5 Get Receiver Numbers
Retrieve contacts who don't have an existing conversation thread yet. This is useful for starting new conversations - it shows contacts you can message but haven't messaged yet.
/api/v1/inbox/numbers/receiver
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/inbox/numbers/receiver" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Receiver numbers retrieved successfully",
"data": [
{
"id": 789,
"user_id": 1,
"first_name": "Alice",
"last_name": "Johnson",
"number": "+12025559876",
"email": "alice.johnson@example.com",
"company": "Tech Solutions",
"list_id": 10,
"folder_id": null,
"blacklisted": 0,
"created_at": "2025-10-15T10:30:00.000000Z",
"updated_at": "2025-10-15T10:30:00.000000Z"
},
{
"id": 790,
"user_id": 1,
"first_name": "Bob",
"last_name": "Williams",
"number": "+12025559877",
"email": "bob.williams@example.com",
"company": null,
"list_id": 10,
"folder_id": 3,
"blacklisted": 0,
"created_at": "2025-10-16T14:20:00.000000Z",
"updated_at": "2025-10-16T14:20:00.000000Z"
}
],
"errors": null
}
Example in JavaScript
async function getReceiverNumbers() {
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/numbers/receiver', {
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Available receivers:', data.data);
console.log(`${data.data.length} contacts available for new conversations`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get contacts for starting new conversations
const availableContacts = await getReceiverNumbers();
// Usage - Create "Start New Chat" contact selector
function createNewChatSelector(contacts) {
const select = document.createElement('select');
select.innerHTML = '';
contacts.forEach(contact => {
const option = document.createElement('option');
option.value = contact.id;
option.textContent = `${contact.first_name} ${contact.last_name} (${contact.number})`;
select.appendChild(option);
});
select.addEventListener('change', async (e) => {
if (e.target.value) {
// Start new conversation with selected contact
await startNewChat(e.target.value);
}
});
return select;
}
Important Notes
- Returns contacts WITHOUT existing conversation threads
- Limited to 30 contacts (no pagination)
- Useful for "Start New Chat" functionality
- Contacts must not be blacklisted
- Once you message a contact, they move to the inbox and won't appear in this list
4.6 Get Chat Details
Retrieve full details of a specific conversation including contact information, notes, and paginated message history. Messages are automatically marked as read when you view the conversation.
/api/v1/inbox/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Chat ID (conversation ID) |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of messages per page (default: 10) |
page |
integer | No | Page number for message pagination (default: 1) |
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/inbox/1234?limit=20" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Chat retrieved successfully",
"data": {
"chat": {
"id": 567,
"user_id": 1,
"first_name": "John",
"last_name": "Doe",
"number": "+12025551234",
"email": "john.doe@example.com",
"company": "Acme Corp",
"list_id": 10,
"folder_id": 5,
"blacklisted": 0,
"contact_id": 567,
"from_number": "+12025559999",
"chat_id": 1234,
"avatar_ltr": "JD",
"created_at": "2025-09-15T10:30:00.000000Z",
"updated_at": "2025-10-17T14:30:00.000000Z",
"notes": [
{
"id": 1,
"note": "VIP customer - priority support"
},
{
"id": 2,
"note": "Interested in premium features"
}
]
},
"messages": {
"current_page": 1,
"data": [
{
"id": 5001,
"chat_id": 1234,
"message": "Thanks for the update!",
"direction": "inbound",
"status": 1,
"from_number": "+12025551234",
"to_number": "+12025559999",
"media_url": null,
"created_at": "2025-10-17T14:30:00.000000Z",
"updated_at": "2025-10-17T14:30:05.000000Z"
},
{
"id": 5000,
"chat_id": 1234,
"message": "Your order has been shipped!",
"direction": "outbound",
"status": 1,
"from_number": "+12025559999",
"to_number": "+12025551234",
"media_url": null,
"created_at": "2025-10-17T14:25:00.000000Z",
"updated_at": "2025-10-17T14:25:05.000000Z"
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/inbox/1234?page=1",
"from": 1,
"last_page": 3,
"last_page_url": "https://api.texttorrent.com/api/v1/inbox/1234?page=3",
"next_page_url": "https://api.texttorrent.com/api/v1/inbox/1234?page=2",
"path": "https://api.texttorrent.com/api/v1/inbox/1234",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 28
}
},
"errors": null
}
Chat Not Found (200 OK)
{
"code": 200,
"success": true,
"message": "Chat not found",
"data": null,
"errors": null
}
Message Fields
| Field | Type | Description |
|---|---|---|
direction |
string | inbound (received) or outbound (sent) |
status |
integer | 0 = unread, 1 = read |
from_number |
string | Sender's phone number |
to_number |
string | Recipient's phone number |
media_url |
string|null | URL to media attachment (for MMS) |
Example in JavaScript
async function getChatDetails(chatId, options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
const response = await fetch(
`https://api.texttorrent.com/api/v1/inbox/${chatId}?${params}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success && data.data) {
const { chat, messages } = data.data;
console.log('Contact:', `${chat.first_name} ${chat.last_name}`);
console.log('Notes:', chat.notes);
console.log('Messages:', messages.data);
console.log(`Total messages: ${messages.total}`);
return data.data;
} else {
console.log('Chat not found');
return null;
}
}
// Usage
const chatDetails = await getChatDetails(1234);
// Usage - Load more messages (pagination)
const moreMessages = await getChatDetails(1234, { page: 2, limit: 20 });
// Usage - Display conversation UI
function displayConversation(chatData) {
if (!chatData) return;
const { chat, messages } = chatData;
// Display contact info
document.getElementById('contact-name').textContent =
`${chat.first_name} ${chat.last_name}`;
document.getElementById('contact-number').textContent = chat.number;
// Display notes
const notesContainer = document.getElementById('notes');
notesContainer.innerHTML = chat.notes
.map(note => `${note.note}`)
.join('');
// Display messages
const messagesContainer = document.getElementById('messages');
messagesContainer.innerHTML = messages.data
.reverse() // Show oldest first
.map(msg => `
`)
.join('');
}
Important Notes
- Messages are ordered by
created_at DESC(newest first in API response) - All unread messages are automatically marked as read when viewing
- Returns
nullif chat doesn't exist or doesn't belong to you - Contact notes are included in the response
- Messages support pagination (10 per page by default)
avatar_ltrprovides initials for avatar display
4.7 Delete Chat
Permanently delete a conversation thread and all associated messages. This removes the chat from your inbox but does not delete the contact record.
/api/v1/inbox/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Chat ID (conversation ID) to delete |
Example Request
curl -X DELETE "https://api.texttorrent.com/api/v1/inbox/1234" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Chat deleted successfully",
"data": null,
"errors": null
}
Example in JavaScript
async function deleteChat(chatId) {
// Confirm deletion
if (!confirm('Delete this conversation? All messages will be permanently removed.')) {
return;
}
const response = await fetch(`https://api.texttorrent.com/api/v1/inbox/${chatId}`, {
method: 'DELETE',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Chat deleted successfully');
alert('Conversation deleted');
// Redirect to inbox
window.location.href = '/inbox';
} else {
console.error('Error:', data.message);
alert('Failed to delete conversation');
}
}
// Usage
deleteChat(1234);
Important Notes
- Deletion is permanent and cannot be undone
- Deletes all messages in the conversation
- Deletes the chat record itself
- Does NOT delete the contact - they remain in your contacts list
- If the contact messages you again, a new conversation will be created
Inbox & Messaging Best Practices
Conversation Management
- Use Filters: Leverage folder and time filters to organize conversations
- Monitor Unread: Use
unread=truefilter to track pending messages - Search Effectively: Search works across names, numbers, emails, and message content
- Templates: Create templates for frequently sent messages to save time
Performance Optimization
- Use pagination to limit data transfer (default 10 items per page)
- Implement infinite scroll for message history instead of loading all at once
- Cache active numbers list - it rarely changes
- Debounce search input to avoid excessive API calls
Real-Time Updates
For real-time message updates, implement polling or webhooks:
// Polling example - check for new messages every 10 seconds
let lastCheckTime = new Date();
async function pollNewMessages() {
const chats = await getInboxMessages({
time: 'today',
limit: 50
});
// Find conversations updated since last check
const newMessages = chats.data.filter(chat =>
new Date(chat.last_chat_time) > lastCheckTime
);
if (newMessages.length > 0) {
console.log(`${newMessages.length} new message(s)`);
// Update UI
refreshInbox();
// Play notification sound
playNotificationSound();
}
lastCheckTime = new Date();
}
// Poll every 10 seconds
setInterval(pollNewMessages, 10000);
Complete Inbox Management Example
class InboxManager {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1';
}
async getConversations(filters = {}) {
const params = new URLSearchParams(filters);
return await this.apiCall('GET', `/inbox?${params}`);
}
async getLastChat() {
return await this.apiCall('GET', '/inbox/last-chat');
}
async getTemplates(search = null) {
const params = search ? `?search=${search}` : '';
return await this.apiCall('GET', `/inbox/templates${params}`);
}
async getActiveNumbers() {
return await this.apiCall('GET', '/inbox/numbers/active');
}
async getReceiverNumbers() {
return await this.apiCall('GET', '/inbox/numbers/receiver');
}
async getChatDetails(chatId, page = 1, limit = 10) {
return await this.apiCall('GET', `/inbox/${chatId}?page=${page}&limit=${limit}`);
}
async deleteChat(chatId) {
return await this.apiCall('DELETE', `/inbox/${chatId}`);
}
async apiCall(method, endpoint, data = null) {
const headers = {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
};
if (data && method !== 'GET') {
headers['Content-Type'] = 'application/json';
}
const options = {
method,
headers,
body: data && method !== 'GET' ? JSON.stringify(data) : null
};
const response = await fetch(this.baseUrl + endpoint, options);
const result = await response.json();
if (!result.success) {
throw new Error(result.message);
}
return result;
}
}
// Usage
const inbox = new InboxManager('SID....................................', 'PK.....................................');
// Get unread conversations
const unreadChats = await inbox.getConversations({ unread: true });
// Resume last conversation
const lastChat = await inbox.getLastChat();
if (lastChat.data) {
const chatDetails = await inbox.getChatDetails(lastChat.data.chat_id);
}
// Get templates for quick replies
const templates = await inbox.getTemplates();
// Delete old conversations
await inbox.deleteChat(1234);
UI/UX Recommendations
- Show unread count badge on conversation list items
- Highlight conversations with unread messages
- Auto-scroll to newest message when opening conversation
- Show typing indicator for better user experience
- Display timestamp in relative format ("2 minutes ago")
- Group messages by date for easier navigation
- Show delivery/read status for sent messages
Common Mistakes to Avoid
- ❌ Loading all messages at once without pagination
- ❌ Not confirming before deleting conversations
- ❌ Ignoring unread message counts
- ❌ Not implementing search debouncing
- ❌ Forgetting to refresh conversation list after actions
- ✅ Use pagination for messages and conversations
- ✅ Implement confirmation dialogs for destructive actions
- ✅ Show clear visual indicators for unread messages
- ✅ Implement efficient search with debouncing
- ✅ Refresh data after send/delete operations
4.9 Send Message
Send an SMS or MMS message to a contact in an existing conversation. Supports both text messages and media attachments (MMS). Messages are automatically cleaned using AI to fix encoding issues, and credits are deducted based on message type and gateway.
/api/v1/inbox/chat
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: multipart/form-data
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
message |
string | Yes | Message content (max 5000 characters) |
chat_id |
integer | Yes | ID of the conversation thread |
from_number |
string | Yes | Sender phone number (must exist in your active numbers) |
to_number |
string | Yes | Recipient phone number |
chatFile |
file | No | Media file attachment for MMS (image, video, audio) |
Example Request - Send SMS
curl -X POST "https://api.texttorrent.com/api/v1/inbox/chat" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-F "message=Hello! How can I help you today?" \
-F "chat_id=1234" \
-F "from_number=+12025559999" \
-F "to_number=+12025551234"
Example Request - Send MMS with Media
curl -X POST "https://api.texttorrent.com/api/v1/inbox/chat" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-F "message=Check out this image!" \
-F "chat_id=1234" \
-F "from_number=+12025559999" \
-F "to_number=+12025551234" \
-F "chatFile=@/path/to/image.jpg"
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Message send successfully",
"data": {
"id": 5002,
"chat_id": 1234,
"direction": "outbound",
"message": "Hello! How can I help you today?",
"msg_type": "sms",
"file": null,
"api_send_status": "sent",
"msg_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"created_at": "2025-10-17T15:30:00.000000Z",
"updated_at": "2025-10-17T15:30:05.000000Z"
},
"errors": null
}
Error Response - Invalid Sender Number (400 Bad Request)
{
"code": 400,
"success": false,
"message": "Invalid sender number",
"data": null,
"errors": null
}
Error Response - Insufficient Credits (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "You do not have enough credits to perform this action.",
"data": null,
"errors": null
}
Error Response - Daily Limit Reached (403 Forbidden)
{
"code": 403,
"success": false,
"message": "You have reached your daily limit for sending messages.",
"data": null,
"errors": null
}
Error Response - Inactive Number (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Unable to send message. Sender number is not active!",
"data": null,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"message": ["The message field is required."],
"from_number": ["The selected from number is invalid."],
"chat_id": ["The chat id field is required."],
"to_number": ["The to number field is required."]
}
}
Example in JavaScript
async function sendMessage(chatId, message, fromNumber, toNumber, mediaFile = null) {
const formData = new FormData();
formData.append('chat_id', chatId);
formData.append('message', message);
formData.append('from_number', fromNumber);
formData.append('to_number', toNumber);
if (mediaFile) {
formData.append('chatFile', mediaFile);
}
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/chat', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
},
body: formData
});
const data = await response.json();
if (data.success) {
console.log('Message sent:', data.data);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Send SMS
await sendMessage(1234, 'Hello!', '+12025559999', '+12025551234');
// Usage - Send MMS with image
const fileInput = document.getElementById('file-input');
const file = fileInput.files[0];
await sendMessage(1234, 'Check this out!', '+12025559999', '+12025551234', file);
Credit Costs
| Gateway Type | SMS (per segment) | MMS |
|---|---|---|
| Own Gateway | 1 credit | 3 credits + SMS credits for text |
| Text Torrent Gateway | 3 credits | 7 credits + SMS credits for text |
Important Notes
- Messages are automatically cleaned using AI to fix encoding issues (like corrupted characters)
- SMS messages are segmented: 160 chars for GSM-7, 70 chars for UCS-2 (Unicode)
- Multi-segment messages cost credits per segment
- MMS costs more credits and requires media file upload
- Sub-accounts have daily sending limits
- Trial accounts append "- Powered by Text Torrent" to messages
- Auto-recharge triggers if enabled when credits are low
- Sender number must be active and belong to your account
4.10 Start New Chat
Create a new conversation thread with a contact. If the contact doesn't exist in your contact list, they will be automatically created. This is useful for initiating conversations with new or existing contacts.
/api/v1/inbox/chat/create
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
receiver_number |
string | Yes | Receiver's phone number (10 digits, without +1) |
sender_id |
string | Yes | Sender phone number (your active number) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/inbox/chat/create" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"receiver_number": "2025551234",
"sender_id": "+12025559999"
}'
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Chat started successfully.",
"data": {
"id": 1235,
"user_id": 1,
"contact_id": 568,
"from_number": "+12025559999",
"last_message": null,
"created_at": "2025-10-17T15:45:00.000000Z",
"updated_at": "2025-10-17T15:45:00.000000Z"
},
"errors": null
}
Error Response - Contact Blacklisted (404 Not Found)
{
"code": 404,
"success": false,
"message": "This contact is blacklisted.",
"data": null,
"errors": null
}
Error Response - Chat Already Exists (404 Not Found)
{
"code": 404,
"success": false,
"message": "You have already started a chat with this contact.",
"data": null,
"errors": null
}
Example in JavaScript
async function startNewChat(receiverNumber, senderNumber) {
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/chat/create', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
receiver_number: receiverNumber,
sender_id: senderNumber
})
});
const data = await response.json();
if (data.success) {
console.log('Chat created:', data.data);
// Navigate to the new chat
window.location.href = `/inbox/${data.data.id}`;
return data.data;
} else {
console.error('Error:', data.message);
alert(data.message);
}
}
// Usage - Start chat with new number
await startNewChat('2025551234', '+12025559999');
Important Notes
- Phone number is automatically prefixed with +1 (US numbers)
- If contact doesn't exist, a new contact record is created
- Cannot create chat with blacklisted contacts
- Prevents duplicate chats - returns error if chat already exists
- Chat is created empty - use "Send Message" endpoint to send first message
4.11 Generate AI Replies
Generate 5-7 AI-powered reply suggestions based on the conversation history. The AI analyzes the tone, context, and recent messages to provide natural, contextually appropriate responses. Useful for quick replies and maintaining conversation flow.
/api/v1/inbox/generate/ai/response
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
chat_id |
integer | Yes | ID of the conversation (must exist in chats table) |
direction |
string | Yes | Message direction: inbound or outbound |
message |
string | Yes | The last message content for context |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/inbox/generate/ai/response" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"chat_id": 1234,
"direction": "inbound",
"message": "When will my order arrive?"
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "AI replies generated successfully.",
"data": [
"Your order should arrive by Friday. I'll send you tracking info shortly!",
"Great question! Let me check the tracking for you right now.",
"It's on the way! Expected delivery is this Friday.",
"I'll look up your order status and get back to you in a moment.",
"Your package is in transit and should arrive within 2-3 business days.",
"Let me pull up your order details to give you an accurate ETA.",
"I see it's scheduled for Friday delivery. Would you like the tracking number?"
],
"errors": null
}
Error Response - No Chat History (404 Not Found)
{
"code": 404,
"success": false,
"message": "No chat history found for this chat.",
"data": null,
"errors": null
}
Error Response - Non-Conversational Message (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "No suitable replies generated. The last message may be too short or passive",
"data": null,
"errors": null
}
Error Response - Insufficient Credits (404 Not Found)
{
"code": 404,
"success": false,
"message": "You do not have enough credits to perform this action.",
"data": null,
"errors": null
}
Example in JavaScript
async function generateAiReplies(chatId, direction, message) {
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/generate/ai/response', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
chat_id: chatId,
direction: direction,
message: message
})
});
const data = await response.json();
if (data.success) {
console.log('AI suggestions:', data.data);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get AI reply suggestions
const suggestions = await generateAiReplies(1234, 'inbound', 'When will my order arrive?');
// Display suggestions in UI
function displaySuggestions(suggestions) {
const container = document.getElementById('suggestions');
container.innerHTML = '';
suggestions.forEach((suggestion, index) => {
const button = document.createElement('button');
button.className = 'suggestion-btn';
button.textContent = suggestion;
button.onclick = () => {
document.getElementById('message-input').value = suggestion;
};
container.appendChild(button);
});
}
displaySuggestions(suggestions);
Credit Costs
| Gateway Type | Cost per Request |
|---|---|
| Own Gateway | 3 credits |
| Text Torrent Gateway | 6 credits |
Important Notes
- AI analyzes full conversation history for context
- Returns 5-7 unique reply suggestions
- Suggestions match the tone and style of the conversation
- Filters out passive/short messages (e.g., "ok", "thanks") that don't need replies
- Uses GPT-4o for high-quality, contextual responses
- Credits are deducted per API call, not per suggestion
- Auto-recharge triggers if enabled when credits are low
- Requires active subscription
4.12 Block Contact
Add a contact to your blocked list. Blocked contacts cannot send you messages, and their conversations are hidden from your inbox. Useful for preventing unwanted communications.
/api/v1/inbox/blacklist/{contactId}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contactId |
integer | Yes | ID of the contact to block |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/inbox/blacklist/567" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact blacklisted successfully",
"data": null,
"errors": null
}
Example in JavaScript
async function blockContact(contactId) {
if (!confirm('Block this contact? They will not be able to message you.')) {
return;
}
const response = await fetch(`https://api.texttorrent.com/api/v1/inbox/blacklist/${contactId}`, {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Contact blocked successfully');
alert('Contact has been blocked');
// Refresh inbox list
window.location.reload();
} else {
console.error('Error:', data.message);
alert('Failed to block contact');
}
}
// Usage
blockContact(567);
Important Notes
- Blocked contacts are excluded from inbox listings
- Existing conversation remains but is hidden
- Records who blocked the contact and when
- Blocked contacts can be unblocked later
- No credits are deducted for blocking
4.13 Unblock Contact
Remove one or more contacts from your blocked list. Unblocked contacts can send you messages again, and their conversations will reappear in your inbox. Supports bulk unblocking.
/api/v1/inbox/unblock
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_ids |
array | Yes | Array of contact IDs to unblock (minimum 1) |
Example Request - Unblock Single Contact
curl -X POST "https://api.texttorrent.com/api/v1/inbox/unblock" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"contact_ids": [567]
}'
Example Request - Unblock Multiple Contacts
curl -X POST "https://api.texttorrent.com/api/v1/inbox/unblock" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"contact_ids": [567, 568, 569]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact unblocked successfully",
"data": null,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"contact_ids": ["The contact ids field is required."],
"contact_ids.0": ["The selected contact ids.0 is invalid."]
}
}
Example in JavaScript
async function unblockContacts(contactIds) {
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/unblock', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
contact_ids: contactIds
})
});
const data = await response.json();
if (data.success) {
console.log('Contacts unblocked successfully');
alert(`${contactIds.length} contact(s) have been unblocked`);
return true;
} else {
console.error('Error:', data.message);
alert('Failed to unblock contacts');
return false;
}
}
// Usage - Unblock single contact
await unblockContacts([567]);
// Usage - Unblock multiple contacts
await unblockContacts([567, 568, 569]);
// Usage - Bulk unblock from checkbox selection
function bulkUnblock() {
const checkboxes = document.querySelectorAll('.contact-checkbox:checked');
const contactIds = Array.from(checkboxes).map(cb => parseInt(cb.value));
if (contactIds.length === 0) {
alert('Please select contacts to unblock');
return;
}
if (confirm(`Unblock ${contactIds.length} contact(s)?`)) {
unblockContacts(contactIds);
}
}
Important Notes
- Supports bulk unblocking (multiple contacts at once)
- Resets all blacklist-related fields (blacklisted_by, blacklisted_by_word, blacklisted_at)
- Conversations with unblocked contacts reappear in inbox
- Contact IDs must exist in your contacts
- No credits are deducted for unblocking
4.14 Add Event
Create a scheduled event or reminder related to a conversation. Events can trigger alerts at a specified time before the event, helping you stay on top of follow-ups, appointments, and important dates.
/api/v1/inbox/event/add
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Event name/title |
subject |
string | Yes | Event subject/description |
date |
string | Yes | Event date (YYYY-MM-DD format) |
time |
string | Yes | Event time (HH:MM format, 24-hour) |
sender_number |
string | Yes | Phone number to send reminder from |
alert_before |
integer | Yes | Minutes before event to send alert |
participant_number |
string | No | Participant phone number |
participant_email |
string | No | Participant email address (must be valid email format) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/inbox/event/add" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "Follow-up Call",
"subject": "Discuss product demo with client",
"date": "2025-10-20",
"time": "14:00",
"sender_number": "+12025559999",
"alert_before": 30,
"participant_number": "+12025551234",
"participant_email": "client@example.com"
}'
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Event created successfully",
"data": {
"id": 123,
"name": "Follow-up Call",
"subject": "Discuss product demo with client",
"date": "2025-10-20",
"time": "14:00",
"sender_number": "+12025559999",
"alert_before": 30,
"alert_at": "2025-10-20 13:30:00",
"receiver_number": "+12025551111",
"participant_number": "+12025551234",
"participant_email": "client@example.com",
"user_id": 1,
"created_at": "2025-10-17T15:30:00.000000Z",
"updated_at": "2025-10-17T15:30:00.000000Z"
},
"errors": null
}
Example in JavaScript
async function addEvent(eventData) {
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/event/add', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(eventData)
});
const data = await response.json();
if (data.success) {
console.log('Event created:', data.data);
alert('Event scheduled successfully');
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Create follow-up event
await addEvent({
name: 'Follow-up Call',
subject: 'Discuss product demo with client',
date: '2025-10-20',
time: '14:00',
sender_number: '+12025559999',
alert_before: 30,
participant_number: '+12025551234',
participant_email: 'client@example.com'
});
// Usage - Create event from form
function scheduleEventFromForm() {
const form = document.getElementById('event-form');
const formData = new FormData(form);
const eventData = {
name: formData.get('name'),
subject: formData.get('subject'),
date: formData.get('date'),
time: formData.get('time'),
sender_number: formData.get('sender_number'),
alert_before: parseInt(formData.get('alert_before')),
participant_number: formData.get('participant_number'),
participant_email: formData.get('participant_email')
};
addEvent(eventData);
}
Important Notes
- Alert time is automatically calculated:
event_time - alert_before - Receiver number defaults to your account phone number
- Events can be used for follow-ups, appointments, reminders
- Alert is sent via SMS at the calculated alert time
- Date must be in YYYY-MM-DD format (e.g., 2025-10-20)
- Time must be in 24-hour format (e.g., 14:00 for 2:00 PM)
- Participant fields are optional but useful for tracking
4.15 Export Inbox
Export all your inbox conversations to a CSV file. The export includes all contacts you've had conversations with (excluding blacklisted contacts). Useful for backups, data analysis, or importing into other systems.
/api/v1/inbox/export
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/inbox/export" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Exported successfully",
"data": {
"path": "inbox/chats_2025_10_17_15_30_45.csv",
"url": "https://your-cdn.com/inbox/chats_2025_10_17_15_30_45.csv"
},
"errors": null
}
Example in JavaScript
async function exportInbox() {
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/export', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Export successful:', data.data);
// Download the file
const link = document.createElement('a');
link.href = data.data.url;
link.download = 'inbox_export.csv';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
alert('Inbox exported successfully');
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
exportInbox();
Important Notes
- Exports all contacts with conversation history
- Excludes blacklisted contacts
- File is stored on cloud storage (DigitalOcean Spaces)
- Filename includes timestamp for uniqueness
- CSV includes contact IDs for reference
- No credits are deducted for exporting
- File remains accessible via the provided URL
4.16 Move Contact to Folder
Organize your inbox by moving a contact to a specific folder. This helps categorize conversations for better organization and filtering.
/api/v1/inbox/to-folder/create
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id |
integer | Yes | ID of the contact to move (must exist in contacts table) |
folder_id |
integer | Yes | ID of the destination folder |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/inbox/to-folder/create" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 567,
"folder_id": 5
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Contact moved to folder successfully.",
"data": {
"id": 567,
"user_id": 1,
"first_name": "John",
"last_name": "Doe",
"number": "+12025551234",
"email": "john.doe@example.com",
"company": "Acme Corp",
"folder_id": 5,
"created_at": "2025-09-15T10:30:00.000000Z",
"updated_at": "2025-10-17T15:45:00.000000Z"
},
"errors": null
}
Example in JavaScript
async function moveContactToFolder(contactId, folderId) {
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/to-folder/create', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
contact_id: contactId,
folder_id: folderId
})
});
const data = await response.json();
if (data.success) {
console.log('Contact moved:', data.data);
alert('Contact moved to folder successfully');
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
await moveContactToFolder(567, 5);
// Usage - Create folder selector dropdown
function createFolderMoveAction(folders, contactId) {
const select = document.createElement('select');
select.innerHTML = '';
folders.forEach(folder => {
const option = document.createElement('option');
option.value = folder.id;
option.textContent = folder.name;
select.appendChild(option);
});
select.addEventListener('change', async (e) => {
if (e.target.value) {
await moveContactToFolder(contactId, parseInt(e.target.value));
}
});
return select;
}
Important Notes
- Contact must exist in your contacts
- Updates the contact's
folder_idfield - Use with "List Contact Folders" endpoint to get available folders
- Helps organize conversations by category (e.g., Clients, Leads, Support)
- No credits are deducted for moving contacts
4.17 Add Inbox Note
Add a note to a contact from the inbox. Notes help you track important information, context, or reminders about specific contacts. Same functionality as Contact Notes but accessible from the inbox interface.
/api/v1/inbox/note/add
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id |
integer | Yes | ID of the contact to add note to |
note |
string | Yes | Note content |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/inbox/note/add" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 567,
"note": "VIP customer - priority support. Interested in enterprise plan."
}'
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Note added successfully.",
"data": {
"id": 123,
"contact_id": 567,
"note": "VIP customer - priority support. Interested in enterprise plan.",
"created_at": "2025-10-17T15:50:00.000000Z",
"updated_at": "2025-10-17T15:50:00.000000Z"
},
"errors": null
}
Example in JavaScript
async function addInboxNote(contactId, noteText) {
const response = await fetch('https://api.texttorrent.com/api/v1/inbox/note/add', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
contact_id: contactId,
note: noteText
})
});
const data = await response.json();
if (data.success) {
console.log('Note added:', data.data);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
await addInboxNote(567, 'VIP customer - priority support');
Important Notes
- Same as Contact Notes API, accessible from inbox context
- Notes are displayed in chat details
- Useful for tracking customer preferences, special instructions, or follow-up items
- Multiple notes can be added to a single contact
- No credits are deducted for adding notes
4.18 Delete Inbox Note
Remove a note from a contact. This permanently deletes the note from the contact record.
/api/v1/inbox/note/delete/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | ID of the note to delete |
Example Request
curl -X DELETE "https://api.texttorrent.com/api/v1/inbox/note/delete/123" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Note deleted successfully.",
"data": {
"id": 123,
"contact_id": 567,
"note": "VIP customer - priority support. Interested in enterprise plan.",
"created_at": "2025-10-17T15:50:00.000000Z",
"updated_at": "2025-10-17T15:50:00.000000Z"
},
"errors": null
}
Example in JavaScript
async function deleteInboxNote(noteId) {
if (!confirm('Delete this note?')) {
return;
}
const response = await fetch(`https://api.texttorrent.com/api/v1/inbox/note/delete/${noteId}`, {
method: 'DELETE',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success) {
console.log('Note deleted successfully');
return true;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
await deleteInboxNote(123);
Important Notes
- Deletion is permanent and cannot be undone
- Returns the deleted note data in the response
- No credits are deducted for deleting notes
5. Sub-Account Management
The Sub-Account Management API allows you to create and manage sub-users under your main account. Sub-accounts can have limited permissions, daily SMS limits, and role-based access control. This is perfect for teams, agencies, or organizations that need multiple users with controlled access.
Key Features:
- Create and manage unlimited sub-accounts
- Set daily SMS sending limits per sub-account
- Assign roles and permissions
- Enable/disable sub-account access
- Login as sub-user for support purposes
- Search and filter sub-accounts
- Paginated sub-account listings
5.1 List Sub-Accounts
Retrieve a paginated list of all sub-accounts under your main account. Results can be searched by name and sorted by various fields. Each sub-account includes their role information.
/api/v1/user/sub-account/list
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
search |
string | No | Search by first name or last name |
sortBy |
string | No | Field to sort by (default: id) |
sortType |
string | No | Sort direction: asc or desc (default: desc) |
Example Request - Get All Sub-Accounts
curl -X GET "https://api.texttorrent.com/api/v1/user/sub-account/list" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Search and Sort
curl -X GET "https://api.texttorrent.com/api/v1/user/sub-account/list?search=john&sortBy=first_name&sortType=asc&limit=20" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Retrieved successfully",
"data": {
"current_page": 1,
"data": [
{
"id": 101,
"first_name": "John",
"last_name": "Smith",
"username": "johnsmith",
"email": "john.smith@example.com",
"type": "sub",
"parent_id": 1,
"status": 1,
"role": 2,
"permissions": ["view_contacts", "send_messages"],
"daily_sms_limit": 500,
"admin_approval": 1,
"created_at": "2025-09-15T10:30:00.000000Z",
"updated_at": "2025-10-17T14:20:00.000000Z",
"role": {
"id": 2,
"name": "Sales Agent"
}
},
{
"id": 102,
"first_name": "Sarah",
"last_name": "Johnson",
"username": "sarahjohnson",
"email": "sarah.johnson@example.com",
"type": "sub",
"parent_id": 1,
"status": 1,
"role": 3,
"permissions": ["view_contacts", "send_messages", "view_analytics"],
"daily_sms_limit": 1000,
"admin_approval": 1,
"created_at": "2025-09-20T11:15:00.000000Z",
"updated_at": "2025-10-16T09:30:00.000000Z",
"role": {
"id": 3,
"name": "Marketing Manager"
}
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/user/sub-account/list?page=1",
"from": 1,
"last_page": 3,
"last_page_url": "https://api.texttorrent.com/api/v1/user/sub-account/list?page=3",
"next_page_url": "https://api.texttorrent.com/api/v1/user/sub-account/list?page=2",
"path": "https://api.texttorrent.com/api/v1/user/sub-account/list",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 25
},
"errors": null
}
Sub-Account Fields
| Field | Type | Description |
|---|---|---|
type |
string | Always "sub" for sub-accounts |
parent_id |
integer | ID of the parent account (your account) |
status |
integer | Account status: 1 = active, 0 = inactive |
permissions |
array | List of permission strings assigned to this sub-account |
daily_sms_limit |
integer | Maximum SMS messages this sub-account can send per day |
admin_approval |
integer | Admin approval status: 1 = approved, 0 = pending |
Example in JavaScript
async function getSubAccounts(options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
if (options.search) params.append('search', options.search);
if (options.sortBy) params.append('sortBy', options.sortBy);
if (options.sortType) params.append('sortType', options.sortType);
const response = await fetch(
`https://api.texttorrent.com/api/v1/user/sub-account/list?${params}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Sub-accounts:', data.data.data);
console.log(`Total sub-accounts: ${data.data.total}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get all sub-accounts
const subAccounts = await getSubAccounts();
// Usage - Search sub-accounts
const searchResults = await getSubAccounts({ search: 'john' });
// Usage - Sort by name ascending
const sorted = await getSubAccounts({ sortBy: 'first_name', sortType: 'asc' });
Important Notes
- Only returns sub-accounts where
parent_idmatches your user ID - Search performs partial matching on first name and last name
- Results include role information via relationship
- Default sorting is by ID in descending order (newest first)
5.2 Create Sub-Account
Create a new sub-account under your main account. The sub-account will receive a welcome email with their login credentials. You can set permissions, role, and daily SMS limits during creation.
/api/v1/user/sub-account/store
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
first_name |
string | Yes | First name (max 20 characters) |
last_name |
string | Yes | Last name (max 20 characters) |
username |
string | Yes | Unique username for login |
email |
string | Yes | Unique email address |
password |
string | Yes | Password (minimum 6 characters) |
confirm_password |
string | Yes | Must match password |
role |
integer | Yes | Role ID (must exist in user_roles table) |
permissions |
array | No | Array of permission strings |
sms_limit |
integer | No | Daily SMS limit (default: 0 = unlimited) |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/user/sub-account/store" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Smith",
"username": "johnsmith",
"email": "john.smith@example.com",
"password": "SecurePass123",
"confirm_password": "SecurePass123",
"role": 2,
"permissions": ["view_contacts", "send_messages", "view_inbox"],
"sms_limit": 500
}'
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Sub Account Created Successfully",
"data": {
"id": 103,
"first_name": "John",
"last_name": "Smith",
"username": "johnsmith",
"email": "john.smith@example.com",
"type": "sub",
"parent_id": 1,
"status": 1,
"role": 2,
"permissions": ["view_contacts", "send_messages", "view_inbox"],
"daily_sms_limit": 500,
"admin_approval": 1,
"created_at": "2025-10-17T16:00:00.000000Z",
"updated_at": "2025-10-17T16:00:00.000000Z"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"username": ["The username has already been taken."],
"email": ["The email has already been taken."],
"password": ["The password must be at least 6 characters."],
"confirm_password": ["The confirm password and password must match."],
"role": ["The selected role is invalid."]
}
}
Example in JavaScript
async function createSubAccount(accountData) {
const response = await fetch('https://api.texttorrent.com/api/v1/user/sub-account/store', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(accountData)
});
const data = await response.json();
if (data.success) {
console.log('Sub-account created:', data.data);
alert('Sub-account created successfully! Welcome email sent.');
return data.data;
} else {
console.error('Error:', data.message, data.errors);
throw new Error(data.message);
}
}
// Usage
await createSubAccount({
first_name: 'John',
last_name: 'Smith',
username: 'johnsmith',
email: 'john.smith@example.com',
password: 'SecurePass123',
confirm_password: 'SecurePass123',
role: 2,
permissions: ['view_contacts', 'send_messages', 'view_inbox'],
sms_limit: 500
});
Important Notes
- Username and email must be unique across all users
- Welcome email is automatically sent to the sub-account
- Sub-account is automatically approved (
admin_approval=1) - Sub-account is created with active status (
status=1) - Password is securely hashed before storage
- Daily SMS limit of 0 means unlimited sending
- Sub-accounts share your main account's credits
5.3 Get Sub-Account Details
Retrieve detailed information about a specific sub-account. Useful for viewing full sub-account profile before editing or for displaying sub-account information in your UI.
/api/v1/user/sub-account/show/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Sub-account user ID |
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/user/sub-account/show/101" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Sub Account Retrieved Successfully",
"data": {
"id": 101,
"first_name": "John",
"last_name": "Smith",
"username": "johnsmith",
"email": "john.smith@example.com",
"type": "sub",
"parent_id": 1,
"status": 1,
"role": 2,
"permissions": ["view_contacts", "send_messages", "view_inbox"],
"daily_sms_limit": 500,
"admin_approval": 1,
"created_at": "2025-09-15T10:30:00.000000Z",
"updated_at": "2025-10-17T14:20:00.000000Z"
},
"errors": null
}
Error Response - Not Found (404 Not Found)
{
"code": 404,
"success": false,
"message": "Sub Account Not Found",
"data": [],
"errors": null
}
Example in JavaScript
async function getSubAccountDetails(subAccountId) {
const response = await fetch(
`https://api.texttorrent.com/api/v1/user/sub-account/show/${subAccountId}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Sub-account details:', data.data);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
const subAccount = await getSubAccountDetails(101);
Important Notes
- Only returns sub-accounts that belong to your account (
parent_idcheck) - Returns 404 if sub-account doesn't exist or doesn't belong to you
- Useful for pre-populating edit forms
5.4 Update Sub-Account
Update an existing sub-account's information including name, email, username, password, permissions, role, and daily SMS limit. Password update is optional.
/api/v1/user/sub-account/update/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Sub-account user ID to update |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
first_name |
string | Yes | First name (max 20 characters) |
last_name |
string | Yes | Last name (max 20 characters) |
username |
string | Yes | Username (unique, except current user) |
email |
string | Yes | Email address (unique, except current user) |
password |
string | No | New password (minimum 6 characters) |
confirm_password |
string | No* | Required if password is provided, must match password |
role |
integer | Yes | Role ID (must exist in user_roles table) |
permissions |
array | No | Array of permission strings |
sms_limit |
integer | No | Daily SMS limit (default: 0 = unlimited) |
Example Request - Update Without Password
curl -X PUT "https://api.texttorrent.com/api/v1/user/sub-account/update/101" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Smith",
"username": "johnsmith",
"email": "john.smith@example.com",
"role": 2,
"permissions": ["view_contacts", "send_messages", "view_inbox", "view_analytics"],
"sms_limit": 1000
}'
Example Request - Update With Password
curl -X PUT "https://api.texttorrent.com/api/v1/user/sub-account/update/101" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Smith",
"username": "johnsmith",
"email": "john.smith@example.com",
"password": "NewSecurePass456",
"confirm_password": "NewSecurePass456",
"role": 2,
"permissions": ["view_contacts", "send_messages", "view_inbox"],
"sms_limit": 1000
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Sub Account Updated Successfully",
"data": {
"id": 101,
"first_name": "John",
"last_name": "Smith",
"username": "johnsmith",
"email": "john.smith@example.com",
"type": "sub",
"parent_id": 1,
"status": 1,
"role": 2,
"permissions": ["view_contacts", "send_messages", "view_inbox", "view_analytics"],
"daily_sms_limit": 1000,
"admin_approval": 1,
"created_at": "2025-09-15T10:30:00.000000Z",
"updated_at": "2025-10-17T16:15:00.000000Z"
},
"errors": null
}
Error Response - Not Found (404 Not Found)
{
"code": 404,
"success": false,
"message": "Sub Account Not Found",
"data": [],
"errors": null
}
Example in JavaScript
async function updateSubAccount(subAccountId, accountData) {
const response = await fetch(
`https://api.texttorrent.com/api/v1/user/sub-account/update/${subAccountId}`,
{
method: 'PUT',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(accountData)
}
);
const data = await response.json();
if (data.success) {
console.log('Sub-account updated:', data.data);
alert('Sub-account updated successfully!');
return data.data;
} else {
console.error('Error:', data.message, data.errors);
throw new Error(data.message);
}
}
// Usage - Update without password
await updateSubAccount(101, {
first_name: 'John',
last_name: 'Smith',
username: 'johnsmith',
email: 'john.smith@example.com',
role: 2,
permissions: ['view_contacts', 'send_messages', 'view_inbox', 'view_analytics'],
sms_limit: 1000
});
// Usage - Update with password
await updateSubAccount(101, {
first_name: 'John',
last_name: 'Smith',
username: 'johnsmith',
email: 'john.smith@example.com',
password: 'NewSecurePass456',
confirm_password: 'NewSecurePass456',
role: 2,
permissions: ['view_contacts', 'send_messages'],
sms_limit: 1000
});
Important Notes
- Password field is optional - omit to keep existing password
- Username and email must be unique (excluding current user)
- Password is securely hashed if provided
- All fields except password are required
- Returns 404 if sub-account doesn't exist or doesn't belong to you
5.5 Delete Sub-Account
Permanently delete a sub-account from your account. This action cannot be undone. All data associated with the sub-account will be removed.
/api/v1/user/sub-account/delete/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Sub-account user ID to delete |
Example Request
curl -X DELETE "https://api.texttorrent.com/api/v1/user/sub-account/delete/101" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Sub Account Deleted Successfully",
"data": [],
"errors": null
}
Error Response - Not Found (404 Not Found)
{
"code": 404,
"success": false,
"message": "Sub Account Not Found",
"data": [],
"errors": null
}
Example in JavaScript
async function deleteSubAccount(subAccountId) {
if (!confirm('Delete this sub-account? This action cannot be undone.')) {
return;
}
const response = await fetch(
`https://api.texttorrent.com/api/v1/user/sub-account/delete/${subAccountId}`,
{
method: 'DELETE',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Sub-account deleted successfully');
alert('Sub-account has been deleted');
return true;
} else {
console.error('Error:', data.message);
alert('Failed to delete sub-account');
return false;
}
}
// Usage
await deleteSubAccount(101);
Important Notes
- Deletion is permanent and cannot be undone
- Only sub-accounts belonging to your account can be deleted
- Returns 404 if sub-account doesn't exist or doesn't belong to you
- Consider disabling instead of deleting if you might need the account later
5.6 Change Sub-Account Status
Enable or disable a sub-account. Disabled sub-accounts cannot log in or access the system. This is useful for temporarily suspending access without deleting the account.
/api/v1/user/sub-account/status/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Sub-account user ID |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
integer | Yes | New status: 1 = active, 0 = inactive |
Example Request - Disable Sub-Account
curl -X PATCH "https://api.texttorrent.com/api/v1/user/sub-account/status/101" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"status": 0
}'
Example Request - Enable Sub-Account
curl -X PATCH "https://api.texttorrent.com/api/v1/user/sub-account/status/101" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"status": 1
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Sub Account Status Updated Successfully",
"data": {
"id": 101,
"first_name": "John",
"last_name": "Smith",
"username": "johnsmith",
"email": "john.smith@example.com",
"type": "sub",
"parent_id": 1,
"status": 0,
"role": 2,
"permissions": ["view_contacts", "send_messages"],
"daily_sms_limit": 500,
"admin_approval": 1,
"created_at": "2025-09-15T10:30:00.000000Z",
"updated_at": "2025-10-17T16:30:00.000000Z"
},
"errors": null
}
Error Response - Not Found (404 Not Found)
{
"code": 404,
"success": false,
"message": "Sub Account Not Found",
"data": [],
"errors": null
}
Example in JavaScript
async function changeSubAccountStatus(subAccountId, status) {
const statusText = status === 1 ? 'enable' : 'disable';
if (!confirm(`Are you sure you want to ${statusText} this sub-account?`)) {
return;
}
const response = await fetch(
`https://api.texttorrent.com/api/v1/user/sub-account/status/${subAccountId}`,
{
method: 'PATCH',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ status })
}
);
const data = await response.json();
if (data.success) {
console.log('Status updated:', data.data);
alert(`Sub-account ${statusText}d successfully`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Disable sub-account
await changeSubAccountStatus(101, 0);
// Usage - Enable sub-account
await changeSubAccountStatus(101, 1);
// Usage - Toggle status
async function toggleSubAccountStatus(subAccount) {
const newStatus = subAccount.status === 1 ? 0 : 1;
return await changeSubAccountStatus(subAccount.id, newStatus);
}
Important Notes
- Status 1 = active (can log in), Status 0 = inactive (cannot log in)
- Inactive sub-accounts cannot access the system
- Use this instead of deleting if you want to temporarily suspend access
- Sub-account data is preserved when disabled
- Returns 404 if sub-account doesn't exist
5.7 Login as Sub-User
Generate an authentication token to log in as a sub-user. This is useful for support purposes, allowing you to view the system from the sub-account's perspective without knowing their password.
/api/v1/user/sub-account/login-as-sub-user/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Sub-account user ID to log in as |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/user/sub-account/login-as-sub-user/101" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Logged in as Sub Account Successfully",
"data": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
},
"errors": null
}
Error Response - Not Found (404 Not Found)
{
"code": 404,
"success": false,
"message": "Sub Account Not Found",
"data": [],
"errors": null
}
Example in JavaScript
async function loginAsSubUser(subAccountId) {
const response = await fetch(
`https://api.texttorrent.com/api/v1/user/sub-account/login-as-sub-user/${subAccountId}`,
{
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Sub-user token generated:', data.data.token);
// Store the token
localStorage.setItem('sub_user_token', data.data.token);
// Redirect to dashboard as sub-user
window.location.href = '/dashboard?as_sub_user=true';
return data.data.token;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
await loginAsSubUser(101);
// Usage - With confirmation
async function impersonateSubUser(subAccountId, subAccountName) {
if (confirm(`Log in as ${subAccountName}? You'll be viewing the system from their perspective.`)) {
const token = await loginAsSubUser(subAccountId);
console.log('Now viewing as sub-user');
}
}
Example in PHP
<?php
$subAccountId = 101;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.texttorrent.com/api/v1/user/sub-account/login-as-sub-user/{$subAccountId}");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Accept: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
$token = $data['data']['token'];
// Store token in session for sub-user access
$_SESSION['sub_user_token'] = $token;
$_SESSION['is_impersonating'] = true;
// Redirect to dashboard
header('Location: /dashboard');
exit;
} else {
echo "Error: " . $data['message'];
}
?>
Important Notes
- Generates a valid OAuth access token for the sub-account
- Token can be used for all API requests as that sub-user
- Only works for sub-accounts that belong to your account
- Use for support purposes only (troubleshooting, training, etc.)
- Consider logging impersonation events for security audit trails
- Token follows standard OAuth token expiration rules
Sub-Account Management Best Practices
Security
- Strong Passwords: Enforce minimum 6 characters (consider increasing for production)
- Unique Credentials: Each sub-account must have unique username and email
- Audit Trail: Log all sub-account creation, updates, and impersonation events
- Regular Review: Periodically review and disable unused sub-accounts
Permission Management
- Principle of Least Privilege: Grant only necessary permissions
- Role-Based Access: Use roles to group common permission sets
- Document Permissions: Clearly document what each permission allows
- Regular Audits: Review permissions quarterly
Daily Limits
- Set Appropriate Limits: Based on sub-account role and responsibility
- Monitor Usage: Track daily SMS usage per sub-account
- Adjust as Needed: Increase limits for productive users
- Zero for Unlimited: Use 0 for sub-accounts that need unlimited sending
Complete Sub-Account Management Example
class SubAccountManager {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1/user/sub-account';
}
async list(options = {}) {
const params = new URLSearchParams(options);
return await this.apiCall('GET', `/list?${params}`);
}
async create(accountData) {
return await this.apiCall('POST', '/store', accountData);
}
async get(id) {
return await this.apiCall('GET', `/show/${id}`);
}
async update(id, accountData) {
return await this.apiCall('PUT', `/update/${id}`, accountData);
}
async delete(id) {
return await this.apiCall('DELETE', `/delete/${id}`);
}
async changeStatus(id, status) {
return await this.apiCall('PATCH', `/status/${id}`, { status });
}
async loginAs(id) {
return await this.apiCall('POST', `/login-as-sub-user/${id}`);
}
async apiCall(method, endpoint, data = null) {
const headers = {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
};
if (data && method !== 'GET') {
headers['Content-Type'] = 'application/json';
}
const options = {
method,
headers,
body: data && method !== 'GET' ? JSON.stringify(data) : null
};
const response = await fetch(this.baseUrl + endpoint, options);
const result = await response.json();
if (!result.success) {
throw new Error(result.message);
}
return result;
}
}
// Usage
const subAccountMgr = new SubAccountManager('SID....................................', 'PK.....................................');
// List sub-accounts
const accounts = await subAccountMgr.list({ search: 'john' });
// Create new sub-account
const newAccount = await subAccountMgr.create({
first_name: 'Jane',
last_name: 'Doe',
username: 'janedoe',
email: 'jane.doe@example.com',
password: 'SecurePass123',
confirm_password: 'SecurePass123',
role: 2,
permissions: ['view_contacts', 'send_messages'],
sms_limit: 500
});
// Update sub-account
await subAccountMgr.update(101, {
first_name: 'John',
last_name: 'Smith',
username: 'johnsmith',
email: 'john.smith@example.com',
role: 2,
permissions: ['view_contacts', 'send_messages', 'view_analytics'],
sms_limit: 1000
});
// Disable sub-account
await subAccountMgr.changeStatus(101, 0);
// Login as sub-user
const token = await subAccountMgr.loginAs(101);
Common Mistakes to Avoid
- ❌ Sharing account credentials between multiple people
- ❌ Granting excessive permissions "just in case"
- ❌ Not setting daily limits for untested sub-accounts
- ❌ Forgetting to disable sub-accounts when employees leave
- ❌ Not logging impersonation events
- ✅ Create individual sub-accounts for each team member
- ✅ Use role-based permissions
- ✅ Set appropriate daily limits based on role
- ✅ Regularly audit and clean up unused accounts
- ✅ Log all administrative actions
Typical Roles and Permissions
| Role | Permissions | Daily Limit |
|---|---|---|
| Sales Agent | view_contacts, send_messages, view_inbox | 500 |
| Marketing Manager | view_contacts, send_messages, view_inbox, view_analytics, create_campaigns | 1000 |
| Support Rep | view_contacts, send_messages, view_inbox, manage_notes | 300 |
| Admin | All permissions | 0 (unlimited) |
6. Bulk Messaging & Campaigns
The Bulk Messaging API allows you to create and manage SMS/MMS campaigns to send messages to multiple contacts at once. You can schedule campaigns, use message templates, implement batch processing, and track campaign performance. Perfect for marketing campaigns, announcements, and mass notifications.
Key Features:
- Send SMS and MMS campaigns to contact lists
- Schedule campaigns for future delivery
- Batch processing with customizable size and frequency
- Round-robin number pool distribution
- Message template support
- Spin text variations for personalization
- Automatic opt-out link inclusion
- Credit calculation and validation
- Real-time segment counting
- Campaign status tracking
6.1 Get Campaign Creation Data
Retrieve all necessary data for creating a bulk messaging campaign. This endpoint provides contact lists, active phone numbers, and sub-user information. Use this endpoint to populate your campaign creation form.
/api/v1/campaigning/bulk
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search_number |
string | No | Search/filter contact lists by name |
search_contact |
string | No | Search/filter active numbers |
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/bulk" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - With Search
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/bulk?search_number=customers&search_contact=+1" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Data fetched successfully",
"data": {
"contactLists": [
{
"id": 45,
"name": "VIP Customers",
"bookmarked": 1,
"total_contacts": 1250
},
{
"id": 42,
"name": "Newsletter Subscribers",
"bookmarked": 0,
"total_contacts": 3420
}
],
"activeNumbers": [
{
"id": 12,
"number": "+15551234567",
"friendly_name": "Main Business Line",
"capabilities": {"sms": true, "mms": true, "voice": true},
"status": 1
},
{
"id": 15,
"number": "+15559876543",
"friendly_name": "Marketing Line",
"capabilities": {"sms": true, "mms": true, "voice": false},
"status": 1
}
],
"subUsers": [
{
"id": 1,
"name": "John Smith (2)",
"numbers": ["+15551234567", "+15559876543"]
},
{
"id": 101,
"name": "Sarah Johnson (1)",
"numbers": ["+15556543210"]
}
]
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
contactLists |
array | Available contact lists with total contact counts |
activeNumbers |
array | Active phone numbers available for sending (status=1) |
subUsers |
array | Sub-accounts and their associated active phone numbers |
total_contacts |
integer | Number of contacts in each list |
capabilities |
object | Number capabilities (sms, mms, voice) |
Example in JavaScript
async function getCampaignData(searchOptions = {}) {
const params = new URLSearchParams();
if (searchOptions.contactList) {
params.append('search_number', searchOptions.contactList);
}
if (searchOptions.phoneNumber) {
params.append('search_contact', searchOptions.phoneNumber);
}
const queryString = params.toString() ? `?${params}` : '';
const response = await fetch(
`https://api.texttorrent.com/api/v1/campaigning/bulk${queryString}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Contact Lists:', data.data.contactLists);
console.log('Active Numbers:', data.data.activeNumbers);
console.log('Sub Users:', data.data.subUsers);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get all data
const campaignData = await getCampaignData();
// Usage - Search contact lists
const filtered = await getCampaignData({
contactList: 'customers',
phoneNumber: '+1'
});
Important Notes
- Only returns contact lists where
user_idmatches your account - Only active numbers (
status=1) are included - Sub-users include parent account's numbers
- Contact counts exclude blacklisted contacts
- Use this data to populate campaign creation forms
6.2 Create Bulk Campaign
Create a new bulk messaging campaign to send SMS or MMS messages to a contact list. You can schedule the campaign for future delivery, enable batch processing, use number pools, and track credit consumption. The campaign will be processed in the background via job queue.
/api/v1/campaigning/bulk
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: multipart/form-data
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
campaign_name |
string | Yes | Campaign name (max 255 characters) |
contact_list_id |
integer | Yes | ID of contact list to send to (must exist) |
template_id |
integer | Yes | ID of message template (must exist) |
sms_type |
string | Yes | Message type: sms or mms |
sms_body |
string | Yes | Message content (max 1600 characters) |
numbers |
array | Yes | Array of phone numbers to send from (max 15 chars each) |
file |
file | No* | Media file for MMS (required if sms_type=mms) |
appended_message |
string | No | Additional text appended to message (max 1600 chars) |
number_pool |
boolean | No | Enable number pool distribution |
batch_process |
boolean | No | Enable batch processing |
opt_out_link |
boolean | No | Include opt-out link in message |
round_robin_campaign |
boolean | No | Enable round-robin number rotation |
batch_size |
integer | No | Number of messages per batch (if batch_process enabled) |
batch_frequency |
integer | No | Minutes between batches (if batch_process enabled) |
selected_date |
date | No | Schedule date (YYYY-MM-DD format) |
selected_time |
string | No | Schedule time (HH:MM format) |
phone_numbers |
string | No | Comma-separated phone numbers |
selected_users |
array | No | Array of sub-user IDs to use their numbers |
Example Request - Simple SMS Campaign
curl -X POST "https://api.texttorrent.com/api/v1/campaigning/bulk" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-F "campaign_name=Spring Sale Announcement" \
-F "contact_list_id=45" \
-F "template_id=12" \
-F "sms_type=sms" \
-F "sms_body=Hi {first_name}, don't miss our Spring Sale! Get 20% off all items. Shop now!" \
-F "numbers[]=+15551234567" \
-F "numbers[]=+15559876543" \
-F "opt_out_link=true"
Example Request - MMS Campaign with File
curl -X POST "https://api.texttorrent.com/api/v1/campaigning/bulk" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-F "campaign_name=Product Launch" \
-F "contact_list_id=45" \
-F "template_id=12" \
-F "sms_type=mms" \
-F "sms_body=Check out our new product!" \
-F "file=@/path/to/product-image.jpg" \
-F "numbers[]=+15551234567" \
-F "opt_out_link=true"
Example Request - Scheduled Campaign with Batch Processing
curl -X POST "https://api.texttorrent.com/api/v1/campaigning/bulk" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-F "campaign_name=Holiday Greetings" \
-F "contact_list_id=45" \
-F "template_id=12" \
-F "sms_type=sms" \
-F "sms_body=Happy Holidays from our team!" \
-F "numbers[]=+15551234567" \
-F "batch_process=true" \
-F "batch_size=100" \
-F "batch_frequency=30" \
-F "selected_date=2025-12-25" \
-F "selected_time=09:00"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Campaign created successfully",
"data": {
"campaign_id": 567,
"total_credit": 1875,
"total_contacts": 1250,
"total_segments": 1
},
"errors": null
}
Error Response - Insufficient Credits (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "You do not have enough credits to perform this action.",
"data": null,
"errors": null
}
Error Response - Trial Account (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "You cannot use this feature during the trial period.",
"data": null,
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"campaign_name": ["The campaign name field is required."],
"contact_list_id": ["The selected contact list id is invalid."],
"template_id": ["The selected template id is invalid."],
"sms_type": ["The sms type must be either sms or mms."],
"file": ["The file field is required when sms type is mms."],
"numbers": ["The numbers field is required."]
}
}
Example in JavaScript
async function createCampaign(campaignData, mediaFile = null) {
const formData = new FormData();
// Required fields
formData.append('campaign_name', campaignData.campaign_name);
formData.append('contact_list_id', campaignData.contact_list_id);
formData.append('template_id', campaignData.template_id);
formData.append('sms_type', campaignData.sms_type);
formData.append('sms_body', campaignData.sms_body);
// Numbers array
campaignData.numbers.forEach(number => {
formData.append('numbers[]', number);
});
// Optional fields
if (campaignData.appended_message) {
formData.append('appended_message', campaignData.appended_message);
}
if (campaignData.number_pool) {
formData.append('number_pool', campaignData.number_pool);
}
if (campaignData.batch_process) {
formData.append('batch_process', campaignData.batch_process);
formData.append('batch_size', campaignData.batch_size);
formData.append('batch_frequency', campaignData.batch_frequency);
}
if (campaignData.opt_out_link) {
formData.append('opt_out_link', campaignData.opt_out_link);
}
if (campaignData.round_robin_campaign) {
formData.append('round_robin_campaign', campaignData.round_robin_campaign);
}
if (campaignData.selected_date) {
formData.append('selected_date', campaignData.selected_date);
formData.append('selected_time', campaignData.selected_time);
}
// MMS file
if (mediaFile && campaignData.sms_type === 'mms') {
formData.append('file', mediaFile);
}
const response = await fetch('https://api.texttorrent.com/api/v1/campaigning/bulk', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
},
body: formData
});
const data = await response.json();
if (data.success) {
console.log('Campaign created:', data.data);
alert(`Campaign created! ID: ${data.data.campaign_id}, Credits: ${data.data.total_credit}`);
return data.data;
} else {
console.error('Error:', data.message, data.errors);
throw new Error(data.message);
}
}
// Usage - Simple SMS campaign
await createCampaign({
campaign_name: 'Spring Sale Announcement',
contact_list_id: 45,
template_id: 12,
sms_type: 'sms',
sms_body: 'Hi {first_name}, don\'t miss our Spring Sale! Get 20% off all items.',
numbers: ['+15551234567', '+15559876543'],
opt_out_link: true
});
// Usage - Scheduled campaign
await createCampaign({
campaign_name: 'Holiday Greetings',
contact_list_id: 45,
template_id: 12,
sms_type: 'sms',
sms_body: 'Happy Holidays from our team!',
numbers: ['+15551234567'],
batch_process: true,
batch_size: 100,
batch_frequency: 30,
selected_date: '2025-12-25',
selected_time: '09:00'
});
// Usage - MMS campaign with file
const fileInput = document.getElementById('mms-file');
await createCampaign({
campaign_name: 'Product Launch',
contact_list_id: 45,
template_id: 12,
sms_type: 'mms',
sms_body: 'Check out our new product!',
numbers: ['+15551234567']
}, fileInput.files[0]);
Example in PHP
<?php
$ch = curl_init();
$campaignData = [
'campaign_name' => 'Spring Sale Announcement',
'contact_list_id' => 45,
'template_id' => 12,
'sms_type' => 'sms',
'sms_body' => 'Hi {first_name}, don\'t miss our Spring Sale! Get 20% off all items.',
'numbers' => ['+15551234567', '+15559876543'],
'opt_out_link' => true
];
// Convert numbers array to proper format
$postFields = $campaignData;
$postFields['numbers'] = $campaignData['numbers'];
curl_setopt($ch, CURLOPT_URL, 'https://api.texttorrent.com/api/v1/campaigning/bulk');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postFields));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Accept: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
echo "Campaign created! ID: {$data['data']['campaign_id']}\n";
echo "Credits consumed: {$data['data']['total_credit']}\n";
echo "Total contacts: {$data['data']['total_contacts']}\n";
} else {
echo "Error: {$data['message']}\n";
}
?>
Understanding Message Segments
| Encoding | Single Segment | Multi-Segment | Credit per Segment |
|---|---|---|---|
| GSM-7 (Standard) | Up to 160 characters | 153 characters each | 1 credit |
| UCS-2 (Unicode) | Up to 70 characters | 67 characters each | 1 credit |
| MMS | N/A | N/A | Fixed MMS rate |
Important Notes
- Credit Calculation: Automatically calculates required credits based on segments and contacts
- Auto-Recharge: Triggers when credits fall below 1000 (if enabled)
- Trial Accounts: Cannot create campaigns during trial period
- Blacklisted Contacts: Automatically excluded from campaigns
- Scheduling: Set both date and time for scheduled campaigns
- Batch Processing: Spreads campaign over time with batch_size and batch_frequency
- Round-Robin: Distributes messages evenly across provided numbers
- Number Pool: Uses multiple numbers to send messages
- Opt-Out Link: Automatically appends opt-out link to message
- Spin Text: Supports {option1|option2} syntax for message variations
- MMS Files: Stored in DigitalOcean Spaces with permanent URLs
- Background Processing: Campaigns processed via job queue
- Activity Logging: Creates activity log entry for each campaign
6.3 Bulk Messaging Best Practices
Campaign Planning
- Test First: Send test messages to yourself before launching campaign
- Check Credits: Ensure sufficient credits before creating large campaigns
- Segment Your Audience: Use contact lists to target specific groups
- Schedule Wisely: Avoid sending at late night or early morning hours
- Use Templates: Create reusable templates for common messages
Message Optimization
- Keep It Short: Aim for single segment messages (160 chars for GSM-7)
- Clear Call-to-Action: Include clear next steps or links
- Personalization: Use {first_name}, {last_name} placeholders
- Spin Text: Use variations like {Hi|Hello|Hey} for uniqueness
- Avoid Spam Words: Minimize use of spam trigger words
Batch Processing Strategy
- Large Lists: Use batch processing for 1000+ contacts
- Recommended Batch Size: 50-200 messages per batch
- Frequency: 15-30 minutes between batches recommended
- Time Zones: Consider recipient time zones when scheduling
Compliance & Legal
- Always Include Opt-Out: Enable opt_out_link for all campaigns
- Honor Opt-Outs: Blacklisted contacts are automatically excluded
- TCPA Compliance: Ensure you have consent to message contacts
- Business Hours: Send during reasonable hours (9 AM - 8 PM)
- Frequency Limits: Don't overwhelm recipients with too many messages
Number Pool & Round-Robin
- Multiple Numbers: Use 3-5 numbers for large campaigns
- Round-Robin: Evenly distributes messages across numbers
- Avoid Spam Filters: Multiple numbers reduce spam detection
- Number Reputation: Rotate numbers to maintain sender reputation
Complete Campaign Example
class CampaignManager {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1/campaigning';
}
async getCampaignData(search = {}) {
const params = new URLSearchParams(search);
const response = await fetch(
`${this.baseUrl}/bulk?${params}`,
{
method: 'GET',
headers: this.getHeaders()
}
);
return await this.handleResponse(response);
}
async createCampaign(campaignData, file = null) {
const formData = new FormData();
// Add all campaign data
Object.keys(campaignData).forEach(key => {
if (Array.isArray(campaignData[key])) {
campaignData[key].forEach(value => {
formData.append(`${key}[]`, value);
});
} else if (campaignData[key] !== null && campaignData[key] !== undefined) {
formData.append(key, campaignData[key]);
}
});
// Add file for MMS
if (file) {
formData.append('file', file);
}
const response = await fetch(`${this.baseUrl}/bulk`, {
method: 'POST',
headers: {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
},
body: formData
});
return await this.handleResponse(response);
}
getHeaders() {
return {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
};
}
async handleResponse(response) {
const data = await response.json();
if (!data.success) {
throw new Error(data.message);
}
return data.data;
}
}
// Usage
const manager = new CampaignManager('SID....................................', 'PK.....................................');
// Get campaign data
const data = await manager.getCampaignData({ search_number: 'customers' });
console.log('Available contact lists:', data.contactLists);
// Create immediate SMS campaign
const campaign = await manager.createCampaign({
campaign_name: 'Flash Sale Alert',
contact_list_id: 45,
template_id: 12,
sms_type: 'sms',
sms_body: 'Hi {first_name}! Flash sale ends in 2 hours. Shop now!',
numbers: ['+15551234567', '+15559876543'],
opt_out_link: true,
round_robin_campaign: true
});
console.log(`Campaign ${campaign.campaign_id} created!`);
console.log(`Credits consumed: ${campaign.total_credit}`);
console.log(`Messages queued: ${campaign.total_contacts}`);
// Create scheduled campaign with batches
const scheduled = await manager.createCampaign({
campaign_name: 'Weekly Newsletter',
contact_list_id: 45,
template_id: 12,
sms_type: 'sms',
sms_body: 'This week\'s top stories: {spin_text}',
numbers: ['+15551234567'],
opt_out_link: true,
batch_process: true,
batch_size: 100,
batch_frequency: 30,
selected_date: '2025-10-20',
selected_time: '09:00'
});
console.log('Scheduled campaign created:', scheduled);
Credit Estimation Formula
function estimateCampaignCredits(messageBody, totalContacts, smsType = 'sms') {
if (smsType === 'mms') {
// MMS has fixed cost + SMS cost if body included
const mmsCredit = 3; // Example MMS cost
const smsSegments = messageBody ? calculateSegments(messageBody) : 0;
return (mmsCredit + smsSegments) * totalContacts;
}
const segments = calculateSegments(messageBody);
return segments * totalContacts;
}
function calculateSegments(message) {
const isGSM7 = /^[\x00-\x7F]*$/u.test(message);
const length = message.length;
if (isGSM7) {
return length <= 160 ? 1 : Math.ceil(length / 153);
} else {
return length <= 70 ? 1 : Math.ceil(length / 67);
}
}
// Usage
const message = 'Hi {first_name}, don\'t miss our Spring Sale!';
const contacts = 1250;
const credits = estimateCampaignCredits(message, contacts);
console.log(`Estimated credits: ${credits}`);
Common Mistakes to Avoid
- ❌ Creating campaigns without checking credit balance
- ❌ Not testing messages before sending to entire list
- ❌ Forgetting to include opt-out link
- ❌ Sending at inappropriate times (late night, early morning)
- ❌ Using single number for very large campaigns
- ❌ Not using batch processing for 1000+ contacts
- ❌ Exceeding character limits causing unexpected segments
- ✅ Always test with small group first
- ✅ Monitor credit usage and set up auto-recharge
- ✅ Use batch processing for large campaigns
- ✅ Include opt-out links in all campaigns
- ✅ Use multiple numbers with round-robin
- ✅ Schedule campaigns for optimal times
- ✅ Keep messages concise and under segment limits
Recommended Campaign Settings
| Campaign Size | Batch Processing | Batch Size | Frequency | Number Pool |
|---|---|---|---|---|
| < 100 contacts | Not needed | N/A | N/A | 1-2 numbers |
| 100 - 500 contacts | Optional | 50-100 | 15-30 min | 2-3 numbers |
| 500 - 2000 contacts | Recommended | 100-200 | 20-30 min | 3-5 numbers |
| 2000+ contacts | Required | 200-500 | 30-60 min | 5+ numbers |
7. Campaign Analytics
The Campaign Analytics API provides comprehensive insights into your bulk messaging campaigns. Track campaign performance, monitor delivery status, view detailed message logs, and analyze success rates. Perfect for optimizing your messaging strategy and troubleshooting delivery issues.
Key Features:
- List all campaigns with pagination and filtering
- Get campaign counts by status (scheduled, processing, completed, failed, paused)
- View detailed campaign information
- Access individual message delivery logs
- Filter messages by status and search criteria
- Track credits consumed per campaign
- Monitor total participants and delivery rates
- View sender and recipient information
7.1 List Campaigns
Retrieve a paginated list of all your bulk messaging campaigns. You can filter by campaign name and status, making it easy to find specific campaigns or view campaigns in a particular state.
/api/v1/campaigning/analytic
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
search |
string | No | Search campaigns by name |
status |
string | No | Filter by status: Scheduled, Processing, Completed, Paused, Failed |
Example Request - Get All Campaigns
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Filter by Status
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic?status=Completed&limit=20" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Search Campaigns
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic?search=Spring Sale" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Campaigns fetched successfully",
"data": {
"current_page": 1,
"data": [
{
"id": 567,
"campaign_name": "Spring Sale Announcement",
"status": "Completed",
"contact_list_id": 45,
"contact_list_name": "VIP Customers",
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com",
"phone": "+15551234567",
"created_at": "2025-10-15T14:30:00.000000Z"
},
{
"id": 568,
"campaign_name": "Holiday Greetings",
"status": "Scheduled",
"contact_list_id": 42,
"contact_list_name": "Newsletter Subscribers",
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com",
"phone": "+15551234567",
"created_at": "2025-10-16T09:15:00.000000Z"
},
{
"id": 569,
"campaign_name": "Product Launch",
"status": "Processing",
"contact_list_id": 45,
"contact_list_name": "VIP Customers",
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com",
"phone": "+15551234567",
"created_at": "2025-10-17T10:00:00.000000Z"
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic?page=1",
"from": 1,
"last_page": 5,
"last_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic?page=5",
"next_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic?page=2",
"path": "https://api.texttorrent.com/api/v1/campaigning/analytic",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 47
},
"errors": null
}
Campaign Status Values
| Status | Description |
|---|---|
Scheduled |
Campaign is waiting for scheduled delivery time |
Processing |
Campaign is currently sending messages |
Completed |
All messages have been sent |
Paused |
Campaign has been temporarily paused |
Failed |
Campaign encountered errors during sending |
Example in JavaScript
async function listCampaigns(options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
if (options.search) params.append('search', options.search);
if (options.status) params.append('status', options.status);
const response = await fetch(
`https://api.texttorrent.com/api/v1/campaigning/analytic?${params}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Campaigns:', data.data.data);
console.log(`Total campaigns: ${data.data.total}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get all campaigns
const campaigns = await listCampaigns();
// Usage - Filter by status
const completed = await listCampaigns({ status: 'Completed' });
// Usage - Search campaigns
const searchResults = await listCampaigns({ search: 'Spring Sale' });
// Usage - Pagination
const page2 = await listCampaigns({ page: 2, limit: 20 });
Important Notes
- Only returns campaigns where
user_idmatches your account - Results are ordered by campaign ID in descending order (newest first)
- Search performs partial matching on campaign name
- Includes user information who created the campaign
- Shows contact list name for easy identification
7.2 Get Campaign Counts
Get a summary of all your campaigns grouped by status. This endpoint provides quick statistics showing how many campaigns are in each state, perfect for dashboard displays and quick overviews.
/api/v1/campaigning/analytic/count
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/count" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Campaigns count fetched successfully",
"data": {
"total": 47,
"scheduled": 5,
"processing": 2,
"completed": 38,
"paused": 1,
"failed": 1
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
total |
integer | Total number of campaigns |
scheduled |
integer | Campaigns waiting for scheduled time |
processing |
integer | Campaigns currently sending messages |
completed |
integer | Campaigns that finished sending |
paused |
integer | Campaigns that are paused |
failed |
integer | Campaigns that encountered errors |
Example in JavaScript
async function getCampaignCounts() {
const response = await fetch(
'https://api.texttorrent.com/api/v1/campaigning/analytic/count',
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Campaign Statistics:');
console.log(`Total: ${data.data.total}`);
console.log(`Scheduled: ${data.data.scheduled}`);
console.log(`Processing: ${data.data.processing}`);
console.log(`Completed: ${data.data.completed}`);
console.log(`Paused: ${data.data.paused}`);
console.log(`Failed: ${data.data.failed}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
const stats = await getCampaignCounts();
// Display as percentages
const successRate = ((stats.completed / stats.total) * 100).toFixed(2);
console.log(`Success rate: ${successRate}%`);
Important Notes
- Counts are based on campaigns owned by your account
- Perfect for dashboard widgets and overview displays
- Lightweight endpoint with minimal data transfer
- Use to quickly assess campaign health
7.3 Get Campaign Details
Retrieve detailed information about a specific campaign including credits consumed, total participants, and current status. Use this to view campaign overview before diving into individual message logs.
/api/v1/campaigning/analytic/details/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Campaign ID |
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Campaigns fetched successfully",
"data": {
"id": 567,
"campaign_name": "Spring Sale Announcement",
"contact_list_id": 45,
"credits_consumed": 1875,
"total_participants": 1250,
"status": "Completed",
"created_at": "2025-10-15T14:30:00.000000Z"
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
credits_consumed |
integer | Total credits used for this campaign |
total_participants |
integer | Number of contacts in the campaign |
contact_list_id |
integer | ID of the contact list used |
Example in JavaScript
async function getCampaignDetails(campaignId) {
const response = await fetch(
`https://api.texttorrent.com/api/v1/campaigning/analytic/details/${campaignId}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Campaign Details:', data.data);
console.log(`Credits: ${data.data.credits_consumed}`);
console.log(`Participants: ${data.data.total_participants}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
const campaign = await getCampaignDetails(567);
Important Notes
- Returns high-level campaign information
- Use for campaign summary displays
- Credits consumed shows total cost of campaign
- Total participants shows number of recipients
7.4 Get Campaign Message List
Retrieve a detailed list of all individual messages in a campaign. This endpoint provides granular tracking of each message sent, including sender/receiver information, delivery status, and execution time. Essential for troubleshooting and detailed analytics.
/api/v1/campaigning/analytic/details/{id}/list
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Campaign ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
search |
string | No | Search by sender or receiver number |
status |
string | No | Filter by status: Completed, Processing, Paused, Scheduled, Failed |
Example Request - Get All Messages
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Filter by Status
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?status=Failed" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Search by Number
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?search=+1555" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Campaigns fetched successfully",
"data": {
"current_page": 1,
"data": [
{
"id": 12345,
"bulk_message_id": 567,
"send_from": "+15551234567",
"send_from_name": "John Smith - Main Business Line",
"send_to": "+15559876543",
"send_to_name": "Jane Doe",
"status": 1,
"send_status": "delivered",
"execute_at": "2025-10-15T14:30:00.000000Z",
"created_at": "2025-10-15T14:29:45.000000Z",
"updated_at": "2025-10-15T14:30:15.000000Z"
},
{
"id": 12346,
"bulk_message_id": 567,
"send_from": "+15551234567",
"send_from_name": "John Smith - Main Business Line",
"send_to": "+15551112222",
"send_to_name": "Bob Wilson",
"status": 1,
"send_status": "sent",
"execute_at": "2025-10-15T14:30:05.000000Z",
"created_at": "2025-10-15T14:29:45.000000Z",
"updated_at": "2025-10-15T14:30:20.000000Z"
},
{
"id": 12347,
"bulk_message_id": 567,
"send_from": "+15559876543",
"send_from_name": "John Smith - Marketing Line",
"send_to": "+15553334444",
"send_to_name": "Alice Johnson",
"status": 1,
"send_status": "failed",
"execute_at": "2025-10-15T14:30:10.000000Z",
"created_at": "2025-10-15T14:29:45.000000Z",
"updated_at": "2025-10-15T14:30:25.000000Z"
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?page=1",
"from": 1,
"last_page": 125,
"last_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?page=125",
"next_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?page=2",
"path": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 1250
},
"errors": null
}
Message Status Values
| Status Code | Send Status | Filter Status | Description |
|---|---|---|---|
0 |
N/A | Scheduled | Message waiting for scheduled time (execute_at > now) |
1 |
pending | Processing | Message is being processed/sent |
1 |
delivered | Completed | Message successfully delivered to recipient |
1 |
sent | Completed | Message sent (delivery confirmation pending) |
1 |
failed | Failed | Message failed to send |
1 |
undelivered | Failed | Message sent but not delivered |
5 |
N/A | Paused | Message paused by user |
Response Fields
| Field | Type | Description |
|---|---|---|
send_from |
string | Phone number message was sent from |
send_from_name |
string | User and friendly name of sender number |
send_to |
string | Recipient phone number |
send_to_name |
string | Recipient name from contacts |
status |
integer | Processing status code (0, 1, 5) |
send_status |
string | Delivery status (pending, sent, delivered, failed, undelivered) |
execute_at |
datetime | When message was/will be sent |
Example in JavaScript
async function getCampaignMessages(campaignId, options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
if (options.search) params.append('search', options.search);
if (options.status) params.append('status', options.status);
const response = await fetch(
`https://api.texttorrent.com/api/v1/campaigning/analytic/details/${campaignId}/list?${params}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Messages:', data.data.data);
console.log(`Total messages: ${data.data.total}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get all messages
const messages = await getCampaignMessages(567);
// Usage - Get failed messages
const failed = await getCampaignMessages(567, { status: 'Failed' });
// Usage - Search by number
const search = await getCampaignMessages(567, { search: '+1555' });
// Usage - Pagination
const page2 = await getCampaignMessages(567, { page: 2, limit: 50 });
Important Notes
- Results are ordered by ID in descending order
- Search performs partial matching on both
send_fromandsend_to - Names are dynamically resolved from user/contact databases
- Status filtering uses complex logic based on status code and send_status
- Perfect for detailed campaign analysis and troubleshooting
7.5 Get Campaign Message Counts
Get a summary count of messages in a campaign grouped by status. This provides quick statistics at the message level (not campaign level), showing exactly how many messages are in each delivery state.
/api/v1/campaigning/analytic/details/{id}/count
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Campaign ID |
Example Request
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/count" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Campaigns count fetched successfully",
"data": {
"total": 1250,
"scheduled": 0,
"processing": 5,
"completed": 1230,
"paused": 0,
"failed": 15
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
total |
integer | Total number of messages in campaign |
scheduled |
integer | Messages waiting for scheduled time (status=0, execute_at > now) |
processing |
integer | Messages being processed (status=1, send_status=pending) |
completed |
integer | Messages delivered or sent (send_status=delivered or sent) |
paused |
integer | Messages paused by user (status=5) |
failed |
integer | Messages that failed (send_status=failed or undelivered) |
Example in JavaScript
async function getCampaignMessageCounts(campaignId) {
const response = await fetch(
`https://api.texttorrent.com/api/v1/campaigning/analytic/details/${campaignId}/count`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Message Statistics:');
console.log(`Total: ${data.data.total}`);
console.log(`Completed: ${data.data.completed}`);
console.log(`Failed: ${data.data.failed}`);
// Calculate delivery rate
const deliveryRate = ((data.data.completed / data.data.total) * 100).toFixed(2);
console.log(`Delivery rate: ${deliveryRate}%`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage
const stats = await getCampaignMessageCounts(567);
// Display metrics
function displayCampaignMetrics(stats) {
const deliveryRate = ((stats.completed / stats.total) * 100).toFixed(2);
const failureRate = ((stats.failed / stats.total) * 100).toFixed(2);
console.log('Campaign Performance:');
console.log(` Total Messages: ${stats.total}`);
console.log(` Delivery Rate: ${deliveryRate}%`);
console.log(` Failure Rate: ${failureRate}%`);
console.log(` Still Processing: ${stats.processing}`);
}
displayCampaignMetrics(stats);
Important Notes
- Counts are at the message level, not campaign level
- Use to calculate delivery rates and failure rates
- Perfect for campaign performance dashboards
- Lightweight endpoint for quick statistics
7.6 Campaign Analytics Best Practices
Monitoring Strategy
- Regular Checks: Monitor campaign status regularly during sending
- Track Failures: Investigate failed messages to identify patterns
- Delivery Rates: Aim for 95%+ delivery rate for optimal performance
- Processing Time: Large campaigns may take time based on batch settings
Performance Analysis
- Compare Campaigns: Track which campaigns perform best
- Time Analysis: Note when messages have highest delivery rates
- Number Performance: Track which sending numbers have best rates
- Contact Quality: High failure rates may indicate stale contacts
Troubleshooting Failed Messages
- Invalid Numbers: Check for formatting issues
- Carrier Blocks: Some carriers may block bulk messages
- Credit Issues: Verify sufficient credits during campaign
- Rate Limits: Respect carrier rate limits with batch processing
Complete Analytics Dashboard Example
class CampaignAnalytics {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1/campaigning/analytic';
}
async getCampaignOverview() {
const [campaigns, counts] = await Promise.all([
this.listCampaigns({ limit: 5 }),
this.getCampaignCounts()
]);
return {
recentCampaigns: campaigns.data,
statistics: counts
};
}
async getCampaignPerformance(campaignId) {
const [details, messageCounts, messages] = await Promise.all([
this.getCampaignDetails(campaignId),
this.getMessageCounts(campaignId),
this.getMessages(campaignId, { limit: 100 })
]);
const deliveryRate = (messageCounts.completed / messageCounts.total * 100).toFixed(2);
const failureRate = (messageCounts.failed / messageCounts.total * 100).toFixed(2);
return {
campaign: details,
metrics: {
total: messageCounts.total,
completed: messageCounts.completed,
failed: messageCounts.failed,
deliveryRate: `${deliveryRate}%`,
failureRate: `${failureRate}%`,
creditsPerMessage: (details.credits_consumed / messageCounts.total).toFixed(2)
},
recentMessages: messages.data
};
}
async listCampaigns(options = {}) {
return await this.apiCall('GET', '', options);
}
async getCampaignCounts() {
return await this.apiCall('GET', '/count');
}
async getCampaignDetails(id) {
return await this.apiCall('GET', `/details/${id}`);
}
async getMessages(id, options = {}) {
return await this.apiCall('GET', `/details/${id}/list`, options);
}
async getMessageCounts(id) {
return await this.apiCall('GET', `/details/${id}/count`);
}
async apiCall(method, endpoint, params = {}) {
const queryString = new URLSearchParams(params).toString();
const url = `${this.baseUrl}${endpoint}${queryString ? `?${queryString}` : ''}`;
const response = await fetch(url, {
method,
headers: {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
}
});
const data = await response.json();
if (!data.success) throw new Error(data.message);
return data.data;
}
}
// Usage - Dashboard Overview
const analytics = new CampaignAnalytics('SID....................................', 'PK.....................................');
const overview = await analytics.getCampaignOverview();
console.log('Recent Campaigns:', overview.recentCampaigns);
console.log('Campaign Stats:', overview.statistics);
// Usage - Detailed Campaign Analysis
const performance = await analytics.getCampaignPerformance(567);
console.log('Campaign:', performance.campaign.campaign_name);
console.log('Delivery Rate:', performance.metrics.deliveryRate);
console.log('Failure Rate:', performance.metrics.failureRate);
console.log('Cost per Message:', performance.metrics.creditsPerMessage);
// Usage - Find Problem Campaigns
const campaigns = await analytics.listCampaigns({ status: 'Failed' });
for (const campaign of campaigns.data) {
const counts = await analytics.getMessageCounts(campaign.id);
const failureRate = (counts.failed / counts.total * 100).toFixed(2);
console.log(`${campaign.campaign_name}: ${failureRate}% failure rate`);
}
Key Performance Indicators (KPIs)
| KPI | Calculation | Good Target |
|---|---|---|
| Delivery Rate | (Completed / Total) × 100 | > 95% |
| Failure Rate | (Failed / Total) × 100 | < 5% |
| Cost per Message | Credits Consumed / Total Messages | 1-3 credits |
| Campaign Completion Time | Last Message - First Message | Depends on batch settings |
Common Issues & Solutions
-
High Failure Rate:
- Clean contact lists regularly
- Validate numbers before campaigns
- Remove invalid/disconnected numbers
-
Stuck in Processing:
- Check batch settings aren't too restrictive
- Verify sufficient credits remain
- Contact support if persists > 1 hour
-
Scheduled Not Starting:
- Verify scheduled time is in future
- Check server timezone settings
- Ensure job queue is running
Reporting Best Practices
- ✅ Export failed messages for contact list cleanup
- ✅ Track delivery rates over time for trends
- ✅ Compare performance across different contact lists
- ✅ Monitor processing times for batch optimization
- ✅ Keep historical data for compliance
- ✅ Document any unusual failure patterns
- ❌ Don't ignore failed messages
- ❌ Don't skip regular performance reviews
- ❌ Don't send to lists with high failure rates
8. Message Templates
The Message Templates API allows you to create, manage, and organize reusable message templates for your campaigns and conversations. Templates save time by storing frequently used messages and ensure consistency across your communications. Perfect for standard responses, campaign messages, and automated workflows.
Key Features:
- Create and manage reusable message templates
- Search and filter templates by name
- Sort templates by various fields
- Assign templates to specific users/sub-accounts
- Enable/disable templates with status control
- Preview message content
- Export templates to Excel for backup
- Paginated template listings
8.1 List Message Templates
Retrieve a paginated list of all your message templates. You can search by template name, sort by various fields, and control pagination. Each template includes assignee information showing which users have access.
/api/v1/campaigning/template
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of results per page (default: 10) |
page |
integer | No | Page number for pagination (default: 1) |
search |
string | No | Search templates by name |
sort_by |
string | No | Field to sort by (default: created_at) |
sort_direction |
string | No | Sort direction: ASC or DESC (default: DESC) |
Example Request - Get All Templates
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/template" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Example Request - Search and Sort
curl -X GET "https://api.texttorrent.com/api/v1/campaigning/template?search=welcome&sort_by=template_name&sort_direction=ASC" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Templates retrieved successfully",
"data": {
"current_page": 1,
"data": [
{
"id": 12,
"template_name": "Welcome Message",
"status": 1,
"assigned_users": [101, 102, 103],
"preview_message": "Hi {first_name}, welcome to our service! We're excited to have you.",
"created_at": "2025-09-20T10:30:00.000000Z",
"user_name": "John Smith",
"assignees": [
{
"id": 101,
"name": "Sarah Johnson",
"email": "sarah.johnson@example.com"
},
{
"id": 102,
"name": "Mike Davis",
"email": "mike.davis@example.com"
},
{
"id": 103,
"name": "Emily Brown",
"email": "emily.brown@example.com"
}
]
},
{
"id": 15,
"template_name": "Flash Sale Alert",
"status": 1,
"assigned_users": [],
"preview_message": "{Hi|Hello|Hey} {first_name}! Flash sale ends in 2 hours. Shop now!",
"created_at": "2025-10-10T14:15:00.000000Z",
"user_name": "John Smith",
"assignees": []
},
{
"id": 18,
"template_name": "Order Confirmation",
"status": 0,
"assigned_users": [101],
"preview_message": "Thank you for your order! Your order number is {order_id}. We'll notify you when it ships.",
"created_at": "2025-10-15T09:45:00.000000Z",
"user_name": "John Smith",
"assignees": [
{
"id": 101,
"name": "Sarah Johnson",
"email": "sarah.johnson@example.com"
}
]
}
],
"first_page_url": "https://api.texttorrent.com/api/v1/campaigning/template?page=1",
"from": 1,
"last_page": 3,
"last_page_url": "https://api.texttorrent.com/api/v1/campaigning/template?page=3",
"next_page_url": "https://api.texttorrent.com/api/v1/campaigning/template?page=2",
"path": "https://api.texttorrent.com/api/v1/campaigning/template",
"per_page": 10,
"prev_page_url": null,
"to": 10,
"total": 28
},
"errors": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
status |
integer | Template status: 1 = active, 0 = inactive |
assigned_users |
array | Array of user IDs who can access this template |
preview_message |
string | Template content with variables and spin text |
user_name |
string | Name of user who created the template |
assignees |
array | Full details of assigned users (id, name, email) |
Example in JavaScript
async function listTemplates(options = {}) {
const params = new URLSearchParams({
page: options.page || 1,
limit: options.limit || 10
});
if (options.search) params.append('search', options.search);
if (options.sort_by) params.append('sort_by', options.sort_by);
if (options.sort_direction) params.append('sort_direction', options.sort_direction);
const response = await fetch(
`https://api.texttorrent.com/api/v1/campaigning/template?${params}`,
{
method: 'GET',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Templates:', data.data.data);
console.log(`Total templates: ${data.data.total}`);
return data.data;
} else {
console.error('Error:', data.message);
throw new Error(data.message);
}
}
// Usage - Get all templates
const templates = await listTemplates();
// Usage - Search templates
const searchResults = await listTemplates({ search: 'welcome' });
// Usage - Sort by name
const sorted = await listTemplates({
sort_by: 'template_name',
sort_direction: 'ASC'
});
Important Notes
- Only returns templates where
user_idmatches your account - Search performs partial matching on template name
- Assignees array includes full user details (id, name, email)
- Default sorting is by creation date (newest first)
- Empty
assigned_usersmeans template is available to all
8.2 Create Message Template
Create a new message template with customizable content, status, and user assignments. Templates can include placeholder variables for personalization and spin text for message variations.
/api/v1/campaigning/template
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
template_name |
string | Yes | Template name (max 255 characters) |
preview_message |
string | Yes | Template message content |
status |
boolean | Yes | Template status: true = active, false = inactive |
assignees |
array | No | Array of user IDs to assign template to |
Example Request - Simple Template
curl -X POST "https://api.texttorrent.com/api/v1/campaigning/template" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"template_name": "Welcome Message",
"preview_message": "Hi {first_name}, welcome to our service! We'\''re excited to have you.",
"status": true
}'
Example Request - With Spin Text and Assignees
curl -X POST "https://api.texttorrent.com/api/v1/campaigning/template" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"template_name": "Flash Sale Alert",
"preview_message": "{Hi|Hello|Hey} {first_name}! Flash sale ends in 2 hours. {Shop now|Don'\''t miss out|Hurry}!",
"status": true,
"assignees": [101, 102, 103]
}'
Success Response (201 Created)
{
"code": 201,
"success": true,
"message": "Template created successfully",
"data": {
"id": 25,
"user_id": 1,
"template_name": "Welcome Message",
"preview_message": "Hi {first_name}, welcome to our service! We're excited to have you.",
"status": 1,
"assigned_users": [],
"created_at": "2025-10-17T16:45:00.000000Z",
"updated_at": "2025-10-17T16:45:00.000000Z"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"template_name": ["The template name field is required."],
"preview_message": ["The preview message field is required."],
"status": ["The status field is required."],
"assignees.0": ["The selected assignees.0 is invalid."]
}
}
Example in JavaScript
async function createTemplate(templateData) {
const response = await fetch('https://api.texttorrent.com/api/v1/campaigning/template', {
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(templateData)
});
const data = await response.json();
if (data.success) {
console.log('Template created:', data.data);
alert(`Template "${data.data.template_name}" created successfully!`);
return data.data;
} else {
console.error('Error:', data.message, data.errors);
throw new Error(data.message);
}
}
// Usage - Simple template
await createTemplate({
template_name: 'Welcome Message',
preview_message: 'Hi {first_name}, welcome to our service!',
status: true
});
// Usage - With spin text and assignees
await createTemplate({
template_name: 'Flash Sale Alert',
preview_message: '{Hi|Hello|Hey} {first_name}! Flash sale ends in 2 hours!',
status: true,
assignees: [101, 102, 103]
});
Supported Variables
| Variable | Description | Example Output |
|---|---|---|
{first_name} |
Contact's first name | John |
{last_name} |
Contact's last name | Smith |
{option1|option2} |
Spin text (random selection) | option1 or option2 |
Important Notes
- Template is automatically assigned to creator's user ID
- Empty assignees array means template is available to all users
- Assignees must be valid user IDs in the system
- Status controls template availability (active/inactive)
- Spin text format: {option1|option2|option3}
- Variables are replaced during message sending
8.3 Update Message Template
Update an existing message template's name, content, status, or user assignments. The creation timestamp is preserved during updates.
/api/v1/campaigning/template/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Template ID to update |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
template_name |
string | Yes | Template name (max 255 characters) |
preview_message |
string | Yes | Template message content |
status |
boolean | Yes | Template status: true = active, false = inactive |
assignees |
array | No | Array of user IDs to assign template to |
Example Request
curl -X PUT "https://api.texttorrent.com/api/v1/campaigning/template/12" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"template_name": "Updated Welcome Message",
"preview_message": "Hi {first_name}, thank you for joining us! We'\''re thrilled to have you on board.",
"status": true,
"assignees": [101, 102]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Template updated successfully",
"data": {
"id": 12,
"user_id": 1,
"template_name": "Updated Welcome Message",
"preview_message": "Hi {first_name}, thank you for joining us! We're thrilled to have you on board.",
"status": 1,
"assigned_users": [101, 102],
"created_at": "2025-09-20T10:30:00.000000Z",
"updated_at": "2025-10-17T16:50:00.000000Z"
},
"errors": null
}
Example in JavaScript
async function updateTemplate(templateId, templateData) {
const response = await fetch(
`https://api.texttorrent.com/api/v1/campaigning/template/${templateId}`,
{
method: 'PUT',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(templateData)
}
);
const data = await response.json();
if (data.success) {
console.log('Template updated:', data.data);
alert('Template updated successfully!');
return data.data;
} else {
console.error('Error:', data.message, data.errors);
throw new Error(data.message);
}
}
// Usage
await updateTemplate(12, {
template_name: 'Updated Welcome Message',
preview_message: 'Hi {first_name}, thank you for joining us!',
status: true,
assignees: [101, 102]
});
Important Notes
- All fields are required even if not changing
- Creation timestamp is preserved during update
- Updated timestamp reflects the update time
- Use empty array for assignees to make available to all
8.4 Delete Message Template
Permanently delete a message template. This action cannot be undone. If the template has an associated avatar file, it will also be deleted.
/api/v1/campaigning/template/{id}
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Template ID to delete |
Example Request
curl -X DELETE "https://api.texttorrent.com/api/v1/campaigning/template/12" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json"
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Template deleted successfully",
"data": [],
"errors": null
}
Example in JavaScript
async function deleteTemplate(templateId) {
if (!confirm('Delete this template? This action cannot be undone.')) {
return;
}
const response = await fetch(
`https://api.texttorrent.com/api/v1/campaigning/template/${templateId}`,
{
method: 'DELETE',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Template deleted successfully');
alert('Template has been deleted');
return true;
} else {
console.error('Error:', data.message);
alert('Failed to delete template');
return false;
}
}
// Usage
await deleteTemplate(12);
Important Notes
- Deletion is permanent and cannot be undone
- Associated avatar file is automatically deleted
- Consider disabling instead of deleting if you might need it later
8.5 Export Message Templates
Export selected message templates to an Excel file. The file is stored in DigitalOcean Spaces and a download URL is returned. Perfect for backing up templates or sharing with team members.
/api/v1/campaigning/template/export
Request Headers
X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
template_ids |
array | Yes | Array of template IDs to export |
Example Request
curl -X POST "https://api.texttorrent.com/api/v1/campaigning/template/export" \
-H "X-API-SID: SID...................................." \
-H "X-API-PUBLIC-KEY: PK....................................." \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"template_ids": [12, 15, 18, 25]
}'
Success Response (200 OK)
{
"code": 200,
"success": true,
"message": "Templates exported successfully",
"data": {
"path": "inbox_template/inbox_template_1729180800.xlsx",
"url": "https://texttorrent.nyc3.digitaloceanspaces.com/inbox_template/inbox_template_1729180800.xlsx"
},
"errors": null
}
Validation Errors (422 Unprocessable Entity)
{
"code": 422,
"success": false,
"message": "Validation Error",
"data": null,
"errors": {
"template_ids": ["The template ids field is required."],
"template_ids.0": ["The selected template ids.0 is invalid."]
}
}
Example in JavaScript
async function exportTemplates(templateIds) {
const response = await fetch(
'https://api.texttorrent.com/api/v1/campaigning/template/export',
{
method: 'POST',
headers: {
'X-API-SID': 'SID....................................',
'X-API-PUBLIC-KEY': 'PK.....................................',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ template_ids: templateIds })
}
);
const data = await response.json();
if (data.success) {
console.log('Export successful:', data.data);
// Download the file
window.open(data.data.url, '_blank');
return data.data;
} else {
console.error('Error:', data.message, data.errors);
throw new Error(data.message);
}
}
// Usage - Export selected templates
await exportTemplates([12, 15, 18, 25]);
// Usage - Export all templates from list
const templates = await listTemplates();
const allIds = templates.data.map(t => t.id);
await exportTemplates(allIds);
Example in PHP
<?php
$templateIds = [12, 15, 18, 25];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.texttorrent.com/api/v1/campaigning/template/export');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['template_ids' => $templateIds]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-SID: SID....................................',
'X-API-PUBLIC-KEY: PK.....................................',
'Accept: application/json',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success']) {
$downloadUrl = $data['data']['url'];
echo "Download URL: {$downloadUrl}\n";
// Optionally download the file
file_put_contents('templates.xlsx', file_get_contents($downloadUrl));
} else {
echo "Error: {$data['message']}\n";
}
?>
Important Notes
- All template IDs must exist and belong to your account
- File is stored in DigitalOcean Spaces cloud storage
- URL is publicly accessible for download
- File name includes timestamp for uniqueness
- Excel format (.xlsx) is used for exports
8.6 Message Template Best Practices
Template Organization
- Descriptive Names: Use clear, descriptive names like "Welcome New Customer" instead of "Template 1"
- Categorize: Use naming conventions to group related templates (e.g., "Sales - ", "Support - ")
- Status Management: Disable outdated templates instead of deleting them
- Regular Review: Audit templates quarterly to remove unused ones
Content Best Practices
- Personalization: Always use {first_name} when possible for better engagement
- Spin Text: Use variations {Hi|Hello|Hey} to make messages feel less automated
- Keep it Concise: Shorter messages have higher read rates
- Clear CTA: Include a clear call-to-action in every template
- Test Variables: Verify all variables work before using in campaigns
User Assignment Strategy
- Role-Based: Assign templates based on team roles (sales, support, etc.)
- Global Templates: Leave assignees empty for company-wide templates
- Personal Templates: Create user-specific templates for individual workflows
- Training: Document which templates to use for different scenarios
Complete Template Manager Example
class TemplateManager {
constructor(apiSid, apiKey) {
this.apiSid = apiSid;
this.apiKey = apiKey;
this.baseUrl = 'https://api.texttorrent.com/api/v1/campaigning/template';
}
async list(options = {}) {
return await this.apiCall('GET', '', options);
}
async create(templateData) {
return await this.apiCall('POST', '', {}, templateData);
}
async update(id, templateData) {
return await this.apiCall('PUT', `/${id}`, {}, templateData);
}
async delete(id) {
return await this.apiCall('DELETE', `/${id}`);
}
async export(templateIds) {
return await this.apiCall('POST', '/export', {}, { template_ids: templateIds });
}
async search(query) {
return await this.list({ search: query });
}
async getActiveTemplates() {
const templates = await this.list({ limit: 100 });
return templates.data.filter(t => t.status === 1);
}
async apiCall(method, endpoint, params = {}, body = null) {
const queryString = new URLSearchParams(params).toString();
const url = `${this.baseUrl}${endpoint}${queryString ? `?${queryString}` : ''}`;
const options = {
method,
headers: {
'X-API-SID': this.apiSid,
'X-API-PUBLIC-KEY': this.apiKey,
'Accept': 'application/json'
}
};
if (body && (method === 'POST' || method === 'PUT')) {
options.headers['Content-Type'] = 'application/json';
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
const data = await response.json();
if (!data.success) throw new Error(data.message);
return data.data;
}
}
// Usage
const manager = new TemplateManager('SID....................................', 'PK.....................................');
// List all templates
const templates = await manager.list();
// Search templates
const searchResults = await manager.search('welcome');
// Create new template
const newTemplate = await manager.create({
template_name: 'Order Confirmation',
preview_message: 'Hi {first_name}, your order #{order_id} is confirmed!',
status: true,
assignees: [101]
});
// Update template
await manager.update(12, {
template_name: 'Updated Name',
preview_message: 'Updated message',
status: true,
assignees: []
});
// Export templates
const exportData = await manager.export([12, 15, 18]);
console.log('Download:', exportData.url);
// Get only active templates
const active = await manager.getActiveTemplates();
console.log(`${active.length} active templates`);
Template Variables Reference
| Variable Type | Format | Example | Output |
|---|---|---|---|
| First Name | {first_name} |
Hi {first_name}! | Hi John! |
| Last Name | {last_name} |
Mr. {last_name} | Mr. Smith |
| Spin Text (2 options) | {opt1|opt2} |
{Hi|Hello} there! | Hi there! or Hello there! |
| Spin Text (3+ options) | {a|b|c} |
{Great|Awesome|Excellent}! | Great! or Awesome! or Excellent! |
Common Template Use Cases
| Template Type | Example Message | Assignees |
|---|---|---|
| Welcome Message | Hi {first_name}, welcome to our service! We're excited to have you. | All users |
| Order Confirmation | Thank you {first_name}! Your order #{order_id} is confirmed. | Sales team |
| Appointment Reminder | {Hi|Hello} {first_name}, reminder: appointment tomorrow at {time}. | Support team |
| Flash Sale | {Hey|Hi} {first_name}! {Limited|Flash} sale ends in 2 hours. Shop now! | Marketing team |
Common Mistakes to Avoid
- ❌ Using generic templates without personalization
- ❌ Creating too many similar templates
- ❌ Not testing variables before campaign use
- ❌ Forgetting to update outdated templates
- ❌ Deleting templates instead of disabling them
- ❌ Not assigning templates to appropriate teams
- ✅ Always use {first_name} for personalization
- ✅ Use spin text for message variations
- ✅ Test templates with sample data first
- ✅ Keep template library organized
- ✅ Disable instead of delete for historical records
- ✅ Assign templates based on roles