API Documentation

1. Getting Started1.1. Base URL1.2. HTTP Response Codes2. Authentication2.1. API Key Authentication2.2. Authentication Errors3. Contact Module3.1. Contact List Management3.1.1. Get All Contact Lists3.1.2. Create Contact List3.1.3. Update Contact List3.1.4. Toggle Bookmark Status3.1.5. Delete Contact List3.1.6. Get Contacts in a List3.2. Contact Number Management3.2.1. Verify Numbers3.2.2. Validate Phone Numbers3.2.3. Get Contact Details3.3. Contact Notes3.3.1. Add Contact Note3.3.2. Update Contact Note3.3.3. Delete Contact Note3.4. Contact Folders3.4.1. List Contact Folders3.4.2. Create Contact Folder3.4.3. Update Contact Folder3.4.4. Delete Contact Folder3.4.5. Assign Contact to Folder3.5. Contact CRUD Operations3.5.1. Add Contact3.5.2. Update Contact3.5.3. Delete Contacts3.5.4. Import Contacts from CSV3.6. Contact Validator3.6.1. Get Validation History3.6.2. Process Validation Cleanup3.6.3. Export Validation Results3.6.4. Delete Validation Record3.7. Contact Import History3.7.1. Get Import History3.7.2. Download Skipped Numbers PDF3.8. Blocked List Management3.8.1. Get Blocked List3.8.2. Add to Blocked List3.8.3. Remove from Blocked List3.8.4. Delete Blocked Contacts3.8.5. Export Blocked List3.8.6. Best Practices3.9. Opt-Out Words Management3.9.1. Get Opt-Out Words3.9.2. Create Opt-Out Word3.9.3. Update Opt-Out Word3.9.4. Delete Opt-Out Words3.9.5. Export Opt-Out Words3.9.6. Best Practices4. Inbox & Messaging4.1. Get Inbox Messages4.2. Get Last Chat4.3. Get Message Templates4.4. Get Active Numbers4.5. Get Receiver Numbers4.6. Get Chat Details4.7. Delete Chat4.8. Best Practices4.9. Send Message4.10. Start New Chat4.11. Generate AI Replies4.12. Block Contact4.13. Unblock Contact4.14. Add Event4.15. Export Inbox4.16. Move Contact to Folder4.17. Add Inbox Note4.18. Delete Inbox Note5. Sub-Account Management5.1. List Sub-Accounts5.2. Create Sub-Account5.3. Get Sub-Account Details5.4. Update Sub-Account5.5. Delete Sub-Account5.6. Change Sub-Account Status5.7. Login as Sub-User5.8. Best Practices6. Bulk Messaging & Campaigns6.1. Get Campaign Data6.2. Create Campaign6.3. Best Practices7. Campaign Analytics7.1. List Campaigns7.2. Get Campaign Counts7.3. Get Campaign Details7.4. Get Message List7.5. Get Message Counts7.6. Best Practices8. Message Templates8.1. List Message Templates8.2. Create Message Template8.3. Update Message Template8.4. Delete Message Template8.5. Export Message Templates8.6. Best Practices

Text Torrent API Documentation

Last updated: January 25, 2025

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
ℹ️ Note: All API endpoints are prefixed with /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)

CodeStatusDescriptionUse Case
200OKThe request was successfulGET, PUT, PATCH requests that successfully retrieve or update data
201CreatedThe resource was successfully createdPOST requests that create new resources (contacts, messages, sub-accounts)

Client Error Codes (4xx)

CodeStatusDescriptionCommon Causes
400Bad RequestThe request was malformed or invalidMissing required parameters, invalid data format, malformed JSON
401UnauthorizedAuthentication failed or credentials are missingMissing or invalid X-API-SID or X-API-PUBLIC-KEY headers
403ForbiddenValid authentication but insufficient permissionsTrying to access resources you don't have permission for, disabled API access
404Not FoundThe requested resource does not existInvalid endpoint URL, resource ID doesn't exist, deleted resource
422Unprocessable EntityThe request was well-formed but contains semantic errorsValidation errors, insufficient credits, business logic violations
429Too Many RequestsRate limit exceededMore than 60 requests per minute from the same API key

Server Error Codes (5xx)

CodeStatusDescriptionWhat to Do
500Internal Server ErrorAn error occurred on the serverRetry the request after a few moments. Contact support if the issue persists
503Service UnavailableThe server is temporarily unavailableWait and retry. The service may be under maintenance

Response Structure

All API responses follow a consistent JSON structure:

Success Response Format:

1{
2  "code": 200,
3  "success": true,
4  "message": "Operation completed successfully",
5  "data": {
6    // Response data here
7  },
8  "errors": null
9}

Error Response Format:

1{
2  "code": 422,
3  "success": false,
4  "message": "Validation Error",
5  "data": null,
6  "errors": {
7    "field_name": [
8      "Error message for this field"
9    ]
10  }
11}

Common Error Scenarios

1. Insufficient Credits (422):

1{
2  "code": 422,
3  "success": false,
4  "message": "You do not have enough credits to perform this action.",
5  "data": null,
6  "errors": null
7}

2. Resource Not Found (404):

1{
2  "code": 404,
3  "success": false,
4  "message": "Sub Account Not Found",
5  "data": null,
6  "errors": []
7}

3. Trial Limitation (422):

1{
2  "code": 422,
3  "success": false,
4  "message": "You cannot purchase numbers while on a trial subscription. Please upgrade to a paid plan.",
5  "data": null,
6  "errors": null
7}

4. Rate Limit Exceeded (429):

1{
2  "code": 429,
3  "success": false,
4  "message": "Too Many Requests. Please try again in 60 seconds.",
5  "data": null,
6  "errors": null
7}

Best Practices for Error Handling

  • Always check the success field to determine if the request was successful
  • Use the code field for programmatic error handling
  • Display the message field to users for user-friendly error messages
  • Parse the errors object 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 andX-API-PUBLIC-KEY headers. Unauthenticated requests will receive a 401 Unauthorizedresponse.

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 andX-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.....................................)
⚠️ Security Warning: Your API credentials provide full access to your account. Keep them secure and never share them publicly or commit them to version control.

Making Authenticated Requests

Include both X-API-SID and X-API-PUBLIC-KEY headers in all API requests:

Request Headers:

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: 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:

1curl -X GET https://api.texttorrent.com/api/v1/user/auth/me
2  -H "X-API-SID: SID...................................."
3  -H "X-API-PUBLIC-KEY: PK....................................."
4  -H "Content-Type: application/json"
5  -H "Accept: application/json""

Success Response (200 OK):

1{
2  "code": 200,
3  "success": true,
4  "message": "Successfully fetched user information",
5  "data": {
6    "id": 1,
7    "first_name": "John",
8    "last_name": "Doe",
9    "username": "johndoe",
10    "email": "john@example.com",
11    "credits": 1000,
12    "plan": {
13      "package_name": "pro",
14      "status": "Active"
15    }
16  }
17}

Example in PHP:

1$sid = 'SID....................................';
2$publicKey = 'PK.....................................';
3
4$ch = curl_init('https://api.texttorrent.com/api/v1/user/auth/me');
5curl_setopt($ch, CURLOPT_HTTPHEADER, [
6    'X-API-SID: ' . $sid,
7    'X-API-PUBLIC-KEY: ' . $publicKey,
8    'Content-Type: application/json',
9    'Accept: application/json'
10]);
11curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
12
13$response = curl_exec($ch);
14curl_close($ch);
15
16$data = json_decode($response, true);

Example in JavaScript:

1const sid = 'SID....................................';
2const publicKey = 'PK.....................................';
3
4fetch('https://api.texttorrent.com/api/v1/user/auth/me', {
5  method: 'GET',
6  headers: {
7    'X-API-SID': sid,
8    'X-API-PUBLIC-KEY': publicKey,
9    'Content-Type': 'application/json',
10    'Accept': 'application/json'
11  }
12})
13.then(response => response.json())
14.then(data => console.log(data))
15.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

Sub Account Login (X-ACT-AS-USER)

🔑 IMPORTANT FEATURE - Parent User Sub Account Access

This section describes the critical X-ACT-AS-USER header that allows parent users to perform API requests on behalf of their sub accounts. Ensure you understand the authentication flow and error handling before implementing this feature.

Parent users can authenticate as their sub accounts by including theX-ACT-AS-USER header in addition to the standard authentication headers. This allows parent users to perform actions on behalf of their sub accounts.

How It Works

  • A parent user authenticates with their own API credentials (X-API-SID and X-API-PUBLIC-KEY)
  • The parent user includes the X-ACT-AS-USER header with the sub account's email address
  • The API validates that the provided email belongs to an active sub account of the parent user
  • If valid, the request is processed as if it were from the sub account

Request Headers

Include the following headers to login as a sub user:

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3X-ACT-AS-USER: subuser@example.com
4Content-Type: application/json
5Accept: application/json

Example: Get Sub Account User Information

The following example demonstrates how to fetch user information as a sub account:

cURL Request:

1curl -X GET https://api.texttorrent.com/api/v1/user/auth/me
2  -H "X-API-SID: SID...................................."
3  -H "X-API-PUBLIC-KEY: PK....................................."
4  -H "X-ACT-AS-USER: subuser@example.com"
5  -H "Content-Type: application/json"
6  -H "Accept: application/json"

PHP Example:

1$sid = 'SID....................................';
2$publicKey = 'PK.....................................';
3$subUserEmail = 'subuser@example.com';
4
5$ch = curl_init('https://api.texttorrent.com/api/v1/user/auth/me');
6curl_setopt($ch, CURLOPT_HTTPHEADER, [
7    'X-API-SID: ' . $sid,
8    'X-API-PUBLIC-KEY: ' . $publicKey,
9    'X-ACT-AS-USER: ' . $subUserEmail,
10    'Content-Type: application/json',
11    'Accept: application/json'
12]);
13curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
14
15$response = curl_exec($ch);
16curl_close($ch);
17
18$data = json_decode($response, true);

JavaScript Example:

1const sid = 'SID....................................';
2const publicKey = 'PK.....................................';
3const subUserEmail = 'subuser@example.com';
4
5fetch('https://api.texttorrent.com/api/v1/user/auth/me', {
6  method: 'GET',
7  headers: {
8    'X-API-SID': sid,
9    'X-API-PUBLIC-KEY': publicKey,
10    'X-ACT-AS-USER': subUserEmail,
11    'Content-Type': 'application/json',
12    'Accept': 'application/json'
13  }
14})
15.then(response => response.json())
16.then(data => console.log(data))
17.catch(error => console.error('Error:', error));

Success Response (200 OK):

1{
2  "code": 200,
3  "success": true,
4  "message": "Successfully fetched user information",
5  "data": {
6    "id": 5,
7    "first_name": "Jane",
8    "last_name": "Smith",
9    "username": "janesmith",
10    "email": "subuser@example.com",
11    "credits": 500,
12    "plan": {
13      "package_name": "basic",
14      "status": "Active"
15    }
16  }
17}
⚠️ Critical Security & Validation Rules
  • MUST validate: The X-ACT-AS-USER email must belong to the parent user's sub account
  • ALWAYS handle 401: Return Unauthorized if the provided email is not a valid sub account
  • Audit trail: All requests with X-ACT-AS-USER are logged under the sub account for compliance
  • Rate limiting: Applies per parent user, not per sub account

Important Notes

  • The X-ACT-AS-USER header must contain a valid sub account email address
  • The sub account email must belong to the parent user making the request
  • If the provided email is not a sub account of the parent user, an 401 Unauthorized response will be returned
  • All API operations performed with X-ACT-AS-USER are logged under the sub account
  • Rate limits are applied per parent user account, not per sub account
  • The X-ACT-AS-USER header is optional. If not provided, the API will operate as the parent user

Authentication Error Responses

If authentication fails, the API will return appropriate HTTP status codes and error messages:

Status CodeErrorDescription
401UnauthorizedNo authentication credentials provided, invalid credentials, or invalid sub-user email in X-ACT-AS-USER header
403ForbiddenValid credentials but insufficient permissions for the requested resource
429Too Many RequestsRate limit exceeded (60 requests per minute)

401 Unauthorized Response Example:

1{
2  "status": false,
3  "message": "Unauthorized",
4  "errors": []
5}

401 Invalid Sub-User Response Example:

1{
2  "status": false,
3  "message": "The provided user is not your sub account",
4  "errors": []
5}

429 Rate Limit Response Example:

1{
2  "status": false,
3  "message": "Too Many Requests. Please try again in 60 seconds.",
4  "errors": []
5}

403 Forbidden Response Example:

1{
2  "status": false,
3  "message": "Unauthorized",
4  "errors": []
5}

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.

GET: /api/v1/contact/list

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
emailstringNoBase64 encoded email address of a sub-user. Main accounts can use this to retrieve contact lists belonging to their sub-users.
ℹ️ Sub-Account Access: Main account holders can view contact lists of their sub-users by providing the sub-user s email address (Base64 encoded) via the email parameter. If the provided email does not belong to a sub-user associated with your main account, a 403 Forbidden response will be returned.

Example 1: Basic Request (Main Account)

1curl -X GET "https://api.texttorrent.com/api/v1/contact/list"
2  -H "X-API-SID: SID...................................."
3  -H "X-API-PUBLIC-KEY: PK....................................."
4  -H "Accept: application/json"

Example 2: Retrieve Sub-User's Contact Lists

1# First, encode the sub-user's email in Base64
2# Example: subuser@example.com → c3VidXNlckBleGFtcGxlLmNvbQ==
3
4curl -X GET "https://api.texttorrent.com/api/v1/contact/list?email=c3VidXNlckBleGFtcGxlLmNvbQ==" 
5  -H "X-API-SID: SID...................................." 
6  -H "X-API-PUBLIC-KEY: PK....................................." 
7  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact list retrieved",
5    "data": [
6        {
7            "id": 45,
8            "user_id": 1,
9            "name": "VIP Customers",
10            "bookmarked": 1,
11            "created_at": "2025-01-15T10:30:00.000000Z",
12            "total_contacts": 150
13        },
14        {
15            "id": 44,
16            "user_id": 1,
17            "name": "Newsletter Subscribers",
18            "bookmarked": 0,
19            "created_at": "2025-01-14T14:22:00.000000Z",
20            "total_contacts": 523
21        },
22        {
23            "id": 43,
24            "user_id": 1,
25            "name": "Event Attendees",
26            "bookmarked": 0,
27            "created_at": "2025-01-10T09:15:00.000000Z",
28            "total_contacts": 89
29        }
30    ],
31    "errors": null
32}

Error Response - Unauthorized Sub-User Access (403 Forbidden)

1{
2    "code": 403,
3    "success": false,
4    "message": "Unauthorized access",
5    "data": null
6}

Response Fields

FieldTypeDescription
idintegerUnique identifier for the contact list
user_idintegerID of the user who owns this list
namestringName of the contact list (max 15 characters)
bookmarkedbooleanWhether the list is bookmarked/favorited (1 = true, 0 = false)
created_atstringTimestamp when the list was created (ISO 8601 format)
total_contactsintegerTotal 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 when no email parameter is provided
  • Sub-User Access: Main accounts can retrieve contact lists for their sub-users by providing the Base64 encoded email in the email parameter
  • To encode email in Base64: echo -n "subuser@example.com" | base64 (Linux/Mac) or use an online Base64 encoder
  • Attempting to access data for an email that is not associated with your sub-users will result in a 403 Forbidden error

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.

POST: /api/v1/contact/list/add/bookmark

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
namestringYesName of the contact list (max 15 characters, must be unique)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/contact/list/add/bookmark" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "name": "New Leads 2025"
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact list created successfully",
5    "data": {
6        "id": 46,
7        "user_id": 1,
8        "name": "New Leads 2025",
9        "bookmarked": 0,
10        "created_at": "2025-10-17T12:34:56.000000Z",
11        "updated_at": "2025-10-17T12:34:56.000000Z"
12    },
13    "errors": null
14}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "name": [
8            "The name field is required."
9        ]
10    }
11}

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.

PUT: /api/v1/contact/list/update/bookmark

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
list_idintegerYesID of the contact list to update (must exist)
namestringYesNew name for the contact list (max 15 characters, must be unique)

Example Request

1curl -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 '{
2    "list_id": 46,
3    "name": "Hot Leads 2025"
4  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact list updated successfully",
5    "data": {
6        "id": 46,
7        "user_id": 1,
8        "name": "Hot Leads 2025",
9        "bookmarked": 0,
10        "created_at": "2025-10-17T12:34:56.000000Z",
11        "updated_at": "2025-10-17T13:45:22.000000Z"
12    },
13    "errors": null
14}

Error Response - List Not Found (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "list_id": [
8            "The selected list id is invalid."
9        ]
10    }
11}

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.

PUT: /api/v1/contact/list/toggle/bookmark/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

URL Parameters

ParameterTypeRequiredDescription
idintegerYesID of the contact list to toggle

Example Request - Bookmark a List

1curl -X PUT "https://api.texttorrent.com/api/v1/contact/list/toggle/bookmark/46" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact list updated successfully",
5    "data": {
6        "id": 46,
7        "user_id": 1,
8        "name": "Hot Leads 2025",
9        "bookmarked": 1,
10        "created_at": "2025-10-17T12:34:56.000000Z",
11        "updated_at": "2025-10-17T13:45:22.000000Z"
12    },
13    "errors": null
14}

Error Response - List Not Found (200 with error)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to update contact list",
5    "data": null,
6    "errors": []
7}

Notes

  • If bookmarked is 0, it will be set to 1 (bookmarked)
  • If bookmarked is 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.

DELETE: /api/v1/contact/list/delete/bookmark
⚠️ Warning: This operation permanently deletes contacts and cannot be undone. Use with caution.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
list_idintegerConditionalID of the contact list to delete (required if type is not provided)
typestringConditionalType of contacts to delete: default, blacklisted, orunlisted

Delete Types Explained

  • Specific List: Provide list_id to 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

1curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "list_id": 46
8  }'

Success Response - Specific List (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact list deleted successfully",
5    "data": null,
6    "errors": null
7}

Example 2: Delete All Default Contacts

1curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "type": "default"
8  }'

Success Response - Default Contacts (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "All default contacts deleted successfully",
5    "data": null,
6    "errors": null
7}

Example 3: Delete All Blacklisted Contacts

1curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "type": "blacklisted"
8  }'

Success Response - Blacklisted Contacts (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "All blacklisted contacts deleted successfully",
5    "data": null,
6    "errors": null
7}

Example 4: Delete All Unlisted Contacts

1curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "type": "unlisted"
8  }'

Success Response - Unlisted Contacts (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "All unlisted contacts deleted successfully",
5    "data": null,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "list_id": [
8            "The selected list id is invalid."
9        ]
10    }
11}

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_id OR type, 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.

GET: /api/v1/contact/{listId}/contacts

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

URL Parameters

ParameterTypeRequiredDescription
listIdintegerYesID of the contact list to retrieve contacts from

Query Parameters

ParameterTypeRequiredDefaultDescription
limitintegerNo10Number of contacts per page (pagination)
searchstringNo-Search term to filter contacts by first name, last name, or phone number
sort_list_idstring/integerNo-Filter by list type: specific list ID, unlisted, or blacklisted
pageintegerNo1Page number for pagination

Example 1: Basic Request

1curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example 2: With Pagination

1curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts?limit=25&page=2" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example 3: With Search

1curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts?search=john" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example 4: Filter by Unlisted Contacts

1curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts?sort_list_id=unlisted" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example 5: Filter by Blacklisted Contacts

1curl -X GET "https://api.texttorrent.com/api/v1/contact/46/contacts?sort_list_id=blacklisted" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contacts retrieved",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "id": 1234,
10                "first_name": "John",
11                "last_name": "Doe",
12                "number": "+19292223344",
13                "email": "john.doe@example.com",
14                "company": "Tech Solutions Inc",
15                "valid": 1,
16                "validation_process": 1,
17                "folder_id": null
18            },
19            {
20                "id": 1235,
21                "first_name": "Jane",
22                "last_name": "Smith",
23                "number": "+19293334455",
24                "email": "jane.smith@example.com",
25                "company": "Marketing Pro",
26                "valid": 1,
27                "validation_process": 1,
28                "folder_id": 5
29            }
30        ],
31        "first_page_url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=1",
32        "from": 1,
33        "last_page": 5,
34        "last_page_url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=5",
35        "links": [
36            {
37                "url": null,
38                "label": "« Previous",
39                "active": false
40            },
41            {
42                "url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=1",
43                "label": "1",
44                "active": true
45            },
46            {
47                "url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=2",
48                "label": "2",
49                "active": false
50            }
51        ],
52        "next_page_url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=2",
53        "path": "https://api.texttorrent.com/api/v1/contact/46/contacts",
54        "per_page": 10,
55        "prev_page_url": null,
56        "to": 10,
57        "total": 50
58    },
59    "errors": null
60}

Contact Response Fields

FieldTypeDescription
idintegerUnique identifier for the contact
first_namestringContact's first name
last_namestringContact's last name
numberstringContact's phone number
emailstringContact's email address (nullable)
companystringContact's company name (nullable)
validbooleanWhether the Contact's phone number is valid (1 = valid, 0 = invalid)
validation_processbooleanWhether the contact has been through validation (1 = yes, 0 = no)
folder_idintegerID 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, and number fields
  • 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 number
  • last_page: Total number of pages
  • per_page: Number of items per page
  • total: Total number of contacts matching the query
  • from and to: 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.

POST: /api/v1/contact/number/verify/confirmation

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idarrayYesArray of contact IDs to verify (each ID must exist in contacts table)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/contact/number/verify/confirmation" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_id": [1234, 1235, 1236, 1237, 1238]
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Number validation result",
5    "data": {
6        "total_numbers_selected": 5,
7        "already_validated": 2,
8        "available_validation": 3,
9        "credits": 997
10    },
11    "errors": null
12}

Response Fields

FieldTypeDescription
total_numbers_selectedintegerTotal number of contacts selected for verification
already_validatedintegerCount of contacts that have already been validated
available_validationintegerCount of contacts that need validation (not yet validated)
creditsintegerYour remaining credits after validation (each validation costs 1 credit)

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_id": [
8            "The contact id field is required."
9        ]
10    }
11}

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.

POST: /api/v1/contact/number/validate
⚠️ Requirements: This endpoint requires an active subscription and sufficient credits. Each validation costs 1 credit per contact. Auto-recharge is triggered if enabled and credits fall below threshold.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idsarrayYesArray of contact IDs to validate (each ID must exist)
validator_creditsnumberYesTotal credits required for validation (minimum: 0)
available_validationintegerYesNumber of contacts available for validation (from verify endpoint)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/contact/number/validate" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_ids": [1234, 1235, 1236],
8    "validator_credits": 3,
9    "available_validation": 3
10  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Number validation started successfully",
5    "data": {
6        "total_numbers_selected": 3,
7        "available_validation": 3,
8        "credits": 997
9    },
10    "errors": null
11}

Response Fields

FieldTypeDescription
total_numbers_selectedintegerTotal number of contacts selected for validation
available_validationintegerNumber of contacts that will be validated
creditsintegerYour remaining credits after validation

Error Response - Insufficient Credits (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "You do not have enough credits to perform this action.",
5    "data": null,
6    "errors": []
7}

Error Response - No Active Subscription (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "You do not have an active subscription to perform this action.",
5    "data": null,
6    "errors": []
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_ids": [
8            "The contact ids field is required."
9        ]
10    }
11}

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

  1. Pre-validation: Call /verify/confirmation endpoint to get credit calculation
  2. Credit Check: System checks if you have enough credits
  3. Auto-Recharge: If enabled and credits are low (below 1000), auto-recharge is triggered
  4. Validation Job: Validation is queued and processed asynchronously
  5. Credit Deduction: Credits are deducted immediately when validation starts
  6. 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

1// Step 1: Verify numbers and get cost estimate
2const verifyResponse = await fetch('https://api.texttorrent.com/api/v1/contact/number/verify/confirmation', {
3    method: 'POST',
4    headers: {
5        'X-API-SID': 'SID....................................',
6        'X-API-PUBLIC-KEY': 'PK.....................................',
7        'Content-Type': 'application/json'
8    },
9    body: JSON.stringify({
10        contact_id: [1234, 1235, 1236]
11    })
12});
13
14const verifyData = await verifyResponse.json();
15console.log('Credits after validation:', verifyData.data.credits);
16console.log('Contacts to validate:', verifyData.data.available_validation);
17
18// Step 2: If user confirms, proceed with validation
19if (confirm('Validate "$"{verifyData.data.available_validation} contacts for "$"{verifyData.data.available_validation} credits?')) {
20    const validateResponse = await fetch('https://api.texttorrent.com/api/v1/contact/number/validate', {
21        method: 'POST',
22        headers: {
23            'X-API-SID': 'SID....................................',
24            'X-API-PUBLIC-KEY': 'PK.....................................',
25            'Content-Type': 'application/json'
26        },
27        body: JSON.stringify({
28            contact_ids: [1234, 1235, 1236],
29            validator_credits: verifyData.data.available_validation,
30            available_validation: verifyData.data.available_validation
31        })
32    });
33
34    const validateData = await validateResponse.json();
35    console.log('Validation started:', validateData.message);
36}

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.

GET: /api/v1/contact/number/{contactId}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

URL Parameters

ParameterTypeRequiredDescription
contactIdintegerYesUnique identifier of the contact to retrieve

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/contact/number/1234" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact details retrieved successfully",
5    "data": {
6        "id": 1234,
7        "first_name": "John",
8        "last_name": "Doe",
9        "full_name": "John Doe",
10        "imgWords": "JD",
11        "email": "john.doe@example.com",
12        "number": "+19292223344",
13        "company": "Tech Solutions Inc",
14        "notes": [
15            {
16                "id": 1,
17                "contact_id": 1234,
18                "note": "Follow up on product demo",
19                "created_at": "2025-10-15T10:30:00.000000Z",
20                "updated_at": "2025-10-15T10:30:00.000000Z"
21            },
22            {
23                "id": 2,
24                "contact_id": 1234,
25                "note": "Interested in enterprise plan",
26                "created_at": "2025-10-16T14:20:00.000000Z",
27                "updated_at": "2025-10-16T14:20:00.000000Z"
28            }
29        ]
30    },
31    "errors": null
32}

Response Fields

FieldTypeDescription
idintegerUnique identifier for the contact
first_namestringContact's first name
last_namestringContact's last name
full_namestringContact's full name (first + last)
imgWordsstringInitials for avatar display (first letter of first + last name)
emailstringContact's email address (nullable)
numberstringContact's phone number with country code
companystringContact's company name (nullable)
notesarrayArray of note objects associated with this contact

Error Response - Contact Not Found (200 OK with null data)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact not found",
5    "data": null,
6    "errors": null
7}

Notes Array Structure

Each note object in the notes array contains:

FieldTypeDescription
idintegerUnique identifier for the note
contact_idintegerID of the contact this note belongs to
notestringThe note text content
created_atstringTimestamp when note was created (ISO 8601)
updated_atstringTimestamp 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 (imgWords field)

Important Notes

  • Returns null data if contact doesn't exist (not a 404 error)
  • imgWords is pre-calculated for avatar display (e.g., "JD" for "John Doe")
  • Notes are automatically included in the response
  • Phone number is returned from the phone field (not number field 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.

POST: /api/v1/contact/number/note/add

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idintegerYesID of the contact to add note to (must exist in contacts table)
notestringYesThe note content (max 2000 characters)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/contact/number/note/add" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_id": 1234,
8    "note": "Follow up on product demo scheduled for next week. Customer interested in enterprise plan."
9  }'

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Note added successfully",
5    "data": {
6        "id": 567,
7        "contact_id": 1234,
8        "note": "Follow up on product demo scheduled for next week. Customer interested in enterprise plan.",
9        "created_at": "2025-10-17T14:30:22.000000Z",
10        "updated_at": "2025-10-17T14:30:22.000000Z"
11    },
12    "errors": null
13}

Response Fields

FieldTypeDescription
idintegerUnique identifier for the note
contact_idintegerID of the contact this note belongs to
notestringThe note content
created_atstringTimestamp when the note was created (ISO 8601 format)
updated_atstringTimestamp when the note was last updated (ISO 8601 format)

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_id": [
8            "The contact id field is required."
9        ],
10        "note": [
11            "The note field is required."
12        ]
13    }
14}

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

1async function addContactNote(contactId, noteText) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/contact/number/note/add', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Content-Type': 'application/json',
8            'Accept': 'application/json'
9        },
10        body: JSON.stringify({
11            contact_id: contactId,
12            note: noteText
13        })
14    });
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Note added:', data.data);
20        return data.data;
21    } else {
22        console.error('Error:', data.message);
23        throw new Error(data.message);
24    }
25}

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.

PUT: /api/v1/contact/number/note/update

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_note_idintegerYesID of the note to update (must exist in contact_notes table)
notestringYesThe updated note content (max 2000 characters)

Example Request

1curl -X PUT "https://api.texttorrent.com/api/v1/contact/number/note/update" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_note_id": 567,
8    "note": "Product demo completed successfully. Customer confirmed interest in enterprise plan. Scheduled follow-up for contract review on Oct 25."
9  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Note updated successfully",
5    "data": null,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_note_id": [
8            "The selected contact note id is invalid."
9        ]
10    }
11}

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)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to update note",
5    "data": null,
6    "errors": []
7}

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/"$"{id}
  • Only the note content can be updated; the contact_id remains unchanged
  • The updated_at timestamp is automatically updated

Example in PHP

1$noteId = 567;
2$updatedNote = 'Product demo completed successfully...';
3
4$ch = curl_init('https://api.texttorrent.com/api/v1/contact/number/note/update');
5curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
6curl_setopt($ch, CURLOPT_HTTPHEADER, [
7    'X-API-SID: SID....................................',
8    'X-API-PUBLIC-KEY: PK.....................................',
9    'Content-Type: application/json',
10    'Accept: application/json'
11]);
12curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
13    'contact_note_id' => $noteId,
14    'note' => $updatedNote
15]));
16curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
17
18$response = curl_exec($ch);
19curl_close($ch);
20
21$data = json_decode($response, true);
22if ($data['success']) {
23    echo 'Note updated successfully';
24}

3.3.3 Delete Contact Note

Permanently delete a contact note. This operation cannot be undone, so use it carefully.

DELETE: /api/v1/contact/number/note/{id}
⚠️ Warning: This operation permanently deletes the note and cannot be undone.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

URL Parameters

ParameterTypeRequiredDescription
idintegerYesID of the note to delete

Example Request

1curl -X DELETE "https://api.texttorrent.com/api/v1/contact/number/note/567" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Note deleted successfully",
5    "data": null,
6    "errors": null
7}

Error Response - Note Not Found or Delete Failed (400 Bad Request)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to delete note",
5    "data": null,
6    "errors": []
7}}

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

1async function deleteContactNote(noteId) {
2    // Show confirmation dialog
3    if (!confirm('Are you sure you want to delete this note?')) {
4        return;
5    }
6
7    const response = await fetch('https://api.texttorrent.com/api/v1/contact/number/note/"$"{noteId}', {
8        method: 'DELETE',
9        headers: {
10            'X-API-SID': 'SID....................................',
11            'X-API-PUBLIC-KEY': 'PK.....................................',
12            'Accept': 'application/json'
13        }
14    });
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Note deleted successfully');
20        // Refresh the contact details to show updated notes list
21        await refreshContactDetails();
22    } else {
23        console.error('Error:', data.message);
24        alert('Failed to delete note');
25    }
26}
27
28// Usage
29deleteContactNote(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// 1. Add a new note
2const newNote = await addContactNote(1234, 'Initial contact - interested in demo');
3
4// 2. Later, update the note with more details
5await updateContactNote(newNote.id, 'Initial contact - demo scheduled for Oct 20, 2PM EST');
6
7// 3. Retrieve all notes for the contact
8const contact = await getContactDetails(1234);
9console.log('All notes:', contact.notes);
10
11// 4. If needed, delete outdated notes
12await 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.

GET: /api/v1/contact/number/folder/list

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
searchstringNoSearch term to filter folders by name (partial match)

Example Request - Get All Folders

1curl -X GET "https://api.texttorrent.com/api/v1/contact/number/folder/list" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - Search Folders

1curl -X GET "https://api.texttorrent.com/api/v1/contact/number/folder/list?search=VIP" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Folders retrieved successfully",
5    "data": [
6        {
7            "id": 5,
8            "name": "VIP Clients",
9            "created_at": "2025-09-10T08:30:00.000000Z"
10        },
11        {
12            "id": 8,
13            "name": "Hot Leads",
14            "created_at": "2025-10-01T14:22:00.000000Z"
15        },
16        {
17            "id": 12,
18            "name": "Newsletter Subscribers",
19            "created_at": "2025-10-15T09:15:00.000000Z"
20        }
21    ],
22    "errors": null
23}

Response Fields

FieldTypeDescription
idintegerUnique identifier for the folder
namestringName of the folder
created_atstringTimestamp when the folder was created (ISO 8601 format)

Error Response - Failed to Retrieve (400 Bad Request)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to retrieve folders",
5    "data": null,
6    "errors": []
7}

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.

POST: /api/v1/contact/number/folder/create

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
folder_namestringYesName of the folder to create (max 255 characters)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/contact/number/folder/create" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "folder_name": "VIP Clients"
8  }'

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Folder created successfully",
5    "data": {
6        "id": 15,
7        "name": "VIP Clients",
8        "user_id": 1,
9        "created_at": "2025-10-17T15:30:45.000000Z",
10        "updated_at": "2025-10-17T15:30:45.000000Z"
11    },
12    "errors": null
13}

Response Fields

FieldTypeDescription
idintegerUnique identifier for the newly created folder
namestringName of the folder
user_idintegerID of the user who owns this folder
created_atstringTimestamp when the folder was created (ISO 8601 format)
updated_atstringTimestamp when the folder was last updated (ISO 8601 format)

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "folder_name": [
8            "The folder name field is required."
9        ]
10    }
11}

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)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to create folder",
5    "data": null,
6    "errors": []
7}

Example in JavaScript

1async function createFolder(folderName) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/contact/number/folder/create', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Content-Type': 'application/json',
8            'Accept': 'application/json'
9        },
10        body: JSON.stringify({
11            folder_name: folderName
12        })
13    });
14
15    const data = await response.json();
16
17    if (data.success) {
18        console.log('Folder created:', data.data);
19        return data.data;
20    } else {
21        console.error('Error:', data.message);
22        throw new Error(data.message);
23    }
24}
25
26// Usage
27createFolder('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).

PUT: /api/v1/contact/number/folder/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

URL Parameters

ParameterTypeRequiredDescription
idintegerYesID of the folder to update

Request Body Parameters

ParameterTypeRequiredDescription
folder_namestringYesNew name for the folder (max 255 characters)

Example Request

1curl -X PUT "https://api.texttorrent.com/api/v1/contact/number/folder/15" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "folder_name": "Premium VIP Clients"
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Tag updated successfully",
5    "data": {
6        "id": 15,
7        "name": "Premium VIP Clients",
8        "user_id": 1,
9        "created_at": "2025-10-17T15:30:45.000000Z",
10        "updated_at": "2025-10-17T16:20:30.000000Z"
11    },
12    "errors": null
13}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "folder_name": [
8            "The folder name field is required."
9        ]
10    }
11}

Error Response - Update Failed (400 Bad Request)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to update folder",
5    "data": null,
6    "errors": []
7}

Important Notes

  • The response message says "Tag updated successfully" (folders/tags are interchangeable)
  • Returns the complete updated folder object
  • The updated_at timestamp 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.

DELETE: /api/v1/contact/number/folder/{id}
⚠️ Note: Deleting a folder removes it permanently. Contacts in the folder are not deleted, only their folder assignment is cleared.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

URL Parameters

ParameterTypeRequiredDescription
idintegerYesID of the folder to delete

Example Request

1curl -X DELETE "https://api.texttorrent.com/api/v1/contact/number/folder/15" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Tag deleted successfully",
5    "data": null,
6    "errors": null
7}

Error Response - Delete Failed (400 Bad Request)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to delete tag",
5    "data": null,
6    "errors": []
7}

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_id set to null
  • The operation is permanent and cannot be undone
  • Returns 400 error if folder doesn't exist or deletion fails

Example in JavaScript

1async function deleteFolder(folderId) {
2    if (!confirm('Are you sure you want to delete this folder? Contacts will not be deleted.')) {
3        return;
4    }
5
6    const response = await fetch('https://api.texttorrent.com/api/v1/contact/number/folder/"$"{folderId}', {
7        method: 'DELETE',
8        headers: {
9            'X-API-SID': 'SID....................................',
10            'X-API-PUBLIC-KEY': 'PK.....................................',
11            'Accept': 'application/json'
12        }
13    });
14
15    const data = await response.json();
16
17    if (data.success) {
18        console.log('Folder deleted successfully');
19        // Refresh folder list
20        await loadFolders();
21    } else {
22        console.error('Error:', data.message);
23        alert('Failed to delete folder');
24    }
25}
26
27// Usage
28deleteFolder(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.

POST: /api/v1/contact/add/folder

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idintegerYesID of the contact to assign (must exist in contacts table)
folder_idintegerYesID of the folder to assign contact to (must exist in contact_folders table)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/contact/add/folder" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_id": 1234,
8    "folder_id": 15
9  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact added to folder successfully",
5    "data": {
6        "id": 1234,
7        "first_name": "John",
8        "last_name": "Doe",
9        "number": "+19292223344",
10        "email": "john.doe@example.com",
11        "company": "Tech Solutions Inc",
12        "list_id": 45,
13        "folder_id": 15,
14        "user_id": 1,
15        "blacklisted": 0,
16        "valid": 1,
17        "validation_process": 1,
18        "created_at": "2025-09-15T10:30:00.000000Z",
19        "updated_at": "2025-10-17T16:45:22.000000Z"
20    },
21    "errors": null
22}

Response Fields

Returns the complete updated contact object with all fields including the new folder_id.

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_id": [
8            "The selected contact id is invalid."
9        ],
10        "folder_id": [
11            "The selected folder id is invalid."
12        ]
13    }
14}

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)

1{
2    "code": 400,
3    "success": false,
4    "message": "Contact removed from folder successfully",
5    "data": null,
6    "errors": []
7}

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_at timestamp is automatically updated

Example in PHP

1function assignContactToFolder($contactId, $folderId) {
2    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/add/folder');
3
4    curl_setopt($ch, CURLOPT_POST, true);
5    curl_setopt($ch, CURLOPT_HTTPHEADER, [
6        'X-API-SID: SID....................................',
7        'X-API-PUBLIC-KEY: PK.....................................',
8        'Content-Type: application/json',
9        'Accept: application/json'
10    ]);
11    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
12        'contact_id' => $contactId,
13        'folder_id' => $folderId
14    ]));
15    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
16
17    $response = curl_exec($ch);
18    curl_close($ch);
19
20    $data = json_decode($response, true);
21
22    if ($data['success']) {
23        echo 'Contact assigned to folder successfully';
24        return $data['data'];
25    } else {
26        echo 'Error: ' . $data['message'];
27        return null;
28    }
29}
30
31// Usage
32assignContactToFolder(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

1List: Customers
2├── Folder: VIP Clients
3├── Folder: Regular Customers
4└── Folder: Churned Customers
5
6List: Leads
7├── Folder: Hot Leads
8├── Folder: Warm Leads
9└── Folder: Cold Leads
10
11List: Event Contacts
12├── Folder: 2025 Q1 Webinar
13├── Folder: 2025 Q2 Conference
14└── Folder: 2025 Q3 Workshop

Workflow Example

1// Complete folder workflow
2async function organizeFolderWorkflow() {
3    // 1. Create folders for organization
4    const vipFolder = await createFolder('VIP Clients');
5    const hotLeadsFolder = await createFolder('Hot Leads');
6
7    // 2. List all folders to verify
8    const allFolders = await listFolders();
9    console.log('All folders:', allFolders);
10
11    // 3. Assign contacts to folders
12    await assignContactToFolder(1234, vipFolder.id);
13    await assignContactToFolder(1235, hotLeadsFolder.id);
14
15    // 4. Search for specific folder
16    const vipFolders = await listFolders('VIP');
17    console.log('VIP folders:', vipFolders);
18
19    // 5. Update folder name if needed
20    await updateFolder(vipFolder.id, 'Premium VIP Clients');
21
22    // 6. Clean up unused folders
23    // await deleteFolder(oldFolderId);
24}

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)
ℹ️ Important: All phone numbers are automatically prefixed with "+1" for US numbers. The API assumes North American phone numbers. Contact lists are limited to 10,000 contacts maximum.

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.

POST: /api/v1/contact/add

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
first_namestringYesContact's first name (max 50 characters)
last_namestringNoContact's last name (max 50 characters)
numberstringYesPhone number (7-14 digits, automatically prefixed with +1)
emailstringNoEmail address (max 100 characters, must be valid email format)
companystringNoCompany name (max 100 characters)
list_idintegerYesID of the contact list (must exist in contact_lists table)
typestringNoContact 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

1curl -X POST "https://api.texttorrent.com/api/v1/contact/add" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "first_name": "John",
8    "last_name": "Doe",
9    "number": "2025551234",
10    "email": "john.doe@example.com",
11    "company": "Tech Solutions Inc",
12    "list_id": 45,
13    "type": "default"
14  }'

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Contact added successfully",
5    "data": {
6        "id": 5678,
7        "first_name": "John",
8        "last_name": "Doe",
9        "number": "+12025551234",
10        "email": "john.doe@example.com",
11        "company": "Tech Solutions Inc",
12        "list_id": 45,
13        "folder_id": null,
14        "user_id": 1,
15        "blacklisted": 0,
16        "valid": 0,
17        "validation_process": 0,
18        "created_at": "2025-10-17T18:30:45.000000Z",
19        "updated_at": "2025-10-17T18:30:45.000000Z"
20    },
21    "errors": null
22}

Response Fields

FieldTypeDescription
idintegerUnique identifier for the contact
numberstringPhone number with +1 prefix automatically added
list_idinteger|nullID of the list (null for blacklisted/unlisted contacts)
blacklistedinteger1 if blacklisted, 0 otherwise
validinteger1 if number validated, 0 otherwise
validation_processinteger1 if validation completed, 0 otherwise

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "first_name": [
8            "The first name field is required."
9        ],
10        "number": [
11            "The number field is required.",
12            "The number format is invalid."
13        ],
14        "list_id": [
15            "The selected list id is invalid."
16        ]
17    }
18}

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)

1{
2    "code": 422,
3    "success": false,
4    "message": "List can't have more than 10000 contacts.",
5    "data": null,
6    "errors": null
7}

Error Response - Failed to Add (400 Bad Request)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to add contact",
5    "data": null,
6    "errors": []
7}

Example in JavaScript

1async function addContact(contactData) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/contact/add', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Content-Type': 'application/json',
8            'Accept': 'application/json'
9        },
10        body: JSON.stringify({
11            first_name: contactData.firstName,
12            last_name: contactData.lastName,
13            number: contactData.phoneNumber,
14            email: contactData.email,
15            company: contactData.company,
16            list_id: contactData.listId,
17            type: contactData.type || 'default'
18        })
19    });
20
21    const data = await response.json();
22
23    if (data.success) {
24        console.log('Contact added:', data.data);
25        return data.data;
26    } else {
27        console.error('Error:', data.message);
28        if (data.errors) {
29            Object.entries(data.errors).forEach(([field, messages]) => {
30                console.error('"$"{field}:', messages.join(', '));
31            });
32        }
33        throw new Error(data.message);
34    }
35}
36
37// Usage
38addContact({
39    firstName: 'John',
40    lastName: 'Doe',
41    phoneNumber: '2025551234',
42    email: 'john.doe@example.com',
43    company: 'Tech Solutions Inc',
44    listId: 45
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.

PUT: /api/v1/contact/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

URL Parameters

ParameterTypeRequiredDescription
idintegerYesID of the contact to update

Request Body Parameters

ParameterTypeRequiredDescription
first_namestringYesContact's first name (max 50 characters)
last_namestringNoContact's last name (max 50 characters)
mobile_numberstringYesPhone number (max 20 characters, automatically prefixed with +1)
emailstringNoEmail address (max 100 characters, must be valid email format)
companystringNoCompany name (max 100 characters)

Example Request

1curl -X PUT "https://api.texttorrent.com/api/v1/contact/5678" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................."   -H "Content-Type: application/json" 
4  -H "Accept: application/json" 
5  -d '{
6    "first_name": "John",
7    "last_name": "Smith",
8    "mobile_number": "2025559999",
9    "email": "john.smith@newcompany.com",
10    "company": "New Tech Corp"
11  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact updated successfully",
5    "data": {
6        "id": 5678,
7        "first_name": "John",
8        "last_name": "Smith",
9        "number": "+12025559999",
10        "email": "john.smith@newcompany.com",
11        "company": "New Tech Corp",
12        "list_id": 45,
13        "folder_id": null,
14        "user_id": 1,
15        "blacklisted": 0,
16        "valid": 0,
17        "validation_process": 0,
18        "created_at": "2025-10-17T18:30:45.000000Z",
19        "updated_at": "2025-10-17T19:15:22.000000Z"
20    },
21    "errors": null
22}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "first_name": [
8            "The first name field is required."
9        ],
10        "mobile_number": [
11            "The mobile number field is required."
12        ],
13        "email": [
14            "The email must be a valid email address."
15        ]
16    }
17}

Error Response - Update Failed (400 Bad Request)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to update contact",
5    "data": null,
6    "errors": []
7}

Important Notes

  • Field name is mobile_number (not number like 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_at timestamp is automatically updated
  • Returns the complete updated contact object

Example in PHP

1function updateContact($contactId, $contactData) {
2    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/' . $contactId);
3
4    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
5    curl_setopt($ch, CURLOPT_HTTPHEADER, [
6        'X-API-SID: SID....................................',
7        'X-API-PUBLIC-KEY: PK.....................................',
8        'Content-Type: application/json',
9        'Accept: application/json'
10    ]);
11    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
12        'first_name' => $contactData['first_name'],
13        'last_name' => $contactData['last_name'] ?? null,
14        'mobile_number' => $contactData['mobile_number'],
15        'email' => $contactData['email'] ?? null,
16        'company' => $contactData['company'] ?? null
17    ]));
18    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
19
20    $response = curl_exec($ch);
21    curl_close($ch);
22
23    $data = json_decode($response, true);
24
25    if ($data['success']) {
26        echo 'Contact updated successfully';
27        return $data['data'];
28    } else {
29        echo 'Error: ' . $data['message'];
30        return null;
31    }
32}
33
34// Usage
35updateContact(5678, [
36    'first_name' => 'John',
37    'last_name' => 'Smith',
38    'mobile_number' => '2025559999',
39    'email' => 'john.smith@newcompany.com',
40    'company' => 'New Tech Corp'
41]);

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.

DELETE: /api/v1/contact/
⚠️ Warning: Deleting contacts is permanent and cannot be undone. Make sure to backup important contact data before deletion. All associated notes and folder assignments will also be removed.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idsarrayYesArray of contact IDs to delete (all must exist in contacts table)

Example Request - Delete Single Contact

1curl -X DELETE "https://api.texttorrent.com/api/v1/contact/" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_ids": [5678]
8  }'

Example Request - Bulk Delete

1curl -X DELETE "https://api.texttorrent.com/api/v1/contact/" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_ids": [5678, 5679, 5680, 5681]
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contacts deleted successfully",
5    "data": null,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_ids": [
8            "The contact ids field is required.",
9            "The contact ids must be an array."
10        ],
11        "contact_ids.0": [
12            "The selected contact ids.0 is invalid."
13        ]
14    }
15}

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)

1{
2    "code": 400,
3    "success": false,
4    "message": "Failed to delete contacts",
5    "data": null,
6    "errors": []
7}

Example in JavaScript

1async function deleteContacts(contactIds) {
2    // Confirm deletion
3    if (!confirm('Are you sure you want to delete "$"{contactIds.length} contact(s)? This cannot be undone.')) {
4        return;
5    }
6
7    const response = await fetch('https://api.texttorrent.com/api/v1/contact/', {
8        method: 'DELETE',
9        headers: {
10            'X-API-SID': 'SID....................................',
11            'X-API-PUBLIC-KEY': 'PK.....................................',
12            'Content-Type': 'application/json',
13            'Accept': 'application/json'
14        },
15        body: JSON.stringify({
16            contact_ids: contactIds
17        })
18    });
19
20    const data = await response.json();
21
22    if (data.success) {
23        console.log('Contacts deleted:', contactIds.length);
24        // Refresh contact list
25        await loadContacts();
26    } else {
27        console.error('Error:', data.message);
28        alert('Failed to delete contacts');
29    }
30}
31
32// Usage - Delete single contact
33deleteContacts([5678]);
34
35// Usage - Bulk delete
36deleteContacts([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.

POST: /api/v1/contact/import
ℹ️ CSV Format: You can download a sample CSV file from
GET: /api/v1/contact/sample/download
to see the expected format.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: multipart/form-data
4Accept: application/json

Request Parameters (multipart/form-data)

ParameterTypeRequiredDescription
chunks[]file[]YesArray of CSV file chunks (for large file uploads)
file_names[]string[]YesArray of file names corresponding to each chunk (max 255 chars each)
total_recordsintegerYesTotal number of records in the CSV file
file_sizenumericYesTotal file size in bytes
list_idintegerNoID of existing list to import contacts into (creates new list if omitted)
first_name_columnstringYesCSV column name containing first names (max 255 chars)
last_name_columnstringNoCSV column name containing last names (max 255 chars)
email_address_columnstringNoCSV column name containing email addresses (max 255 chars)
company_columnstringNoCSV column name containing company names (max 255 chars)
phone_number_columnstringYesCSV column name containing phone numbers (max 255 chars)
additional_1_columnstringNoCSV column name for additional field 1 (max 255 chars)
additional_2_columnstringNoCSV column name for additional field 2 (max 255 chars)
additional_3_columnstringNoCSV column name for additional field 3 (max 255 chars)

List Creation Behavior

  • If list_id is provided and valid, contacts are added to that list
  • If list_id is 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

1curl -X POST "https://api.texttorrent.com/api/v1/contact/import"   -H "X-API-SID: SID...................................." 
2  -H "X-API-PUBLIC-KEY: PK....................................." 
3  -H "Accept: application/json" 
4  -F "chunks[]=@contacts.csv" 
5  -F "file_names[]=contacts.csv" 
6  -F "total_records=150" 
7  -F "file_size=25600" 
8  -F "list_id=45" 
9  -F "first_name_column=First Name" 
10  -F "last_name_column=Last Name" 
11  -F "email_address_column=Email" 
12  -F "company_column=Company" 
13  -F "phone_number_column=Phone"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contacts imported successfully",
5    "data": {
6        "imported_count": 150,
7        "list_id": 45,
8        "list_name": "Customers"
9    },
10    "errors": null
11}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "chunks": [
8            "The chunks field is required.",
9            "The chunks must be an array."
10        ],
11        "first_name_column": [
12            "The first name column field is required."
13        ],
14        "phone_number_column": [
15            "The phone number column field is required."
16        ],
17        "list_id": [
18            "The selected list id is invalid."
19        ]
20    }
21}

Download Sample CSV

Get a sample CSV file to understand the expected format:

1curl -X GET "https://api.texttorrent.com/api/v1/contact/sample/download" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Sample Download Response

1{
2    "code": 200,
3    "success": true,
4    "message": "Sample file download",
5    "data": {
6        "url": "https://api.texttorrent.com/files/Sample Contact File.csv"
7    },
8    "errors": null
9}

Example in JavaScript with File Upload

1async function importContacts(file, columnMapping, listId = null) {
2    const formData = new FormData();
3
4    // Add file chunks (simplified - single file in this example)
5    formData.append('chunks[]', file);
6    formData.append('file_names[]', file.name);
7    formData.append('total_records', 100); // Calculate from CSV
8    formData.append('file_size', file.size);
9
10    // Add list ID if provided
11    if (listId) {
12        formData.append('list_id', listId);
13    }
14
15    // Add column mappings
16    formData.append('first_name_column', columnMapping.firstName);
17    formData.append('phone_number_column', columnMapping.phoneNumber);
18
19    // Optional columns
20    if (columnMapping.lastName) {
21        formData.append('last_name_column', columnMapping.lastName);
22    }
23    if (columnMapping.email) {
24        formData.append('email_address_column', columnMapping.email);
25    }
26    if (columnMapping.company) {
27        formData.append('company_column', columnMapping.company);
28    }
29
30    const response = await fetch('https://api.texttorrent.com/api/v1/contact/import', {
31        method: 'POST',
32        headers: {
33            'X-API-SID': 'SID....................................',
34            'X-API-PUBLIC-KEY': 'PK.....................................',
35            'Accept': 'application/json'
36            // Note: Don't set Content-Type for FormData, browser sets it automatically
37        },
38        body: formData
39    });
40
41    const data = await response.json();
42
43    if (data.success) {
44        console.log('Import successful:', data.data);
45        alert('Successfully imported "$"{data.data.imported_count} contacts');
46        return data.data;
47    } else {
48        console.error('Import failed:', data.message);
49        throw new Error(data.message);
50    }
51}
52
53// Usage with file input
54document.getElementById('csvFile').addEventListener('change', (e) => {
55    const file = e.target.files[0];
56    importContacts(file, {
57        firstName: 'First Name',
58        lastName: 'Last Name',
59        phoneNumber: 'Phone',
60        email: 'Email',
61        company: 'Company'
62    }, 45);
63});

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_column and phone_number_column are required
  • If no list_id provided, 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-data content 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

1// Complete contact management workflow
2class ContactManager {
3    constructor(apiSid, apiKey) {
4        this.apiSid = apiSid;
5        this.apiKey = apiKey;
6        this.baseUrl = 'https://api.texttorrent.com/api/v1';
7    }
8
9    async addContact(contact) {
10        // Validate before adding
11        if (!this.validatePhone(contact.number)) {
12            throw new Error('Invalid phone number format');
13        }
14
15        // Check list size
16        const listSize = await this.getListSize(contact.list_id);
17        if (listSize >= 10000) {
18            throw new Error('List is full (10,000 contacts max)');
19        }
20
21        // Add contact
22        return await this.apiCall('POST', '/contact/add', contact);
23    }
24
25    async updateContact(id, updates) {
26        return await this.apiCall('PUT', '/contact/"$"{id}', updates);
27    }
28
29    async deleteContacts(contactIds) {
30        if (!confirm('Delete "$"{contactIds.length} contact(s)?')) {
31            return;
32        }
33        return await this.apiCall('DELETE', '/contact/', { contact_ids: contactIds });
34    }
35
36    async importFromCSV(file, mapping, listId) {
37        const formData = new FormData();
38        formData.append('chunks[]', file);
39        formData.append('file_names[]', file.name);
40        formData.append('total_records', await this.countCSVRows(file));
41        formData.append('file_size', file.size);
42
43        if (listId) formData.append('list_id', listId);
44
45        Object.entries(mapping).forEach(([key, value]) => {
46            if (value) formData.append(key, value);
47        });
48
49        return await this.apiCall('POST', '/contact/import', formData, true);
50    }
51
52    validatePhone(number) {
53        return /^[0-9]{10}$/.test(number);
54    }
55
56    async getListSize(listId) {
57        const result = await this.apiCall('GET', '/contact/"$"{listId}/contacts');
58        return result.data.total || 0;
59    }
60
61    async countCSVRows(file) {
62        const text = await file.text();
63        return text.split('
64').length - 1; // Exclude header
65    }
66
67    async apiCall(method, endpoint, data = null, isFormData = false) {
68        const headers = {
69            'X-API-SID': this.apiSid,
70            'X-API-PUBLIC-KEY': this.apiKey,
71            'Accept': 'application/json'
72        };
73
74        if (!isFormData) {
75            headers['Content-Type'] = 'application/json';
76        }
77
78        const options = {
79            method,
80            headers,
81            body: data ? (isFormData ? data : JSON.stringify(data)) : null
82        };
83
84        const response = await fetch(this.baseUrl + endpoint, options);
85        return await response.json();
86    }
87}
88
89// Usage
90const manager = new ContactManager('SID....................................', 'PK.....................................');
91
92// Add contact
93await manager.addContact({
94    first_name: 'John',
95    last_name: 'Doe',
96    number: '2025551234',
97    email: 'john@example.com',
98    list_id: 45
99});
100
101// Update contact
102await manager.updateContact(5678, {
103    first_name: 'John',
104    mobile_number: '2025559999',
105    email: 'john.new@example.com'
106});
107
108// Delete contacts
109await manager.deleteContacts([5678, 5679]);
110
111// Import from CSV
112const fileInput = document.getElementById('csvFile');
113await manager.importFromCSV(fileInput.files[0], {
114    first_name_column: 'First Name',
115    phone_number_column: 'Phone',
116    email_address_column: 'Email'
117}, 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
ℹ️ How It Works: First validate numbers using the endpoint in section 3.2.2. Once validation is complete, use these endpoints to view results, cleanup invalid numbers, and export data.

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.

GET: /api/v1/contact/validator/

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
searchstringNoSearch term to filter by list name (partial match)
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)

Example Request - Get All Validations

1curl -X GET "https://api.texttorrent.com/api/v1/contact/validator/" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - With Pagination and Search

1curl -X GET "https://api.texttorrent.com/api/v1/contact/validator/?search=VIP&limit=20&page=1" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Validator Credits",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "id": 123,
10                "list_name": "VIP Clients",
11                "total_credits": 150,
12                "total_number": 150,
13                "total_mobile_numbers": 120,
14                "total_landline_numbers": 25,
15                "total_other_numbers": 5,
16                "invalid_numbers": 0,
17                "created_at": "2025-10-15T14:30:00.000000Z",
18                "status": "Completed",
19                "cleaned": 0
20            },
21            {
22                "id": 122,
23                "list_name": "Hot Leads",
24                "total_credits": 200,
25                "total_number": 200,
26                "total_mobile_numbers": 185,
27                "total_landline_numbers": 10,
28                "total_other_numbers": 3,
29                "invalid_numbers": 2,
30                "created_at": "2025-10-14T10:15:00.000000Z",
31                "status": "Completed",
32                "cleaned": 1
33            }
34        ],
35        "first_page_url": "https://api.texttorrent.com/api/v1/contact/validator?page=1",
36        "from": 1,
37        "last_page": 5,
38        "last_page_url": "https://api.texttorrent.com/api/v1/contact/validator?page=5",
39        "next_page_url": "https://api.texttorrent.com/api/v1/contact/validator?page=2",
40        "path": "https://api.texttorrent.com/api/v1/contact/validator",
41        "per_page": 10,
42        "prev_page_url": null,
43        "to": 10,
44        "total": 48
45    },
46    "errors": null
47}

Response Fields

FieldTypeDescription
idintegerUnique identifier for the validation record
list_namestringName of the contact list that was validated
total_creditsintegerNumber of credits used for this validation
total_numberintegerTotal number of phone numbers validated
total_mobile_numbersintegerCount of valid mobile phone numbers
total_landline_numbersintegerCount of landline phone numbers
total_other_numbersintegerCount of other number types (VoIP, toll-free, etc.)
invalid_numbersintegerCount of invalid or unverifiable numbers
statusstringValidation status (e.g., "Completed", "Processing")
cleanedinteger1 if cleanup processed (non-mobile numbers removed), 0 otherwise
created_atstringTimestamp 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

1async function getValidationHistory(page = 1, limit = 10, search = '') {
2    const params = new URLSearchParams({
3        page: page,
4        limit: limit
5    });
6
7    if (search) {
8        params.append('search', search);
9    }
10
11    const response = await fetch('https://api.texttorrent.com/api/v1/contact/validator/?"$"{params}', {
12        method: 'GET',
13        headers: {
14            'X-API-SID': 'SID....................................',
15            'X-API-PUBLIC-KEY': 'PK.....................................',
16            'Accept': 'application/json'
17        }
18    });
19
20    const data = await response.json();
21
22    if (data.success) {
23        console.log('Validation history:', data.data.data);
24        console.log('Page "$"{data.data.current_page} of "$"{data.data.last_page}');
25        console.log('Total validations: "$"{data.data.total}');
26        return data.data;
27    } else {
28        console.error('Error:', data.message);
29        throw new Error(data.message);
30    }
31}
32
33// Usage - Get first page
34getValidationHistory();
35
36// Usage - Search with pagination
37getValidationHistory(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 cleaned field 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.

POST: /api/v1/contact/validator/process-cleanup/{id}
⚠️ Warning: Cleanup permanently deletes contacts with non-mobile numbers (landline, VoIP, invalid, etc.) from your list. This action cannot be undone. Only mobile numbers will remain.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

URL Parameters

ParameterTypeRequiredDescription
idintegerYesID 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

1curl -X POST "https://api.texttorrent.com/api/v1/contact/validator/process-cleanup/123" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Cleanup Successful",
5    "data": null,
6    "errors": null
7}

Example in JavaScript

1async function processCleanup(validationId) {
2    // Confirm cleanup action
3    const confirmed = confirm(
4        'This will permanently delete all non-mobile numbers from the list. ' +
5        'Only mobile numbers will remain. Continue?'
6    );
7
8    if (!confirmed) {
9        return;
10    }
11
12    const response = await fetch(
13        'https://api.texttorrent.com/api/v1/contact/validator/process-cleanup/"$"{validationId}',
14        {
15            method: 'POST',
16            headers: {
17                'X-API-SID': 'SID....................................',
18                'X-API-PUBLIC-KEY': 'PK.....................................',
19                'Accept': 'application/json'
20            }
21        }
22    );
23
24    const data = await response.json();
25
26    if (data.success) {
27        console.log('Cleanup completed successfully');
28        alert('Non-mobile numbers have been removed from the list');
29        // Refresh validation history
30        await getValidationHistory();
31    } else {
32        console.error('Cleanup failed:', data.message);
33        alert('Failed to process cleanup');
34    }
35}
36
37// Usage
38processCleanup(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.

POST: /api/v1/contact/validator/export

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
validation_idsarrayYesArray of validation IDs to export (all must exist in number_validations table)

Example Request - Export Single Validation

1curl -X POST "https://api.texttorrent.com/api/v1/contact/validator/export" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "validation_ids": [123]
8  }'

Example Request - Export Multiple Validations

1curl -X POST "https://api.texttorrent.com/api/v1/contact/validator/export" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "validation_ids": [123, 124, 125]
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Exported successfully",
5    "data": {
6        "url": "https://your-storage.digitaloceanspaces.com/validation/validation_export_1729180845.xlsx"
7    },
8    "errors": null
9}

Response Fields

FieldTypeDescription
urlstringDirect download URL for the exported Excel file

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "validation_ids": [
8            "The validation ids field is required.",
9            "The validation ids must be an array."
10        ],
11        "validation_ids.0": [
12            "The selected validation ids.0 is invalid."
13        ]
14    }
15}

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

1async function exportValidations(validationIds) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/contact/validator/export', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Content-Type': 'application/json',
8            'Accept': 'application/json'
9        },
10        body: JSON.stringify({
11            validation_ids: validationIds
12        })
13    });
14
15    const data = await response.json();
16
17    if (data.success) {
18        console.log('Export successful, downloading...');
19        // Trigger download
20        window.open(data.data.url, '_blank');
21        return data.data.url;
22    } else {
23        console.error('Export failed:', data.message);
24        throw new Error(data.message);
25    }
26}
27
28// Usage - Export single validation
29exportValidations([123]);
30
31// Usage - Export multiple validations
32exportValidations([123, 124, 125]);

Example in PHP

1function exportValidations($validationIds) {
2    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/validator/export');
3
4    curl_setopt($ch, CURLOPT_POST, true);
5    curl_setopt($ch, CURLOPT_HTTPHEADER, [
6        'X-API-SID: SID....................................',
7        'X-API-PUBLIC-KEY: PK.....................................',
8        'Content-Type: application/json',
9        'Accept: application/json'
10    ]);
11    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
12        'validation_ids' => $validationIds
13    ]));
14    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
15
16    $response = curl_exec($ch);
17    curl_close($ch);
18
19    $data = json_decode($response, true);
20
21    if ($data['success']) {
22        echo 'Download URL: ' . $data['data']['url'];
23        return $data['data']['url'];
24    } else {
25        echo 'Error: ' . $data['message'];
26        return null;
27    }
28}
29
30// Usage
31exportValidations([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 here .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).

DELETE: /api/v1/contact/validator/{id}
⚠️ Note: This deletes the validation record and detailed validation results, but does NOT delete contacts from the list. Use cleanup endpoint before deleting if you want to remove non-mobile numbers.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

URL Parameters

ParameterTypeRequiredDescription
idintegerYesID of the validation record to delete

What Gets Deleted

  • Validation record from number_validations table
  • All associated validation items from number_validation_items table
  • Detailed results for each validated number

What Does NOT Get Deleted

  • Contacts in the list (they remain unchanged)
  • The contact list itself

Example Request

1curl -X DELETE "https://api.texttorrent.com/api/v1/contact/validator/123" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Validation deleted successfully",
5    "data": null,
6    "errors": null
7}

Example in JavaScript

1async function deleteValidation(validationId) {
2    // Confirm deletion
3    if (!confirm('Delete this validation record? This cannot be undone.')) {
4        return;
5    }
6
7    const response = await fetch(
8        'https://api.texttorrent.com/api/v1/contact/validator/"$"{validationId}',
9        {
10            method: 'DELETE',
11            headers: {
12                'X-API-SID': 'SID....................................',
13                'X-API-PUBLIC-KEY': 'PK.....................................',
14                'Accept': 'application/json'
15            }
16        }
17    );
18
19    const data = await response.json();
20
21    if (data.success) {
22        console.log('Validation record deleted');
23        alert('Validation record deleted successfully');
24        // Refresh validation history
25        await getValidationHistory();
26    } else {
27        console.error('Delete failed:', data.message);
28        alert('Failed to delete validation record');
29    }
30}
31
32// Usage
33deleteValidation(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

  1. Review validation results using GET endpoint
  2. Export data if needed for records using export endpoint
  3. Process cleanup to remove non-mobile numbers (optional)
  4. Delete validation record to clean up history

Contact Validator Best Practices

Validation Workflow

  1. Pre-validate: Use verify endpoint (3.2.1) to check credits needed
  2. Validate: Use validate endpoint (3.2.2) to process validation
  3. Review results: Use validator GET endpoint (3.6.1) to see statistics
  4. Export data: Export results to Excel for analysis (3.6.3)
  5. Cleanup: Remove non-mobile numbers if needed (3.6.2)
  6. 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

1// Complete validation management workflow
2class ValidationManager {
3    constructor(apiSid, apiKey) {
4        this.apiSid = apiSid;
5        this.apiKey = apiKey;
6        this.baseUrl = 'https://api.texttorrent.com/api/v1';
7    }
8
9    async getHistory(page = 1, search = '') {
10        const params = new URLSearchParams({ page, limit: 20 });
11        if (search) params.append('search', search);
12
13        return await this.apiCall('GET', '/contact/validator/?"$"{params}');
14    }
15
16    async processCleanup(validationId) {
17        const confirmed = confirm(
18            'Remove all non-mobile numbers? This cannot be undone.'
19        );
20        if (!confirmed) return;
21
22        return await this.apiCall('POST', '/contact/validator/process-cleanup/"$"{validationId}');
23    }
24
25    async exportResults(validationIds) {
26        const result = await this.apiCall('POST', '/contact/validator/export', {
27            validation_ids: validationIds
28        });
29
30        if (result.success) {
31            // Auto-download
32            window.open(result.data.url, '_blank');
33        }
34
35        return result;
36    }
37
38    async deleteRecord(validationId) {
39        const confirmed = confirm('Delete validation record?');
40        if (!confirmed) return;
41
42        return await this.apiCall('DELETE', '/contact/validator/"$"{validationId}');
43    }
44
45    async completeWorkflow(validationId) {
46        try {
47            // 1. Get validation details
48            const history = await this.getHistory();
49            const validation = history.data.data.find(v => v.id === validationId);
50
51            console.log('Validation stats:', {
52                total: validation.total_number,
53                mobile: validation.total_mobile_numbers,
54                landline: validation.total_landline_numbers,
55                invalid: validation.invalid_numbers
56            });
57
58            // 2. Export results
59            console.log('Exporting results...');
60            await this.exportResults([validationId]);
61
62            // 3. Cleanup if needed
63            if (validation.total_landline_numbers > 0) {
64                console.log('Processing cleanup...');
65                await this.processCleanup(validationId);
66            }
67
68            // 4. Delete old record
69            console.log('Cleaning up history...');
70            await this.deleteRecord(validationId);
71
72            console.log('Workflow completed successfully');
73        } catch (error) {
74            console.error('Workflow failed:', error);
75        }
76    }
77
78    async apiCall(method, endpoint, data = null) {
79        const headers = {
80            'X-API-SID': this.apiSid,
81            'X-API-PUBLIC-KEY': this.apiKey,
82            'Accept': 'application/json'
83        };
84
85        if (data) {
86            headers['Content-Type'] = 'application/json';
87        }
88
89        const response = await fetch(this.baseUrl + endpoint, {
90            method,
91            headers,
92            body: data ? JSON.stringify(data) : null
93        });
94
95        return await response.json();
96    }
97}
98
99// Usage
100const validator = new ValidationManager('SID....................................', 'PK.....................................');
101
102// Get validation history
103await validator.getHistory(1, 'VIP');
104
105// Process cleanup
106await validator.processCleanup(123);
107
108// Export results
109await validator.exportResults([123, 124]);
110
111// Delete record
112await validator.deleteRecord(123);
113
114// Complete workflow
115await 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
ℹ️ How It Works: When you import contacts using the CSV import endpoint (section 3.5.4), a record is created in the import history. Use these endpoints to track import progress and download reports of any numbers that were skipped during import.

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.

GET: /api/v1/contact/import

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)

Example Request - Get All Imports

1curl -X GET "https://api.texttorrent.com/api/v1/contact/import/" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - With Pagination

1curl -X GET "https://api.texttorrent.com/api/v1/contact/import/?limit=20&page=1" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact imports fetched successfully",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "id": 45,
10                "name": "customers.csv",
11                "file": "imports/customers_1729180845.csv",
12                "status": 2,
13                "status_text": "Completed",
14                "remarks": null,
15                "list_name": "Customers",
16                "total_records": 500,
17                "skipped": 12,
18                "skipped_file_url": "https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx",
19                "stored": 488,
20                "created_at": "2025-10-15T14:30:00.000000Z"
21            },
22            {
23                "id": 44,
24                "name": "leads.csv",
25                "file": "imports/leads_1729094400.csv",
26                "status": 2,
27                "status_text": "Completed",
28                "remarks": null,
29                "list_name": "Hot Leads",
30                "total_records": 250,
31                "skipped": 0,
32                "skipped_file_url": null,
33                "stored": 250,
34                "created_at": "2025-10-14T10:15:00.000000Z"
35            },
36            {
37                "id": 43,
38                "name": "prospects.csv",
39                "file": "imports/prospects_1729008000.csv",
40                "status": 3,
41                "status_text": "Failed",
42                "remarks": "Invalid CSV format",
43                "list_name": null,
44                "total_records": 0,
45                "skipped": 0,
46                "skipped_file_url": null,
47                "stored": 0,
48                "created_at": "2025-10-13T08:00:00.000000Z"
49            },
50            {
51                "id": 42,
52                "name": "contacts.csv",
53                "file": "imports/contacts_1728921600.csv",
54                "status": 1,
55                "status_text": "Processing",
56                "remarks": null,
57                "list_name": "Contacts",
58                "total_records": 1000,
59                "skipped": 0,
60                "skipped_file_url": null,
61                "stored": 450,
62                "created_at": "2025-10-12T06:00:00.000000Z"
63            }
64        ],
65        "first_page_url": "https://api.texttorrent.com/api/v1/contact/import?page=1",
66        "from": 1,
67        "last_page": 3,
68        "last_page_url": "https://api.texttorrent.com/api/v1/contact/import?page=3",
69        "next_page_url": "https://api.texttorrent.com/api/v1/contact/import?page=2",
70        "path": "https://api.texttorrent.com/api/v1/contact/import",
71        "per_page": 10,
72        "prev_page_url": null,
73        "to": 10,
74        "total": 28
75    },
76    "errors": null
77}

Response Fields

FieldTypeDescription
idintegerUnique identifier for the import record
namestringOriginal filename of the uploaded CSV
filestringStored file path in the system
statusintegerStatus code: 0=Pending, 1=Processing, 2=Completed, 3=Failed
status_textstringHuman-readable status: "Pending", "Processing", "Completed", "Failed"
remarksstring|nullError message or notes (populated for failed imports)
list_namestring|nullName of the contact list where contacts were imported
total_recordsintegerTotal number of records in the CSV file
skippedintegerNumber of records that were skipped during import
skipped_file_urlstring|nullURL to download Excel file containing skipped records (null if no skips)
storedintegerNumber of contacts successfully imported and stored
created_atstringTimestamp when import was initiated (ISO 8601 format)

Import Status Values

Status CodeStatus TextDescription
0PendingImport queued but not yet started
1ProcessingImport currently in progress
2CompletedImport finished successfully
3FailedImport 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

1async function getImportHistory(page = 1, limit = 10) {
2    const params = new URLSearchParams({
3        page: page,
4        limit: limit
5    });
6
7    const response = await fetch('https://api.texttorrent.com/api/v1/contact/import/?"$"{params}', {
8        method: 'GET',
9        headers: {
10            'X-API-SID': 'SID....................................',
11            'X-API-PUBLIC-KEY': 'PK.....................................',
12            'Accept': 'application/json'
13        }
14    });
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Import history:', data.data.data);
20
21        // Display summary
22        data.data.data.forEach(importRecord => {
23            console.log('Import "$"{importRecord.id}: "$"{importRecord.name}');
24            console.log('Status: "$"{importRecord.status_text}');
25            console.log('  Total: "$"{importRecord.total_records}, Stored: "$"{importRecord.stored}, Skipped: "$"{importRecord.skipped}');
26
27            if (importRecord.skipped > 0) {
28                console.log('Skipped file: "$"{importRecord.skipped_file_url}');
29            }
30
31            if (importRecord.status === 3) {
32                console.log('  Error: "$"{importRecord.remarks}');
33            }
34        });
35
36        console.log('Page "$"{data.data.current_page} of "$"{data.data.last_page}');
37        console.log('Total imports: "$"{data.data.total}');
38
39        return data.data;
40    } else {
41        console.error('Error:', data.message);
42        throw new Error(data.message);
43    }
44}
45
46// Usage - Get first page
47getImportHistory();
48
49// Usage - Get with pagination
50getImportHistory(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 remarks field

Monitoring Import Progress

1// Poll import status until completion
2async function monitorImport(importId) {
3    let status = 1; // Processing
4
5    while (status === 0 || status === 1) {
6        const history = await getImportHistory();
7        const importRecord = history.data.find(imp => imp.id === importId);
8
9        if (!importRecord) {
10            throw new Error('Import record not found');
11        }
12
13        status = importRecord.status;
14
15        console.log('Import "$"{importId}: "$"{importRecord.status_text} - "$"{importRecord.stored}/"$"{importRecord.total_records} records');
16
17        if (status === 2) {
18            console.log('Import completed successfully');
19            if (importRecord.skipped > 0) {
20                console.log('"$"{importRecord.skipped} records were skipped');
21                console.log('Download skipped records: "$"{importRecord.skipped_file_url}');
22            }
23            return importRecord;
24        } else if (status === 3) {
25            console.error('Import failed:', importRecord.remarks);
26            throw new Error(importRecord.remarks);
27        }
28
29        // Wait 5 seconds before checking again
30        await new Promise(resolve => setTimeout(resolve, 5000));
31    }
32}
33
34// Usage
35monitorImport(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.

POST: /api/v1/contact/import/pdf/download
ℹ️ Note: The skipped file URL is provided in the import history response (from section 3.7.1). Pass that URL to this endpoint to generate and download a PDF report.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/pdf

Request Body Parameters

ParameterTypeRequiredDescription
urlstringYesURL of the skipped file (obtained from import history skipped_file_url)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/contact/import/pdf/download" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/pdf" 
6  -d '{
7    "url": "https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx"
8  }'   --output skipped_numbers.pdf

Success Response (200 OK)

Returns a PDF file for download. Content-Type: application/pdf

1HTTP/1.1 200 OK
2Content-Type: application/pdf
3Content-Disposition: attachment; filename="skipped_numbers.pdf"
4Content-Length: 45678

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "url": [
8            "The url field is required.",
9            "The url must be a valid URL."
10        ]
11    }
12}

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

1async function downloadSkippedPDF(skippedFileUrl) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/contact/import/pdf/download', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Content-Type': 'application/json',
8            'Accept': 'application/pdf'
9        },
10        body: JSON.stringify({
11            url: skippedFileUrl
12        })
13    });
14
15    if (response.ok) {
16        // Convert response to blob
17        const blob = await response.blob();
18
19        // Create download link
20        const downloadUrl = window.URL.createObjectURL(blob);
21        const link = document.createElement('a');
22        link.href = downloadUrl;
23        link.download = 'skipped_numbers_"$"{Date.now()}.pdf';
24        document.body.appendChild(link);
25        link.click();
26        document.body.removeChild(link);
27
28        // Clean up
29        window.URL.revokeObjectURL(downloadUrl);
30
31        console.log('PDF downloaded successfully');
32    } else {
33        const error = await response.json();
34        console.error('Download failed:', error.message);
35        throw new Error(error.message);
36    }
37}
38
39// Usage
40downloadSkippedPDF('https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx');

Example in PHP

1function downloadSkippedPDF($skippedFileUrl, $outputPath) {
2    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/import/pdf/download');
3
4    curl_setopt($ch, CURLOPT_POST, true);
5    curl_setopt($ch, CURLOPT_HTTPHEADER, [
6        'X-API-SID: SID....................................',
7        'X-API-PUBLIC-KEY: PK.....................................',
8        'Content-Type: application/json',
9        'Accept: application/pdf'
10    ]);
11    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
12        'url' => $skippedFileUrl
13    ]));
14    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
15
16    $pdfContent = curl_exec($ch);
17    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
18    curl_close($ch);
19
20    if ($httpCode === 200) {
21        file_put_contents($outputPath, $pdfContent);
22        echo "PDF saved to: $outputPath";
23        return true;
24    } else {
25        $error = json_decode($pdfContent, true);
26        echo "Error: " . ($error['message'] ?? 'Unknown error');
27        return false;
28    }
29}
30
31// Usage
32downloadSkippedPDF(
33    'https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx',
34    'skipped_numbers.pdf'
35);

Workflow Example

1// Complete workflow: Get import history and download skipped reports
2async function reviewImportResults(importId) {
3    // 1. Get import history
4    const history = await getImportHistory();
5    const importRecord = history.data.find(imp => imp.id === importId);
6
7    if (!importRecord) {
8        throw new Error('Import not found');
9    }
10
11    console.log('Import: "$"{importRecord.name}');
12    console.log('Status: "$"{importRecord.status_text}');
13    console.log('Total: "$"{importRecord.total_records}');
14    console.log('Stored: "$"{importRecord.stored}');
15    console.log('Skipped: "$"{importRecord.skipped}');
16
17    // 2. Download skipped numbers PDF if any were skipped
18    if (importRecord.skipped > 0 && importRecord.skipped_file_url) {
19        console.log('Downloading skipped numbers report...');
20        await downloadSkippedPDF(importRecord.skipped_file_url);
21        console.log('Review the PDF to see why records were skipped');
22    } else {
23        console.log('No records were skipped - import was 100% successful!');
24    }
25
26    // 3. Handle failed imports
27    if (importRecord.status === 3) {
28        console.error('Import failed:', importRecord.remarks);
29        console.log('Please fix the issues and try importing again');
30    }
31}
32
33// Usage
34reviewImportResults(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/pdf header to receive PDF
  • Use --output or 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 ReasonFix
Duplicate NumberRemove duplicates from CSV or choose different list
Invalid Phone FormatEnsure numbers are 10 digits without +1 prefix
Missing First NameAdd first names or use placeholder like "Contact"
List Full (10,000 limit)Create new list or cleanup existing list
Invalid Email FormatFix email syntax or leave blank

Complete Import Management Workflow

1// Complete import management system
2class ImportManager {
3    constructor(apiSid, apiKey) {
4        this.apiSid = apiSid;
5        this.apiKey = apiKey;
6        this.baseUrl = 'https://api.texttorrent.com/api/v1';
7    }
8
9    async getHistory(page = 1, limit = 10) {
10        const params = new URLSearchParams({ page, limit });
11        return await this.apiCall('GET', '/contact/import/?"$"{params}');
12    }
13
14    async monitorImport(importId, onProgress) {
15        let status = 1; // Processing
16        let lastStored = 0;
17
18        while (status === 0 || status === 1) {
19            const history = await this.getHistory();
20            const imp = history.data.data.find(i => i.id === importId);
21
22            if (!imp) throw new Error('Import not found');
23
24            status = imp.status;
25
26            // Report progress if changed
27            if (imp.stored !== lastStored) {
28                lastStored = imp.stored;
29                const progress = (imp.stored / imp.total_records * 100).toFixed(1);
30                onProgress?.({
31                    status: imp.status_text,
32                    progress: progress,
33                    stored: imp.stored,
34                    total: imp.total_records,
35                    skipped: imp.skipped
36                });
37            }
38
39            if (status === 2) {
40                // Completed
41                return {
42                    success: true,
43                    import: imp,
44                    message: 'Import completed: "$"{imp.stored} contacts imported, "$"{imp.skipped} skipped'
45                };
46            } else if (status === 3) {
47                // Failed
48                return {
49                    success: false,
50                    import: imp,
51                    message: 'Import failed: "$"{imp.remarks}'
52                };
53            }
54
55            // Wait 5 seconds before checking again
56            await new Promise(resolve => setTimeout(resolve, 5000));
57        }
58    }
59
60    async downloadSkippedPDF(skippedUrl) {
61        const response = await fetch('"$"{this.baseUrl}/contact/import/pdf/download', {
62            method: 'POST',
63            headers: {
64                'X-API-SID': this.apiSid,
65                'X-API-PUBLIC-KEY': this.apiKey,
66                'Content-Type': 'application/json',
67                'Accept': 'application/pdf'
68            },
69            body: JSON.stringify({ url: skippedUrl })
70        });
71
72        if (response.ok) {
73            const blob = await response.blob();
74            const url = window.URL.createObjectURL(blob);
75            const link = document.createElement('a');
76            link.href = url;
77            link.download = 'skipped_"$"{Date.now()}.pdf';
78            link.click();
79            window.URL.revokeObjectURL(url);
80            return true;
81        }
82
83        throw new Error('Failed to download PDF');
84    }
85
86    async handleImportComplete(importId) {
87        const history = await this.getHistory();
88        const imp = history.data.find(i => i.id === importId);
89
90        if (!imp) throw new Error('Import not found');
91
92        console.log('Import completed: "$"{imp.status_text}');
93        console.log('Stored: "$"{imp.stored}, Skipped: "$"{imp.skipped}');
94
95        // Download skipped report if any
96        if (imp.skipped > 0 && imp.skipped_file_url) {
97            console.log('Downloading skipped records report...');
98            await this.downloadSkippedPDF(imp.skipped_file_url);
99            alert('"$"{imp.skipped} records were skipped. Check the downloaded PDF for details.');
100        } else {
101            alert('Import completed successfully with no skipped records!');
102        }
103
104        return imp;
105    }
106
107    async apiCall(method, endpoint, data = null) {
108        const headers = {
109            'X-API-SID': this.apiSid,
110            'X-API-PUBLIC-KEY': this.apiKey,
111            'Accept': 'application/json'
112        };
113
114        if (data) headers['Content-Type'] = 'application/json';
115
116        const response = await fetch(this.baseUrl + endpoint, {
117            method,
118            headers,
119            body: data ? JSON.stringify(data) : null
120        });
121
122        return await response.json();
123    }
124}
125
126// Usage
127const importMgr = new ImportManager('SID....................................', 'PK.....................................');
128
129// Monitor import with progress updates
130await importMgr.monitorImport(45, (progress) => {
131    console.log('"$"{progress.status}: "$"{progress.progress}% ("$"{progress.stored}/"$"{progress.total})');
132    // Update UI progress bar
133    updateProgressBar(progress.progress);
134});
135
136// Handle completion
137await 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 status field before assuming success
  • Display remarks field 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
⚠️ Important: Blocked contacts will NOT receive any messages from your account. This includes SMS campaigns, automated messages, and inbox replies. Use this feature to honor opt-out requests and maintain compliance.
ℹ️ Auto vs Manual Blocking: Contacts can be blocked automatically (when they reply with opt-out words) or manually (when you add them to the blocked list). Use the added_byfilter 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).

GET: /api/v1/contact/blocked-list/

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)
searchstringNoSearch term to filter by phone number (partial match)
added_bystringNoFilter by blocking method: auto (opt-out words) or manual(manually added)

Example Request - Get All Blocked Contacts

1curl -X GET "https://api.texttorrent.com/api/v1/contact/blocked-list/" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - Search and Filter

1curl -X GET "https://api.texttorrent.com/api/v1/contact/blocked-list/?search=555&added_by=manual&limit=20&page=1" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Blocked list fetched successfully",
5    "data": {
6        "blocked_list": {
7            "current_page": 1,
8            "data": [
9                {
10                    "id": 1234,
11                    "first_name": "John",
12                    "last_name": "Doe",
13                    "number": "+12025551234",
14                    "email": "john.doe@example.com",
15                    "company": null,
16                    "user_id": 1,
17                    "list_id": null,
18                    "folder_id": null,
19                    "blacklisted": 1,
20                    "blacklisted_by": 1,
21                    "blacklisted_at": "2025-10-15T14:30:00.000000Z",
22                    "valid": 0,
23                    "validation_process": 0,
24                    "created_at": "2025-10-15T14:30:00.000000Z",
25                    "updated_at": "2025-10-15T14:30:00.000000Z"
26                },
27                {
28                    "id": 1235,
29                    "first_name": "Jane",
30                    "last_name": "Smith",
31                    "number": "+12025555678",
32                    "email": null,
33                    "company": null,
34                    "user_id": 1,
35                    "list_id": null,
36                    "folder_id": null,
37                    "blacklisted": 1,
38                    "blacklisted_by": null,
39                    "blacklisted_at": "2025-10-14T10:15:00.000000Z",
40                    "valid": 0,
41                    "validation_process": 0,
42                    "created_at": "2025-10-14T10:15:00.000000Z",
43                    "updated_at": "2025-10-14T10:15:00.000000Z"
44                }
45            ],
46            "first_page_url": "https://api.texttorrent.com/api/v1/contact/blocked-list?page=1",
47            "from": 1,
48            "last_page": 5,
49            "last_page_url": "https://api.texttorrent.com/api/v1/contact/blocked-list?page=5",
50            "next_page_url": "https://api.texttorrent.com/api/v1/contact/blocked-list?page=2",
51            "path": "https://api.texttorrent.com/api/v1/contact/blocked-list",
52            "per_page": 10,
53            "prev_page_url": null,
54            "to": 10,
55            "total": 48
56        },
57        "total": 48,
58        "total_otp_out": 12
59    },
60    "errors": null
61}

Response Fields

FieldTypeDescription
blocked_listobjectPaginated list of blocked contacts
totalintegerTotal number of blocked contacts
total_otp_outintegerTotal number of opt-out words configured

Blocked Contact Fields

FieldTypeDescription
idintegerUnique identifier for the contact
numberstringBlocked phone number (with +1 prefix)
blacklistedintegerAlways 1 for blocked contacts
blacklisted_byinteger|nullUser ID who manually blocked (null if auto-blocked via opt-out words)
blacklisted_atstringTimestamp when contact was blocked (ISO 8601 format)
list_idinteger|nullAlways 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

1async function getBlockedList(options = {}) {
2    const params = new URLSearchParams({
3        page: options.page || 1,
4        limit: options.limit || 10
5    });
6
7    if (options.search) {
8        params.append('search', options.search);
9    }
10
11    if (options.addedBy) {
12        params.append('added_by', options.addedBy);
13    }
14
15    const response = await fetch(
16        'https://api.texttorrent.com/api/v1/contact/blocked-list/?"$"{params}',
17        {
18            method: 'GET',
19            headers: {
20                'X-API-SID': 'SID....................................',
21                'X-API-PUBLIC-KEY': 'PK.....................................',
22                'Accept': 'application/json'
23            }
24        }
25    );
26
27    const data = await response.json();
28
29    if (data.success) {
30        console.log('Blocked contacts:', data.data.blocked_list.data);
31        console.log('Total blocked: "$"{data.data.total}');
32        console.log('Opt-out words configured: "$"{data.data.total_otp_out}');
33
34        // Categorize by blocking method
35        const manual = data.data.blocked_list.data.filter(c => c.blacklisted_by !== null);
36        const auto = data.data.blocked_list.data.filter(c => c.blacklisted_by === null);
37
38        console.log('Manually blocked: "$"{manual.length}, Auto-blocked: "$"{auto.length}');
39
40        return data.data;
41    } else {
42        console.error('Error:', data.message);
43        throw new Error(data.message);
44    }
45}
46
47// Usage - Get all blocked contacts
48getBlockedList();
49
50// Usage - Search for specific number
51getBlockedList({ search: '555' });
52
53// Usage - Get only manually blocked contacts
54getBlockedList({ addedBy: 'manual', limit: 20 });

Important Notes

  • Results are ordered by blacklisted_at in descending order (newest first)
  • Search filters by phone number (partial match)
  • Blocked contacts have list_id set to null (not in any list)
  • blacklisted_by null = auto-blocked via opt-out words
  • blacklisted_by not 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.

POST: /api/v1/contact/blocked-list/

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
numbersarrayYesArray of phone numbers to block (10 digits, without +1 prefix)

Example Request - Block Single Number

1curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "numbers": ["2025551234"]
8  }'

Example Request - Block Multiple Numbers

1curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "numbers": ["2025551234", "2025555678", "2025559999"]
8  }'

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Blocklist create",
5    "data": true,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "numbers": [
8            "The numbers field is required.",
9            "The numbers must be an array."
10        ],
11        "numbers.0": [
12            "The numbers.0 must be a string."
13        ]
14    }
15}

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_by is set to your user ID (indicates manual blocking)
  • blacklisted_at is set to current timestamp
  • Contact uses your name and email as default information
  • Blocked numbers will not receive any messages

Example in JavaScript

1async function addToBlockedList(phoneNumbers) {
2    // Validate phone numbers
3    const validNumbers = phoneNumbers.filter(num => /^d{10}$/.test(num));
4
5    if (validNumbers.length === 0) {
6        throw new Error('No valid 10-digit phone numbers provided');
7    }
8
9    if (validNumbers.length !== phoneNumbers.length) {
10        console.warn('Some numbers were invalid and skipped');
11    }
12
13    const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/', {
14        method: 'POST',
15        headers: {
16            'X-API-SID': 'SID....................................',
17            'X-API-PUBLIC-KEY': 'PK.....................................',
18            'Content-Type': 'application/json',
19            'Accept': 'application/json'
20        },
21        body: JSON.stringify({
22            numbers: validNumbers
23        })
24    });
25
26    const data = await response.json();
27
28    if (data.success) {
29        console.log('Successfully blocked "$"{validNumbers.length} number(s)');
30        alert('"$"{validNumbers.length} number(s) added to blocked list');
31        // Refresh blocked list
32        await getBlockedList();
33        return data;
34    } else {
35        console.error('Error:', data.message);
36        throw new Error(data.message);
37    }
38}
39
40// Usage - Block single number
41addToBlockedList(['2025551234']);
42
43// Usage - Block multiple numbers
44addToBlockedList(['2025551234', '2025555678', '2025559999']);

Example in PHP

1function addToBlockedList($phoneNumbers) {
2    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/blocked-list/');
3
4    curl_setopt($ch, CURLOPT_POST, true);
5    curl_setopt($ch, CURLOPT_HTTPHEADER, [
6        'X-API-SID: SID....................................',
7        'X-API-PUBLIC-KEY: PK.....................................',
8        'Content-Type: application/json',
9        'Accept: application/json'
10    ]);
11    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
12        'numbers' => $phoneNumbers
13    ]));
14    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
15
16    $response = curl_exec($ch);
17    curl_close($ch);
18
19    $data = json_decode($response, true);
20
21    if ($data['success']) {
22        echo 'Numbers blocked successfully';
23        return true;
24    } else {
25        echo 'Error: ' . $data['message'];
26        return false;
27    }
28}
29
30// Usage
31addToBlockedList(['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 blacklistedstatus to 0, allowing them to receive messages again. The contact record remains in the system.

POST: /api/v1/contact/blocked-list/remove

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idsarrayYesArray of contact IDs to unblock (must exist in contacts table)

Example Request - Unblock Single Contact

1curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/remove" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_ids": [1234]
8  }'

Example Request - Unblock Multiple Contacts

1curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/remove" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_ids": [1234, 1235, 1236]
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact(s) removed from blocked list successfully",
5    "data": null,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_ids": [
8            "The contact ids field is required.",
9            "The contact ids must be an array."
10        ],
11        "contact_ids.0": [
12            "The selected contact ids.0 is invalid."
13        ]
14    }
15}

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

  • blacklisted is set to 0 (contact is no longer blocked)
  • Contact record remains in the system (not deleted)
  • Contact can now receive messages again
  • blacklisted_by and blacklisted_at remain unchanged (for history)
  • Contact is NOT automatically added back to any list

Example in JavaScript

1async function removeFromBlockedList(contactIds) {
2    // Confirm unblocking
3    if (!confirm('Unblock "$"{contactIds.length} contact(s)? They will be able to receive messages again.')) {
4        return;
5    }
6
7    const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/remove', {
8        method: 'POST',
9        headers: {
10            'X-API-SID': 'SID....................................',
11            'X-API-PUBLIC-KEY': 'PK.....................................',
12            'Content-Type': 'application/json',
13            'Accept': 'application/json'
14        },
15        body: JSON.stringify({
16            contact_ids: contactIds
17        })
18    });
19
20    const data = await response.json();
21
22    if (data.success) {
23        console.log('Contacts unblocked successfully');
24        alert('"$"{contactIds.length} contact(s) removed from blocked list');
25        // Refresh blocked list
26        await getBlockedList();
27    } else {
28        console.error('Error:', data.message);
29        alert('Failed to unblock contacts');
30    }
31}
32
33// Usage - Unblock single contact
34removeFromBlockedList([1234]);
35
36// Usage - Unblock multiple contacts
37removeFromBlockedList([1234, 1235, 1236]);

Important Notes

  • Unblocking sets blacklisted=0 but 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.

DELETE: /api/v1/contact/blocked-list/delete
⚠️ Warning: Deleting blocked contacts is permanent and cannot be undone. All contact information will be lost. If you want to keep the contact record but allow messages, use the unblock endpoint (3.8.3) instead.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idsarrayYesArray of contact IDs to delete permanently (must exist in contacts table)

Example Request

1curl -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 '{
2    "contact_ids": [1234, 1235]
3  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact(s) deleted successfully",
5    "data": null,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_ids": [
8            "The contact ids field is required."
9        ],
10        "contact_ids.0": [
11            "The selected contact ids.0 is invalid."
12        ]
13    }
14}

Example in JavaScript

1async function deleteBlockedContacts(contactIds) {
2    // Confirm deletion
3    if (!confirm(
4        'Permanently delete "$"{contactIds.length} blocked contact(s)? ' +
5        'This cannot be undone and all contact information will be lost.'
6    )) {
7        return;
8    }
9
10    const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/delete', {
11        method: 'DELETE',
12        headers: {
13            'X-API-SID': 'SID....................................',
14            'X-API-PUBLIC-KEY': 'PK.....................................',
15            'Content-Type': 'application/json',
16            'Accept': 'application/json'
17        },
18        body: JSON.stringify({
19            contact_ids: contactIds
20        })
21    });
22
23    const data = await response.json();
24
25    if (data.success) {
26        console.log('Blocked contacts deleted permanently');
27        alert('"$"{contactIds.length} blocked contact(s) deleted');
28        // Refresh blocked list
29        await getBlockedList();
30    } else {
31        console.error('Error:', data.message);
32        alert('Failed to delete contacts');
33    }
34}
35
36// Usage
37deleteBlockedContacts([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.

POST: /api/v1/contact/blocked-list/export

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idsarrayYesArray of contact IDs to export (must exist in contacts table)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/export" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "contact_ids": [1234, 1235, 1236]
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "File generated successfully",
5    "data": {
6        "url": "https://your-storage.digitaloceanspaces.com/blocked-list/blocked_list_export_1729180845.xlsx"
7    },
8    "errors": null
9}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_ids": [
8            "The contact ids field is required."
9        ],
10        "contact_ids.0": [
11            "The selected contact ids.0 is invalid."
12        ]
13    }
14}

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

1async function exportBlockedList(contactIds) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/export', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Content-Type': 'application/json',
8            'Accept': 'application/json'
9        },
10        body: JSON.stringify({
11            contact_ids: contactIds
12        })
13    });
14
15    const data = await response.json();
16
17    if (data.success) {
18        console.log('Export successful, downloading...');
19        // Trigger download
20        window.open(data.data.url, '_blank');
21        return data.data.url;
22    } else {
23        console.error('Export failed:', data.message);
24        throw new Error(data.message);
25    }
26}
27
28// Usage - Export specific contacts
29exportBlockedList([1234, 1235, 1236]);
30
31// Usage - Export all blocked contacts (get IDs from list first)
32async function exportAllBlocked() {
33    const blockedData = await getBlockedList({ limit: 1000 });
34    const allIds = blockedData.blocked_list.data.map(contact => contact.id);
35    await exportBlockedList(allIds);
36}

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 here.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_by filter 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

1// Complete blocked list management workflow
2class BlockedListManager {
3    constructor(apiSid, apiKey) {
4        this.apiSid = apiSid;
5        this.apiKey = apiKey;
6        this.baseUrl = 'https://api.texttorrent.com/api/v1';
7    }
8
9    async getList(options = {}) {
10        const params = new URLSearchParams({
11            page: options.page || 1,
12            limit: options.limit || 10
13        });
14
15        if (options.search) params.append('search', options.search);
16        if (options.addedBy) params.append('added_by', options.addedBy);
17
18        return await this.apiCall('GET', '/contact/blocked-list/?"$"{params}');
19    }
20
21    async blockNumbers(phoneNumbers) {
22        return await this.apiCall('POST', '/contact/blocked-list/', {
23            numbers: phoneNumbers
24        });
25    }
26
27    async unblock(contactIds) {
28        return await this.apiCall('POST', '/contact/blocked-list/remove', {
29            contact_ids: contactIds
30        });
31    }
32
33    async delete(contactIds) {
34        return await this.apiCall('DELETE', '/contact/blocked-list/delete', {
35            contact_ids: contactIds
36        });
37    }
38
39    async export(contactIds) {
40        const result = await this.apiCall('POST', '/contact/blocked-list/export', {
41            contact_ids: contactIds
42        });
43
44        if (result.success) {
45            window.open(result.data.url, '_blank');
46        }
47
48        return result;
49    }
50
51    async auditBlockedList() {
52        // Get all blocked contacts
53        const data = await this.getList({ limit: 1000 });
54        const blocked = data.data.blocked_list.data;
55
56        console.log('Total blocked contacts: "$"{data.data.total}');
57        console.log('Opt-out words configured: "$"{data.data.total_otp_out}');
58
59        // Categorize
60        const manual = blocked.filter(c => c.blacklisted_by !== null);
61        const auto = blocked.filter(c => c.blacklisted_by === null);
62
63        console.log('Manually blocked: "$"{manual.length}');
64        console.log('Auto-blocked: "$"{auto.length}');
65
66        // Export for records
67        const allIds = blocked.map(c => c.id);
68        await this.export(allIds);
69
70        return { total: data.data.total, manual: manual.length, auto: auto.length };
71    }
72
73    async apiCall(method, endpoint, data = null) {
74        const headers = {
75            'X-API-SID': this.apiSid,
76            'X-API-PUBLIC-KEY': this.apiKey,
77            'Accept': 'application/json'
78        };
79
80        if (data && method !== 'GET') {
81            headers['Content-Type'] = 'application/json';
82        }
83
84        const options = {
85            method,
86            headers,
87            body: data && method !== 'GET' ? JSON.stringify(data) : null
88        };
89
90        const response = await fetch(this.baseUrl + endpoint, options);
91        return await response.json();
92    }
93}
94
95// Usage
96const blockedMgr = new BlockedListManager('SID....................................', 'PK.....................................');
97
98// Block numbers
99await blockedMgr.blockNumbers(['2025551234', '2025555678']);
100
101// Get blocked list
102await blockedMgr.getList({ addedBy: 'manual' });
103
104// Unblock contacts
105await blockedMgr.unblock([1234, 1235]);
106
107// Delete contacts
108await blockedMgr.delete([1236]);
109
110// Export contacts
111await blockedMgr.export([1234, 1235, 1236]);
112
113// Run audit
114await 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
ℹ️ How Opt-Out Words Work:
  1. You configure opt-out words (e.g., "STOP", "UNSUBSCRIBE", "OPT OUT")
  2. When a contact replies with a message containing any opt-out word
  3. The system automatically sets their blacklisted status to 1
  4. The contact appears in blocked list with blacklisted_by=null (auto-blocked)
  5. They will no longer receive messages from your account
⚠️ Compliance Notice: Opt-out words are critical for regulatory compliance (TCPA, CAN-SPAM, etc.). Always configure standard opt-out keywords like "STOP", "UNSUBSCRIBE", "CANCEL", "END", "QUIT". Failing to honor opt-out requests can result in legal penalties and fines.

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.

GET: /api/v1/contact/opt-out-word

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)
searchstringNoSearch term to filter opt-out words (partial match)

Example Request - Get All Opt-Out Words

1curl -X GET "https://api.texttorrent.com/api/v1/contact/opt-out-word" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - Search and Paginate

1curl -X GET "https://api.texttorrent.com/api/v1/contact/opt-out-word?search=STOP&limit=20&page=1" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Opt-out words fetched successfully",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "id": 1,
10                "user_id": 1,
11                "word": "STOP",
12                "created_at": "2025-01-15T10:30:00.000000Z",
13                "updated_at": "2025-01-15T10:30:00.000000Z"
14            },
15            {
16                "id": 2,
17                "user_id": 1,
18                "word": "UNSUBSCRIBE",
19                "created_at": "2025-01-15T10:31:00.000000Z",
20                "updated_at": "2025-01-15T10:31:00.000000Z"
21            },
22            {
23                "id": 3,
24                "user_id": 1,
25                "word": "CANCEL",
26                "created_at": "2025-01-15T10:32:00.000000Z",
27                "updated_at": "2025-01-15T10:32:00.000000Z"
28            },
29            {
30                "id": 4,
31                "user_id": 1,
32                "word": "END",
33                "created_at": "2025-01-15T10:33:00.000000Z",
34                "updated_at": "2025-01-15T10:33:00.000000Z"
35            },
36            {
37                "id": 5,
38                "user_id": 1,
39                "word": "QUIT",
40                "created_at": "2025-01-15T10:34:00.000000Z",
41                "updated_at": "2025-01-15T10:34:00.000000Z"
42            },
43            {
44                "id": 6,
45                "user_id": 1,
46                "word": "OPT OUT",
47                "created_at": "2025-01-15T10:35:00.000000Z",
48                "updated_at": "2025-01-15T10:35:00.000000Z"
49            }
50        ],
51        "first_page_url": "https://api.texttorrent.com/api/v1/contact/opt-out-word?page=1",
52        "from": 1,
53        "last_page": 1,
54        "last_page_url": "https://api.texttorrent.com/api/v1/contact/opt-out-word?page=1",
55        "next_page_url": null,
56        "path": "https://api.texttorrent.com/api/v1/contact/opt-out-word",
57        "per_page": 10,
58        "prev_page_url": null,
59        "to": 6,
60        "total": 6
61    },
62    "errors": null
63}

Opt-Out Word Fields

FieldTypeDescription
idintegerUnique identifier for the opt-out word
user_idintegerID of the user who created this opt-out word
wordstringThe opt-out keyword or phrase (case-insensitive)
created_atstringTimestamp when opt-out word was created (ISO 8601 format)
updated_atstringTimestamp when opt-out word was last updated (ISO 8601 format)

Example in JavaScript

1async function getOptOutWords(options = {}) {
2    const params = new URLSearchParams({
3        page: options.page || 1,
4        limit: options.limit || 10
5    });
6
7    if (options.search) {
8        params.append('search', options.search);
9    }
10
11    const response = await fetch(
12        'https://api.texttorrent.com/api/v1/contact/opt-out-word?"$"{params}',
13        {
14            method: 'GET',
15            headers: {
16                'X-API-SID': 'SID....................................',
17                'X-API-PUBLIC-KEY': 'PK.....................................',
18                'Accept': 'application/json'
19            }
20        }
21    );
22
23    const data = await response.json();
24
25    if (data.success) {
26        console.log('Opt-out words:', data.data.data);
27        console.log('Total opt-out words: "$"{data.data.total}');
28        return data.data;
29    } else {
30        console.error('Error:', data.message);
31        throw new Error(data.message);
32    }
33}
34
35// Usage - Get all opt-out words
36getOptOutWords();
37
38// Usage - Search for specific word
39getOptOutWords({ search: 'STOP' });
40
41// Usage - Get with custom limit
42getOptOutWords({ limit: 50 });

Important Notes

  • Results are ordered by created_at in 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.

POST: /api/v1/contact/opt-out-word

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
wordstringYesOpt-out keyword or phrase (max 255 characters)

Example Request - Create Single Word

1curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "word": "STOP"
8  }'

Example Request - Create Phrase

1curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "word": "DO NOT CONTACT ME"
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Opt-out word created successfully",
5    "data": {
6        "id": 7,
7        "user_id": 1,
8        "word": "STOP",
9        "created_at": "2025-10-17T14:30:00.000000Z",
10        "updated_at": "2025-10-17T14:30:00.000000Z"
11    },
12    "errors": null
13}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "word": [
8            "The word field is required.",
9            "The word must be a string.",
10            "The word may not be greater than 255 characters."
11        ]
12    }
13}

Duplicate Word Error (422)

1{
2    "code": 422,
3    "success": false,
4    "message": "This word already exists under your account.",
5    "data": null,
6    "errors": null
7}

Hierarchy Conflict Error (422)

1{
2    "code": 422,
3    "success": false,
4    "message": "This word already exists under your parent or master account.",
5    "data": null,
6    "errors": null
7}

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

1async function createOptOutWord(word) {
2    // Validate word
3    if (!word || word.trim().length === 0) {
4        throw new Error('Opt-out word cannot be empty');
5    }
6
7    if (word.length > 255) {
8        throw new Error('Opt-out word is too long (max 255 characters)');
9    }
10
11    const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word', {
12        method: 'POST',
13        headers: {
14            'X-API-SID': 'SID....................................',
15            'X-API-PUBLIC-KEY': 'PK.....................................',
16            'Content-Type': 'application/json',
17            'Accept': 'application/json'
18        },
19        body: JSON.stringify({
20            word: word.trim().toUpperCase() // Standardize to uppercase
21        })
22    });
23
24    const data = await response.json();
25
26    if (data.success) {
27        console.log('Opt-out word "$"{word}" created successfully');
28        alert('Opt-out word added: "$"{word}');
29        return data.data;
30    } else {
31        console.error('Error:', data.message);
32        alert('Failed to add opt-out word: "$"{data.message}');
33        throw new Error(data.message);
34    }
35}
36
37// Usage - Create single opt-out word
38createOptOutWord('STOP');
39
40// Usage - Create common opt-out words
41const commonOptOutWords = ['STOP', 'UNSUBSCRIBE', 'CANCEL', 'END', 'QUIT', 'OPT OUT'];
42for (const word of commonOptOutWords) {
43    try {
44        await createOptOutWord(word);
45    } catch (error) {
46        console.log('Skipping "$"{word}": "$"{error.message}');
47    }
48}

Example in PHP

1function createOptOutWord($word) {
2    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/opt-out-word');
3
4    curl_setopt($ch, CURLOPT_POST, true);
5    curl_setopt($ch, CURLOPT_HTTPHEADER, [
6        'X-API-SID: SID....................................',
7        'X-API-PUBLIC-KEY: PK.....................................',
8        'Content-Type: application/json',
9        'Accept: application/json'
10    ]);
11    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
12        'word' => strtoupper(trim($word))
13    ]));
14    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
15
16    $response = curl_exec($ch);
17    curl_close($ch);
18
19    $data = json_decode($response, true);
20
21    if ($data['success']) {
22        echo "Opt-out word created: " . $word;
23        return $data['data'];
24    } else {
25        echo 'Error: ' . $data['message'];
26        return false;
27    }
28}
29
30// Usage
31createOptOutWord('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.

PUT: /api/v1/contact/opt-out-word/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesID of the opt-out word to update

Request Body Parameters

ParameterTypeRequiredDescription
wordstringYesNew opt-out keyword or phrase (max 255 characters, must be unique)

Example Request

1curl -X PUT "https://api.texttorrent.com/api/v1/contact/opt-out-word/7" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "word": "STOP ALL"
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Opt-out word updated successfully",
5    "data": {
6        "id": 7,
7        "user_id": 1,
8        "word": "STOP ALL",
9        "created_at": "2025-10-17T14:30:00.000000Z",
10        "updated_at": "2025-10-17T15:45:00.000000Z"
11    },
12    "errors": null
13}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "word": [
8            "The word field is required.",
9            "The word has already been taken."
10        ]
11    }
12}

Not Found Error (404)

1{
2    "code": 404,
3    "success": false,
4    "message": "Opt-out word not found",
5    "data": null,
6    "errors": null
7}

Example in JavaScript

1async function updateOptOutWord(id, newWord) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word/"$"{id}', {
3        method: 'PUT',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Content-Type': 'application/json',
8            'Accept': 'application/json'
9        },
10        body: JSON.stringify({
11            word: newWord.trim().toUpperCase()
12        })
13    });
14
15    const data = await response.json();
16
17    if (data.success) {
18        console.log('Opt-out word updated successfully');
19        return data.data;
20    } else {
21        console.error('Error:', data.message);
22        throw new Error(data.message);
23    }
24}
25
26// Usage
27updateOptOutWord(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.

POST: /api/v1/contact/opt-out-word/delete
⚠️ Warning: Be very careful when deleting opt-out words. Removing standard words like "STOP" or "UNSUBSCRIBE" may violate compliance regulations and put your account at legal risk. Only delete custom or duplicate words.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
idsarrayYesArray of opt-out word IDs to delete (must exist in opt_out_words table)

Example Request - Delete Single Word

1curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word/delete" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "ids": [7]
8  }'

Example Request - Delete Multiple Words

1curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word/delete" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "ids": [7, 8, 9]
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Opt-out word(s) deleted successfully",
5    "data": null,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "ids": [
8            "The ids field is required.",
9            "The ids must be an array."
10        ],
11        "ids.0": [
12            "The selected ids.0 is invalid."
13        ]
14    }
15}

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

1async function deleteOptOutWords(ids) {
2    // Confirm deletion
3    if (!confirm('Delete "$"{ids.length} opt-out word(s)? This cannot be undone.')) {
4        return;
5    }
6
7    const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word/delete', {
8        method: 'POST',
9        headers: {
10            'X-API-SID': 'SID....................................',
11            'X-API-PUBLIC-KEY': 'PK.....................................',
12            'Content-Type': 'application/json',
13            'Accept': 'application/json'
14        },
15        body: JSON.stringify({
16            ids: ids
17        })
18    });
19
20    const data = await response.json();
21
22    if (data.success) {
23        console.log('Opt-out words deleted successfully');
24        alert('"$"{ids.length} opt-out word(s) deleted"');
25        // Refresh list
26        await getOptOutWords();
27    } else {
28        console.error('Error:', data.message);
29        alert('Failed to delete opt-out words');
30    }
31}
32
33// Usage - Delete single opt-out word
34deleteOptOutWords([7]);
35
36// Usage - Delete multiple opt-out words
37deleteOptOutWords([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.

POST: /api/v1/contact/opt-out-word/export

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Content-Type: application/json
4Accept: application/json

Request Body Parameters

ParameterTypeRequiredDescription
idsarrayNoArray of opt-out word IDs to export (if omitted, exports all words)

Example Request - Export Specific Words

1curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word/export" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{
7    "ids": [1, 2, 3]
8  }'

Example Request - Export All Words

1curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word/export" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Content-Type: application/json" 
5  -H "Accept: application/json" 
6  -d '{}'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Opt-out words exported successfully",
5    "data": {
6        "message": "Opt-out words exported successfully",
7        "file": "https://your-storage.digitaloceanspaces.com/opt-out-words/opt-out-words-2025-10-17_14-30-45.csv"
8    },
9    "errors": null
10}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "ids": [
8            "The ids must be an array."
9        ],
10        "ids.0": [
11            "The ids.0 must be an integer.",
12            "The selected ids.0 is invalid."
13        ]
14    }
15}

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

1async function exportOptOutWords(ids = null) {
2    const body = ids ? { ids } : {};
3
4    const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word/export', {
5        method: 'POST',
6        headers: {
7            'X-API-SID': 'SID....................................',
8            'X-API-PUBLIC-KEY': 'PK.....................................',
9            'Content-Type': 'application/json',
10            'Accept': 'application/json'
11        },
12        body: JSON.stringify(body)
13    });
14
15    const data = await response.json();
16
17    if (data.success) {
18        console.log('Export successful, downloading...');
19        // Trigger download
20        window.open(data.data.file, '_blank');
21        return data.data.file;
22    } else {
23        console.error('Export failed:', data.message);
24        throw new Error(data.message);
25    }
26}
27
28// Usage - Export specific opt-out words
29exportOptOutWords([1, 2, 3]);
30
31// Usage - Export all opt-out words
32exportOptOutWords();
33
34// Usage - Export all with fetched IDs
35async function exportAllOptOutWords() {
36    const words = await getOptOutWords({ limit: 1000 });
37    const allIds = words.data.map(word => word.id);
38    await exportOptOutWords(allIds);
39}

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 ids is 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/PhrasePriorityReason
STOPCriticalLegally required by TCPA, most common opt-out word
STOPALLCriticalUsed by carriers to stop all messages from account
UNSUBSCRIBECriticalEmail convention, widely recognized
CANCELHighCommon alternative to STOP
ENDHighSimple and clear opt-out word
QUITHighCommon opt-out request
OPT OUTMediumExplicit two-word phrase
REMOVEMediumCommon list removal request

Implementation Workflow

1// Complete opt-out words management system
2class OptOutWordsManager {
3    constructor(apiSid, apiKey) {
4        this.apiSid = apiSid;
5        this.apiKey = apiKey;
6        this.baseUrl = 'https://api.texttorrent.com/api/v1';
7    }
8
9    async getAll(options = {}) {
10        const params = new URLSearchParams({
11            page: options.page || 1,
12            limit: options.limit || 50
13        });
14
15        if (options.search) params.append('search', options.search);
16
17        return await this.apiCall('GET', '/contact/opt-out-word?"$"{params}');
18    }
19
20    async create(word) {
21        return await this.apiCall('POST', '/contact/opt-out-word', { word });
22    }
23
24    async update(id, word) {
25        return await this.apiCall('PUT', '/contact/opt-out-word"$"{id}', { word });
26    }
27
28    async delete(ids) {
29        return await this.apiCall('POST', '/contact/opt-out-worddelete', { ids });
30    }
31
32    async export(ids = null) {
33        const result = await this.apiCall('POST', '/contact/opt-out-wordexport',
34            ids ? { ids } : {}
35        );
36
37        if (result.success) {
38            window.open(result.data.file, '_blank');
39        }
40
41        return result;
42    }
43
44    async setupCompliance() {
45        // Configure all essential opt-out words for compliance
46        const essentialWords = [
47            'STOP',
48            'STOPALL',
49            'UNSUBSCRIBE',
50            'CANCEL',
51            'END',
52            'QUIT',
53            'OPT OUT',
54            'REMOVE'
55        ];
56
57        console.log('Setting up compliance opt-out words...');
58
59        for (const word of essentialWords) {
60            try {
61                await this.create(word);
62                console.log('✓ Added: "$"{word}');
63            } catch (error) {
64                console.log('Skipped "$"{word}: "$"{error.message}');
65            }
66        }
67
68        console.log('Compliance setup complete!');
69
70        // Verify all words are configured
71        const current = await this.getAll({ limit: 100 });
72        console.log('Total opt-out words configured: "$"{current.data.total}');
73
74        return current;
75    }
76
77    async apiCall(method, endpoint, data = null) {
78        const headers = {
79            'X-API-SID': this.apiSid,
80            'X-API-PUBLIC-KEY': this.apiKey,
81            'Accept': 'application/json'
82        };
83
84        if (data && method !== 'GET') {
85            headers['Content-Type'] = 'application/json';
86        }
87
88        const options = {
89            method,
90            headers,
91            body: data && method !== 'GET' ? JSON.stringify(data) : null
92        };
93
94        const response = await fetch(this.baseUrl + endpoint, options);
95        const result = await response.json();
96
97        if (!result.success) {
98            throw new Error(result.message);
99        }
100
101        return result;
102    }
103}
104
105// Usage
106const optOutMgr = new OptOutWordsManager('SID....................................', 'PK.....................................');
107
108// Setup compliance words
109await optOutMgr.setupCompliance();
110
111// Get all opt-out words
112await optOutMgr.getAll();
113
114// Add custom word
115await optOutMgr.create('DO NOT CONTACT');
116
117// Update word
118await optOutMgr.update(7, 'STOP MESSAGING');
119
120// Delete words
121await optOutMgr.delete([7, 8]);
122
123// Export all words for backup
124await optOutMgr.export();

Testing Your Opt-Out Words

  1. Send a test message to a phone number you control
  2. Reply with each opt-out word (STOP, UNSUBSCRIBE, etc.)
  3. Verify the contact appears in blocked list with blacklisted_by=null
  4. Confirm no further messages can be sent to that contact
  5. Test with different capitalization (stop, STOP, Stop)
  6. 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
ℹ️ About Inbox: The Inbox module provides a conversation-centric view of your SMS communications. Each conversation (chat) groups all messages between you and a specific contact. Messages are automatically marked as read when you view a conversation.

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.

GET: /api/v1/inbox

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)
searchstringNoSearch by contact name, number, email, company, or message content
folderintegerNoFilter by folder ID
timestringNoFilter by time: today, last_week, last_month,last_year
unreadbooleanNoShow only conversations with unread messages (default: false)
emailstringNoBase64 encoded email address of a sub-user. Main accounts can use this to retrieve inbox messages belonging to their sub-users.
ℹ️ Sub-Account Access: Main account holders can view inbox messages of their sub-users by providing the sub-user's email address (Base64 encoded) via the email parameter. If the provided email does not belong to a sub-user associated with your main account, a 403 Forbidden response will be returned.

Example 1: Get All Conversations (Main Account)

1curl -X GET "https://api.texttorrent.com/api/v1/inbox" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example 2: Retrieve Sub-User's Inbox Messages

1# First, encode the sub-user's email in Base64
2# Example: subuser@example.com → c3VidXNlckBleGFtcGxlLmNvbQ==
3
4curl -X GET "https://api.texttorrent.com/api/v1/inbox?email=c3VidXNlckBleGFtcGxlLmNvbQ==" 
5  -H "X-API-SID: SID...................................." 
6  -H "X-API-PUBLIC-KEY: PK....................................." 
7  -H "Accept: application/json"

Example 3: Search and Filter

1curl -X GET "https://api.texttorrent.com/api/v1/inbox?search=john&folder=5&time=last_week&unread=true&limit=20" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Chats retrieved successfully",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "chat_id": 1234,
10                "contact_id": 567,
11                "first_name": "John",
12                "last_name": "Doe",
13                "number": "+12025551234",
14                "email": "john.doe@example.com",
15                "company": "Acme Corp",
16                "folder_id": 5,
17                "last_message": "Thanks for the update!",
18                "last_chat_time": "2025-10-17T14:30:00.000000Z",
19                "unread_count": 3,
20                "send_by": "contact",
21                "avatar_ltr": "JD"
22            },
23            {
24                "chat_id": 1235,
25                "contact_id": 568,
26                "first_name": "Jane",
27                "last_name": "Smith",
28                "number": "+12025555678",
29                "email": "jane.smith@example.com",
30                "company": null,
31                "folder_id": null,
32                "last_message": "Got it, will check tomorrow",
33                "last_chat_time": "2025-10-17T13:15:00.000000Z",
34                "unread_count": 0,
35                "send_by": "me",
36                "avatar_ltr": "JS"
37            }
38        ],
39        "first_page_url": "https://api.texttorrent.com/api/v1/inbox?page=1",
40        "from": 1,
41        "last_page": 5,
42        "last_page_url": "https://api.texttorrent.com/api/v1/inbox?page=5",
43        "next_page_url": "https://api.texttorrent.com/api/v1/inbox?page=2",
44        "path": "https://api.texttorrent.com/api/v1/inbox",
45        "per_page": 10,
46        "prev_page_url": null,
47        "to": 10,
48        "total": 48
49    },
50    "errors": null
51}

Error Response - Unauthorized Sub-User Access (403 Forbidden)

1{
2    "code": 403,
3    "success": false,
4    "message": "Unauthorized access",
5    "data": null,
6    "errors": null
7}

Conversation Fields

FieldTypeDescription
chat_idintegerUnique identifier for the conversation thread
contact_idintegerID of the contact in this conversation
last_messagestringMost recent message content in the conversation
last_chat_timestringTimestamp of the last message (ISO 8601 format)
unread_countintegerNumber of unread messages in this conversation
send_bystringWho sent the last message: me or contact
avatar_ltrstringContact initials for avatar display (e.g., "JD")

Example in JavaScript

1async function getInboxMessages(options = {}) {
2    const params = new URLSearchParams({
3        page: options.page || 1,
4        limit: options.limit || 10
5    });
6
7    if (options.search) params.append('search', options.search);
8    if (options.folder) params.append('folder', options.folder);
9    if (options.time) params.append('time', options.time);
10    if (options.unread) params.append('unread', 'true');
11
12    const response = await fetch(
13        'https://api.texttorrent.com/api/v1/inbox?"$"{params}',
14        {
15            method: 'GET',
16            headers: {
17                'X-API-SID': 'SID....................................',
18                'X-API-PUBLIC-KEY': 'PK.....................................',
19                'Accept': 'application/json'
20            }
21        }
22    );
23
24    const data = await response.json();
25
26    if (data.success) {
27        console.log('Conversations:', data.data.data);
28        console.log('Total conversations: "$"{data.data.total}');
29
30        // Count unread messages
31        const totalUnread = data.data.data.reduce((sum, chat) => sum + chat.unread_count, 0);
32        console.log('Total unread messages: "$"{totalUnread}');
33
34        return data.data;
35    } else {
36        console.error('Error:', data.message);
37        throw new Error(data.message);
38    }
39}
40
41// Usage - Get all conversations
42getInboxMessages();
43
44// Usage - Get unread conversations only
45getInboxMessages({ unread: true });
46
47// Usage - Search conversations
48getInboxMessages({ search: 'john' });
49
50// Usage - Filter by folder and time
51getInboxMessages({ 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_time in 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_by indicates who sent the most recent message
  • Sub-accounts will only see their own inbox messages when no email parameter is provided
  • Sub-User Access: Main accounts can retrieve inbox messages for their sub-users by providing the Base64 encoded email in the email parameter
  • To encode email in Base64: echo -n "subuser@example.com" | base64 (Linux/Mac) or use an online Base64 encoder
  • Attempting to access data for an email that is not associated with your sub-users will result in a 403 Forbidden error

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.

GET: /api/v1/inbox/last-chat

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/inbox/last-chat" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Last chat retrieved successfully",
5    "data": {
6        "chat_id": 1234,
7        "contact_id": 567,
8        "contact_name": "John Doe",
9        "contact_phone_number": "+12025551234",
10        "last_message": "Thanks for the update!",
11        "last_chat_time": "2025-10-17T14:30:00.000000Z"
12    },
13    "errors": null
14}

Success Response - No Chats (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Last chat retrieved successfully",
5    "data": null,
6    "errors": null
7}

Example in JavaScript

1async function getLastChat() {
2    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/last-chat', {
3        method: 'GET',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json'
8        }
9    });
10
11    const data = await response.json();
12
13    if (data.success) {
14        if (data.data) {
15            console.log('Last active chat:', data.data);
16            console.log('Continue conversation with "$"{data.data.contact_name}');
17            return data.data;
18        } else {
19            console.log('No previous conversations found');
20            return null;
21        }
22    } else {
23        console.error('Error:', data.message);
24        throw new Error(data.message);
25    }
26}
27
28// Usage
29const lastChat = await getLastChat();
30if (lastChat) {
31    // Navigate to the conversation
32    window.location.href = '/inbox/"$"{lastChat.chat_id}';
33}

Important Notes

  • Returns the conversation with the most recent updated_at timestamp
  • Excludes conversations with blocked contacts
  • Returns null if 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.

GET: /api/v1/inbox/templates

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
searchstringNoSearch templates by name (partial match)
emailstringNoBase64 encoded email address of a sub-user. Main accounts can use this to retrieve message templates belonging to their sub-users.
ℹ️ Sub-Account Access: Main account holders can view message templates of their sub-users by providing the sub-user's email address (Base64 encoded) via the email parameter. If the provided email does not belong to a sub-user associated with your main account, a 403 Forbidden response will be returned.

Example 1: Get All Templates (Main Account)

1curl -X GET "https://api.texttorrent.com/api/v1/inbox/templates" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example 2: Retrieve Sub-User's Message Templates

1# First, encode the sub-user's email in Base64
2# Example: subuser@example.com → c3VidXNlckBleGFtcGxlLmNvbQ==
3
4curl -X GET "https://api.texttorrent.com/api/v1/inbox/templates?email=c3VidXNlckBleGFtcGxlLmNvbQ==" 
5  -H "X-API-SID: SID...................................." 
6  -H "X-API-PUBLIC-KEY: PK....................................." 
7  -H "Accept: application/json"

Example 3: Search Templates

1curl -X GET "https://api.texttorrent.com/api/v1/inbox/templates?search=welcome" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Templates retrieved successfully",
5    "data": [
6        {
7            "id": 1,
8            "user_id": 1,
9            "template_name": "Welcome Message",
10            "status": 1,
11            "preview_message": "Welcome to our service! We're excited to have you on board.",
12            "created_at": "2025-10-01T10:30:00.000000Z"
13        },
14        {
15            "id": 2,
16            "user_id": 1,
17            "template_name": "Follow Up",
18            "status": 1,
19            "preview_message": "Just following up on our previous conversation. Let me know if you have any questions!",
20            "created_at": "2025-10-05T14:20:00.000000Z"
21        },
22        {
23            "id": 3,
24            "user_id": 1,
25            "template_name": "Thank You",
26            "status": 1,
27            "preview_message": "Thank you for your business! We appreciate your support.",
28            "created_at": "2025-10-10T09:15:00.000000Z"
29        }
30    ],
31    "errors": null
32}

Error Response - Unauthorized Sub-User Access (403 Forbidden)

1{
2    "code": 403,
3    "success": false,
4    "message": "Unauthorized access",
5    "data": null,
6    "errors": null
7}

Template Fields

FieldTypeDescription
idintegerUnique identifier for the template
template_namestringName/title of the template
statusintegerTemplate status: 1 = active, 0 = inactive
preview_messagestringThe template message content
created_atstringTimestamp when template was created (ISO 8601 format)

Example in JavaScript

1async function getMessageTemplates(searchTerm = null) {
2    const params = new URLSearchParams();
3    if (searchTerm) params.append('search', searchTerm);
4
5    const url = searchTerm
6        ? 'https://api.texttorrent.com/api/v1/inbox/templates?"$"{params}'
7        : 'https://api.texttorrent.com/api/v1/inbox/templates';
8
9    const response = await fetch(url, {
10        method: 'GET',
11        headers: {
12            'X-API-SID': 'SID....................................',
13            'X-API-PUBLIC-KEY': 'PK.....................................',
14            'Accept': 'application/json'
15        }
16    });
17
18    const data = await response.json();
19
20    if (data.success) {
21        console.log('Message templates:', data.data);
22
23        // Display templates in a dropdown
24        const activeTemplates = data.data.filter(t => t.status === 1);
25        console.log('"$"{activeTemplates.length} active templates available');
26
27        return data.data;
28    } else {
29        console.error('Error:', data.message);
30        throw new Error(data.message);
31    }
32}
33
34// Usage - Get all templates
35const templates = await getMessageTemplates();
36
37// Usage - Search templates
38const welcomeTemplates = await getMessageTemplates('welcome');
39
40// Usage - Create template selector UI
41function createTemplateSelector(templates) {
42    const select = document.createElement('select');
43    select.innerHTML = 'Select a template...';
44
45    templates.forEach(template => {
46        if (template.status === 1) {
47            const option = document.createElement('option');
48            option.value = template.preview_message;
49            option.textContent = template.template_name;
50            select.appendChild(option);
51        }
52    });
53
54    select.addEventListener('change', (e) => {
55        if (e.target.value) {
56            document.getElementById('message-input').value = e.target.value;
57        }
58    });
59
60    return select;
61}

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 have status=0
  • Returns all templates (no pagination)
  • Use templates to save time on frequently sent messages
  • Sub-accounts will only see their own templates when no email parameter is provided
  • Sub-User Access: Main accounts can retrieve message templates for their sub-users by providing the Base64 encoded email in the email parameter
  • To encode email in Base64: echo -n "subuser@example.com" | base64 (Linux/Mac) or use an online Base64 encoder
  • Attempting to access data for an email that is not associated with your sub-users will result in a 403 Forbidden error

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.

GET: /api/v1/inbox/numbers/active

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/inbox/numbers/active?limit=20" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Active numbers retrieved successfully",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "id": 1,
10                "user_id": 1,
11                "purchased_by_user": "John Admin",
12                "purchased_by": 1,
13                "status": 1,
14                "created_at": "2025-09-15T10:30:00.000000Z",
15                "number": "+12025551234",
16                "friendly_name": "Sales Line",
17                "region": "DC",
18                "country": "US",
19                "latitude": "38.895",
20                "longitude": "-77.036",
21                "postal_code": "20001",
22                "capabilities": {
23                    "voice": true,
24                    "sms": true,
25                    "mms": true
26                },
27                "purchased_at": "2025-09-15T10:30:00.000000Z",
28                "twilio_number_sid": "PNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
29                "twilio_service_sid": "MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
30                "type": "local"
31            },
32            {
33                "id": 2,
34                "user_id": 1,
35                "purchased_by_user": "Jane Manager",
36                "purchased_by": 5,
37                "status": 1,
38                "created_at": "2025-09-20T14:20:00.000000Z",
39                "number": "+12025555678",
40                "friendly_name": "Support Line",
41                "region": "DC",
42                "country": "US",
43                "latitude": "38.895",
44                "longitude": "-77.036",
45                "postal_code": "20001",
46                "capabilities": {
47                    "voice": true,
48                    "sms": true,
49                    "mms": true
50                },
51                "purchased_at": "2025-09-20T14:20:00.000000Z",
52                "twilio_number_sid": "PNyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
53                "twilio_service_sid": "MGyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
54                "type": "local"
55            }
56        ],
57        "first_page_url": "https://api.texttorrent.com/api/v1/inbox/numbers/active?page=1",
58        "from": 1,
59        "last_page": 2,
60        "last_page_url": "https://api.texttorrent.com/api/v1/inbox/numbers/active?page=2",
61        "next_page_url": "https://api.texttorrent.com/api/v1/inbox/numbers/active?page=2",
62        "path": "https://api.texttorrent.com/api/v1/inbox/numbers/active",
63        "per_page": 10,
64        "prev_page_url": null,
65        "to": 10,
66        "total": 15
67    },
68    "errors": null
69}

Number Fields

FieldTypeDescription
numberstringPhone number in E.164 format (e.g., +12025551234)
friendly_namestringCustom name for the number (e.g., "Sales Line")
statusintegerNumber status: 1 = active, 0 = inactive
purchased_by_userstringFull name of user who purchased the number
capabilitiesobjectNumber capabilities: voice, sms, mms
typestringNumber type: local, toll-free, mobile
twilio_number_sidstringTwilio number SID

Example in JavaScript

1async function getActiveNumbers(options = {}) {
2    const params = new URLSearchParams({
3        page: options.page || 1,
4        limit: options.limit || 10
5    });
6
7    const response = await fetch(
8        'https://api.texttorrent.com/api/v1/inbox/numbers/active?"$"{params}',
9        {
10            method: 'GET',
11            headers: {
12                'X-API-SID': 'SID....................................',
13                'X-API-PUBLIC-KEY': 'PK.....................................',
14                'Accept': 'application/json'
15            }
16        }
17    );
18
19    const data = await response.json();
20
21    if (data.success) {
22        console.log('Active numbers:', data.data.data);
23        console.log('Total active numbers: "$"{data.data.total}');
24
25        // Create sender number dropdown
26        const smsCapableNumbers = data.data.data.filter(n => n.capabilities.sms);
27        console.log('"$"{smsCapableNumbers.length} numbers can send SMS');
28
29        return data.data;
30    } else {
31        console.error('Error:', data.message);
32        throw new Error(data.message);
33    }
34}
35
36// Usage
37const numbers = await getActiveNumbers();
38
39// Usage - Create from-number selector
40function createFromNumberSelector(numbers) {
41    const select = document.createElement('select');
42    select.innerHTML = 'Select sender number...';
43
44    numbers.data.forEach(number => {
45        if (number.status === 1 && number.capabilities.sms) {
46            const option = document.createElement('option');
47            option.value = number.number;
48            option.textContent = '"$"{number.friendly_name} ("$"{number.number})
49            ';
50            select.appendChild(option);
51        }
52    });
53
54    return select;
55}

Important Notes

  • Only returns numbers with status=1 (active)
  • Numbers must belong to your account (user_id matches)
  • Check capabilities.sms to verify SMS capability
  • Use number field 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.

GET: /api/v1/inbox/numbers/receiver

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/inbox/numbers/receiver" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Receiver numbers retrieved successfully",
5    "data": [
6        {
7            "id": 789,
8            "user_id": 1,
9            "first_name": "Alice",
10            "last_name": "Johnson",
11            "number": "+12025559876",
12            "email": "alice.johnson@example.com",
13            "company": "Tech Solutions",
14            "list_id": 10,
15            "folder_id": null,
16            "blacklisted": 0,
17            "created_at": "2025-10-15T10:30:00.000000Z",
18            "updated_at": "2025-10-15T10:30:00.000000Z"
19        },
20        {
21            "id": 790,
22            "user_id": 1,
23            "first_name": "Bob",
24            "last_name": "Williams",
25            "number": "+12025559877",
26            "email": "bob.williams@example.com",
27            "company": null,
28            "list_id": 10,
29            "folder_id": 3,
30            "blacklisted": 0,
31            "created_at": "2025-10-16T14:20:00.000000Z",
32            "updated_at": "2025-10-16T14:20:00.000000Z"
33        }
34    ],
35    "errors": null
36}

Example in JavaScript

1async function getReceiverNumbers() {
2    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/numbers/receiver', {
3        method: 'GET',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json'
8        }
9    });
10
11    const data = await response.json();
12
13    if (data.success) {
14        console.log('Available receivers:', data.data);
15        console.log('"$"{data.data.length} contacts available for new conversations');
16        return data.data;
17    } else {
18        console.error('Error:', data.message);
19        throw new Error(data.message);
20    }
21}
22
23// Usage - Get contacts for starting new conversations
24const availableContacts = await getReceiverNumbers();
25
26// Usage - Create "Start New Chat" contact selector
27function createNewChatSelector(contacts) {
28    const select = document.createElement('select');
29    select.innerHTML = 'Select contact to message...';
30
31    contacts.forEach(contact => {
32        const option = document.createElement('option');
33        option.value = contact.id;
34        option.textContent = '"$"{contact.first_name} "$"{contact.last_name} ("$"{contact.number})';
35        select.appendChild(option);
36    });
37
38    select.addEventListener('change', async (e) => {
39        if (e.target.value) {
40            // Start new conversation with selected contact
41            await startNewChat(e.target.value);
42        }
43    });
44
45    return select;
46}

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.

GET: /api/v1/inbox/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesChat ID (conversation ID)

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of messages per page (default: 10)
pageintegerNoPage number for message pagination (default: 1)

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/inbox/1234?limit=20" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Chat retrieved successfully",
5    "data": {
6        "chat": {
7            "id": 567,
8            "user_id": 1,
9            "first_name": "John",
10            "last_name": "Doe",
11            "number": "+12025551234",
12            "email": "john.doe@example.com",
13            "company": "Acme Corp",
14            "list_id": 10,
15            "folder_id": 5,
16            "blacklisted": 0,
17            "contact_id": 567,
18            "from_number": "+12025559999",
19            "chat_id": 1234,
20            "avatar_ltr": "JD",
21            "created_at": "2025-09-15T10:30:00.000000Z",
22            "updated_at": "2025-10-17T14:30:00.000000Z",
23            "notes": [
24                {
25                    "id": 1,
26                    "note": "VIP customer - priority support"
27                },
28                {
29                    "id": 2,
30                    "note": "Interested in premium features"
31                }
32            ]
33        },
34        "messages": {
35            "current_page": 1,
36            "data": [
37                {
38                    "id": 5001,
39                    "chat_id": 1234,
40                    "message": "Thanks for the update!",
41                    "direction": "inbound",
42                    "status": 1,
43                    "from_number": "+12025551234",
44                    "to_number": "+12025559999",
45                    "media_url": null,
46                    "created_at": "2025-10-17T14:30:00.000000Z",
47                    "updated_at": "2025-10-17T14:30:05.000000Z"
48                },
49                {
50                    "id": 5000,
51                    "chat_id": 1234,
52                    "message": "Your order has been shipped!",
53                    "direction": "outbound",
54                    "status": 1,
55                    "from_number": "+12025559999",
56                    "to_number": "+12025551234",
57                    "media_url": null,
58                    "created_at": "2025-10-17T14:25:00.000000Z",
59                    "updated_at": "2025-10-17T14:25:05.000000Z"
60                }
61            ],
62            "first_page_url": "https://api.texttorrent.com/api/v1/inbox/1234?page=1",
63            "from": 1,
64            "last_page": 3,
65            "last_page_url": "https://api.texttorrent.com/api/v1/inbox/1234?page=3",
66            "next_page_url": "https://api.texttorrent.com/api/v1/inbox/1234?page=2",
67            "path": "https://api.texttorrent.com/api/v1/inbox/1234",
68            "per_page": 10,
69            "prev_page_url": null,
70            "to": 10,
71            "total": 28
72        }
73    },
74    "errors": null
75}

Chat Not Found (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Chat not found",
5    "data": null,
6    "errors": null
7}

Message Fields

FieldTypeDescription
directionstringinbound (received) or outbound (sent)
statusinteger0 = unread, 1 = read
from_numberstringSender's phone number
to_numberstringRecipient's phone number
media_urlstring|nullURL to media attachment (for MMS)

Example in JavaScript

1async function getChatDetails(chatId, options = {}) {
2    const params = new URLSearchParams({
3        page: options.page || 1,
4        limit: options.limit || 10
5    });
6
7    const response = await fetch(
8        'https://api.texttorrent.com/api/v1/inbox/"$"{chatId}?"$"{params}',
9        {
10            method: 'GET',
11            headers: {
12                'X-API-SID': 'SID....................................',
13                'X-API-PUBLIC-KEY': 'PK.....................................',
14                'Accept': 'application/json'
15            }
16        }
17    );
18
19    const data = await response.json();
20
21    if (data.success && data.data) {
22        const { chat, messages } = data.data;
23
24        console.log('Contact:', '"$"{chat.first_name} "$"{chat.last_name}');
25        console.log('Notes:', chat.notes);
26        console.log('Messages:', messages.data);
27        console.log('Total messages: "$"{messages.total}');
28
29        return data.data;
30    } else {
31        console.log('Chat not found');
32        return null;
33    }
34}
35
36// Usage
37const chatDetails = await getChatDetails(1234);
38
39// Usage - Load more messages (pagination)
40const moreMessages = await getChatDetails(1234, { page: 2, limit: 20 });
41
42// Usage - Display conversation UI
43function displayConversation(chatData) {
44    if (!chatData) return;
45
46    const { chat, messages } = chatData;
47
48    // Display contact info
49    document.getElementById('contact-name').textContent =
50        '"$"{chat.first_name} "$"{chat.last_name}';
51    document.getElementById('contact-number').textContent = chat.number;
52
53    // Display notes
54    const notesContainer = document.getElementById('notes');
55    notesContainer.innerHTML = chat.notes
56        .map(note => '"$"{note.note}')
57        .join('');
58
59    // Display messages
60    const messagesContainer = document.getElementById('messages');
61    messagesContainer.innerHTML = messages.data
62        .reverse() // Show oldest first
63        .map(msg => '
64                "$"{msg.message}
65                "$"{new Date(msg.created_at).toLocaleString()}
66                "$"{msg.media_url ? ' : ''}
67            
68        ')
69        .join('');
70}

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 null if 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_ltr provides 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.

DELETE: /api/v1/inbox/{id}
⚠️ Warning: Deleting a chat permanently removes all messages in the conversation. This action cannot be undone. The contact record remains in your contacts list.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesChat ID (conversation ID) to delete

Example Request

1curl -X DELETE "https://api.texttorrent.com/api/v1/inbox/1234" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Chat deleted successfully",
5    "data": null,
6    "errors": null
7}

Example in JavaScript

1async function deleteChat(chatId) {
2    // Confirm deletion
3    if (!confirm('Delete this conversation? All messages will be permanently removed.')) {
4        return;
5    }
6
7    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/"$"{chatId}', {
8        method: 'DELETE',
9        headers: {
10            'X-API-SID': 'SID....................................',
11            'X-API-PUBLIC-KEY': 'PK.....................................',
12            'Accept': 'application/json'
13        }
14    });
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Chat deleted successfully');
20        alert('Conversation deleted');
21        // Redirect to inbox
22        window.location.href = '/inbox';
23    } else {
24        console.error('Error:', data.message);
25        alert('Failed to delete conversation');
26    }
27}
28
29// Usage
30deleteChat(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=true filter 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:

1// Polling example - check for new messages every 10 seconds
2let lastCheckTime = new Date();
3
4async function pollNewMessages() {
5    const chats = await getInboxMessages({
6        time: 'today',
7        limit: 50
8    });
9
10    // Find conversations updated since last check
11    const newMessages = chats.data.filter(chat =>
12        new Date(chat.last_chat_time) > lastCheckTime
13    );
14
15    if (newMessages.length > 0) {
16        console.log('"$"{newMessages.length} new message(s)');
17        // Update UI
18        refreshInbox();
19        // Play notification sound
20        playNotificationSound();
21    }
22
23    lastCheckTime = new Date();
24}
25
26// Poll every 10 seconds
27setInterval(pollNewMessages, 10000);

Complete Inbox Management Example

1class InboxManager {
2    constructor(apiSid, apiKey) {
3        this.apiSid = apiSid;
4        this.apiKey = apiKey;
5        this.baseUrl = 'https://api.texttorrent.com/api/v1';
6    }
7
8    async getConversations(filters = {}) {
9        const params = new URLSearchParams(filters);
10        return await this.apiCall('GET', '/inbox?"$"{params}');
11    }
12
13    async getLastChat() {
14        return await this.apiCall('GET', '/inbox/last-chat');
15    }
16
17    async getTemplates(search = null) {
18        const params = search ? '?search="$"{search}' : '';
19        return await this.apiCall('GET', '/inbox/templates"$"{params}');
20    }
21
22    async getActiveNumbers() {
23        return await this.apiCall('GET', '/inbox/numbers/active');
24    }
25
26    async getReceiverNumbers() {
27        return await this.apiCall('GET', '/inbox/numbers/receiver');
28    }
29
30    async getChatDetails(chatId, page = 1, limit = 10) {
31        return await this.apiCall('GET', '/inbox/"$"{chatId}?page="$"{page}&limit="$"{limit}');
32    }
33
34    async deleteChat(chatId) {
35        return await this.apiCall('DELETE', '/inbox/"$"{chatId}');
36    }
37
38    async apiCall(method, endpoint, data = null) {
39        const headers = {
40            'X-API-SID': this.apiSid,
41            'X-API-PUBLIC-KEY': this.apiKey,
42            'Accept': 'application/json'
43        };
44
45        if (data && method !== 'GET') {
46            headers['Content-Type'] = 'application/json';
47        }
48
49        const options = {
50            method,
51            headers,
52            body: data && method !== 'GET' ? JSON.stringify(data) : null
53        };
54
55        const response = await fetch(this.baseUrl + endpoint, options);
56        const result = await response.json();
57
58        if (!result.success) {
59            throw new Error(result.message);
60        }
61
62        return result;
63    }
64}
65
66// Usage
67const inbox = new InboxManager('SID....................................', 'PK.....................................');
68
69// Get unread conversations
70const unreadChats = await inbox.getConversations({ unread: true });
71
72// Resume last conversation
73const lastChat = await inbox.getLastChat();
74if (lastChat.data) {
75    const chatDetails = await inbox.getChatDetails(lastChat.data.chat_id);
76}
77
78// Get templates for quick replies
79const templates = await inbox.getTemplates();
80
81// Delete old conversations
82await 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.

POST: /api/v1/inbox/chat

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: multipart/form-data

Request Body Parameters

ParameterTypeRequiredDescription
messagestringYesMessage content (max 5000 characters)
chat_idintegerYesID of the conversation thread
from_numberstringYesSender phone number (must exist in your active numbers)
to_numberstringYesRecipient phone number
chatFilefileNoMedia file attachment for MMS (image, video, audio)

Example Request - Send SMS

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/chat" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -F "message=Hello! How can I help you today?" 
6  -F "chat_id=1234" 
7  -F "from_number=+12025559999" 
8  -F "to_number=+12025551234"

Example Request - Send MMS with Media

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/chat" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -F "message=Check out this image!" 
6  -F "chat_id=1234" 
7  -F "from_number=+12025559999" 
8  -F "to_number=+12025551234" 
9  -F "chatFile=@/path/to/image.jpg"

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Message send successfully",
5    "data": {
6        "id": 5002,
7        "chat_id": 1234,
8        "direction": "outbound",
9        "message": "Hello! How can I help you today?",
10        "msg_type": "sms",
11        "file": null,
12        "api_send_status": "sent",
13        "msg_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
14        "created_at": "2025-10-17T15:30:00.000000Z",
15        "updated_at": "2025-10-17T15:30:05.000000Z"
16    },
17    "errors": null
18}

Error Response - Invalid Sender Number (400 Bad Request)

1{
2    "code": 400,
3    "success": false,
4    "message": "Invalid sender number",
5    "data": null,
6    "errors": null
7}

Error Response - Insufficient Credits (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "You do not have enough credits to perform this action.",
5    "data": null,
6    "errors": null
7}

Error Response - Daily Limit Reached (403 Forbidden)

1{
2    "code": 403,
3    "success": false,
4    "message": "You have reached your daily limit for sending messages.",
5    "data": null,
6    "errors": null
7}

Error Response - Inactive Number (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Unable to send message. Sender number is not active!",
5    "data": null,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "message": ["The message field is required."],
8        "from_number": ["The selected from number is invalid."],
9        "chat_id": ["The chat id field is required."],
10        "to_number": ["The to number field is required."]
11    }
12}

Example in JavaScript

1async function sendMessage(chatId, message, fromNumber, toNumber, mediaFile = null) {
2    const formData = new FormData();
3    formData.append('chat_id', chatId);
4    formData.append('message', message);
5    formData.append('from_number', fromNumber);
6    formData.append('to_number', toNumber);
7
8    if (mediaFile) {
9        formData.append('chatFile', mediaFile);
10    }
11
12    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/chat', {
13        method: 'POST',
14        headers: {
15            'X-API-SID': 'SID....................................',
16            'X-API-PUBLIC-KEY': 'PK.....................................',
17            'Accept': 'application/json'
18        },
19        body: formData
20    });
21
22    const data = await response.json();
23
24    if (data.success) {
25        console.log('Message sent:', data.data);
26        return data.data;
27    } else {
28        console.error('Error:', data.message);
29        throw new Error(data.message);
30    }
31}
32
33// Usage - Send SMS
34await sendMessage(1234, 'Hello!', '+12025559999', '+12025551234');
35
36// Usage - Send MMS with image
37const fileInput = document.getElementById('file-input');
38const file = fileInput.files[0];
39await sendMessage(1234, 'Check this out!', '+12025559999', '+12025551234', file);

Credit Costs

Gateway TypeSMS (per segment)MMS
Own Gateway1 credit3 credits + SMS credits for text
Text Torrent Gateway3 credits7 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.

POST: /api/v1/inbox/chat/create

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Request Body Parameters

ParameterTypeRequiredDescription
receiver_numberstringYesReceiver's phone number (10 digits, without +1)
sender_idstringYesSender phone number (your active number)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/chat/create" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "receiver_number": "2025551234",
8    "sender_id": "+12025559999"
9  }'

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Chat started successfully.",
5    "data": {
6        "id": 1235,
7        "user_id": 1,
8        "contact_id": 568,
9        "from_number": "+12025559999",
10        "last_message": null,
11        "created_at": "2025-10-17T15:45:00.000000Z",
12        "updated_at": "2025-10-17T15:45:00.000000Z"
13    },
14    "errors": null
15}

Error Response - Contact Blacklisted (404 Not Found)

1{
2    "code": 404,
3    "success": false,
4    "message": "This contact is blacklisted.",
5    "data": null,
6    "errors": null
7}

Error Response - Chat Already Exists (404 Not Found)

1{
2    "code": 404,
3    "success": false,
4    "message": "You have already started a chat with this contact.",
5    "data": null,
6    "errors": null
7}

Example in JavaScript

1async function startNewChat(receiverNumber, senderNumber) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/chat/create', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json',
8            'Content-Type': 'application/json'
9        },
10        body: JSON.stringify({
11            receiver_number: receiverNumber,
12            sender_id: senderNumber
13        })
14    });
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Chat created:', data.data);
20        // Navigate to the new chat
21        window.location.href = '/inbox/"$"{data.data.id}';
22        return data.data;
23    } else {
24        console.error('Error:', data.message);
25        alert(data.message);
26    }
27}
28
29// Usage - Start chat with new number
30await 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.

POST: /api/v1/inbox/generate/ai/response

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Request Body Parameters

ParameterTypeRequiredDescription
chat_idintegerYesID of the conversation (must exist in chats table)
directionstringYesMessage direction: inbound or outbound
messagestringYesThe last message content for context

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/generate/ai/response" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "chat_id": 1234,
8    "direction": "inbound",
9    "message": "When will my order arrive?"
10  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "AI replies generated successfully.",
5    "data": [
6        "Your order should arrive by Friday. I'll send you tracking info shortly!",
7        "Great question! Let me check the tracking for you right now.",
8        "It's on the way! Expected delivery is this Friday.",
9        "I'll look up your order status and get back to you in a moment.",
10        "Your package is in transit and should arrive within 2-3 business days.",
11        "Let me pull up your order details to give you an accurate ETA.",
12        "I see it's scheduled for Friday delivery. Would you like the tracking number?"
13    ],
14    "errors": null
15}

Error Response - No Chat History (404 Not Found)

1{
2    "code": 404,
3    "success": false,
4    "message": "No chat history found for this chat.",
5    "data": null,
6    "errors": null
7}

Error Response - Non-Conversational Message (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "No suitable replies generated. The last message may be too short or passive",
5    "data": null,
6    "errors": null
7}

Error Response - Insufficient Credits (404 Not Found)

1{
2    "code": 404,
3    "success": false,
4    "message": "You do not have enough credits to perform this action.",
5    "data": null,
6    "errors": null
7}

Example in JavaScript

1async function generateAiReplies(chatId, direction, message) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/generate/ai/response', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json',
8            'Content-Type': 'application/json'
9        },
10        body: JSON.stringify({
11            chat_id: chatId,
12            direction: direction,
13            message: message
14        })
15    });
16
17    const data = await response.json();
18
19    if (data.success) {
20        console.log('AI suggestions:', data.data);
21        return data.data;
22    } else {
23        console.error('Error:', data.message);
24        throw new Error(data.message);
25    }
26}
27
28// Usage - Get AI reply suggestions
29const suggestions = await generateAiReplies(1234, 'inbound', 'When will my order arrive?');
30
31// Display suggestions in UI
32function displaySuggestions(suggestions) {
33    const container = document.getElementById('suggestions');
34    container.innerHTML = '';
35
36    suggestions.forEach((suggestion, index) => {
37        const button = document.createElement('button');
38        button.className = 'suggestion-btn';
39        button.textContent = suggestion;
40        button.onclick = () => {
41            document.getElementById('message-input').value = suggestion;
42        };
43        container.appendChild(button);
44    });
45}
46
47displaySuggestions(suggestions);

Credit Costs

Gateway TypeCost per Request
Own Gateway3 credits
Text Torrent Gateway6 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.

POST: /api/v1/inbox/blacklist/{contactId}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
contactIdintegerYesID of the contact to block

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/blacklist/567" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact blacklisted successfully",
5    "data": null,
6    "errors": null
7}

Example in JavaScript

1async function blockContact(contactId) {
2    if (!confirm('Block this contact? They will not be able to message you.')) {
3        return;
4    }
5
6    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/blacklist/"$"{contactId}', {
7        method: 'POST',
8        headers: {
9            'X-API-SID': 'SID....................................',
10            'X-API-PUBLIC-KEY': 'PK.....................................',
11            'Accept': 'application/json'
12        }
13    });
14
15    const data = await response.json();
16
17    if (data.success) {
18        console.log('Contact blocked successfully');
19        alert('Contact has been blocked');
20        // Refresh inbox list
21        window.location.reload();
22    } else {
23        console.error('Error:', data.message);
24        alert('Failed to block contact');
25    }
26}
27
28// Usage
29blockContact(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.

POST: /api/v1/inbox/unblock

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idsarrayYesArray of contact IDs to unblock (minimum 1)

Example Request - Unblock Single Contact

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/unblock" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "contact_ids": [567]
8  }'

Example Request - Unblock Multiple Contacts

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/unblock" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "contact_ids": [567, 568, 569]
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact unblocked successfully",
5    "data": null,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "contact_ids": ["The contact ids field is required."],
8        "contact_ids.0": ["The selected contact ids.0 is invalid."]
9    }
10}

Example in JavaScript

1async function unblockContacts(contactIds) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/unblock', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json',
8            'Content-Type': 'application/json'
9        },
10        body: JSON.stringify({
11            contact_ids: contactIds
12        })
13    });
14
15    const data = await response.json();
16
17    if (data.success) {
18        console.log('Contacts unblocked successfully');
19        alert('"$"{contactIds.length} contact(s) have been unblocked');
20        return true;
21    } else {
22        console.error('Error:', data.message);
23        alert('Failed to unblock contacts');
24        return false;
25    }
26}
27
28// Usage - Unblock single contact
29await unblockContacts([567]);
30
31// Usage - Unblock multiple contacts
32await unblockContacts([567, 568, 569]);
33
34// Usage - Bulk unblock from checkbox selection
35function bulkUnblock() {
36    const checkboxes = document.querySelectorAll('.contact-checkbox:checked');
37    const contactIds = Array.from(checkboxes).map(cb => parseInt(cb.value));
38
39    if (contactIds.length === 0) {
40        alert('Please select contacts to unblock');
41        return;
42    }
43
44    if (confirm('Unblock "$"{contactIds.length} contact(s)?')) {
45        unblockContacts(contactIds);
46    }
47}

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.

POST: /api/v1/inbox/event/add

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Request Body Parameters

ParameterTypeRequiredDescription
namestringYesEvent name/title
subjectstringYesEvent subject/description
datestringYesEvent date (YYYY-MM-DD format)
timestringYesEvent time (HH:MM format, 24-hour)
sender_numberstringYesPhone number to send reminder from
alert_beforeintegerYesMinutes before event to send alert
participant_numberstringNoParticipant phone number
participant_emailstringNoParticipant email address (must be valid email format)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/event/add" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "name": "Follow-up Call",
8    "subject": "Discuss product demo with client",
9    "date": "2025-10-20",
10    "time": "14:00",
11    "sender_number": "+12025559999",
12    "alert_before": 30,
13    "participant_number": "+12025551234",
14    "participant_email": "client@example.com"
15  }'

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Event created successfully",
5    "data": {
6        "id": 123,
7        "name": "Follow-up Call",
8        "subject": "Discuss product demo with client",
9        "date": "2025-10-20",
10        "time": "14:00",
11        "sender_number": "+12025559999",
12        "alert_before": 30,
13        "alert_at": "2025-10-20 13:30:00",
14        "receiver_number": "+12025551111",
15        "participant_number": "+12025551234",
16        "participant_email": "client@example.com",
17        "user_id": 1,
18        "created_at": "2025-10-17T15:30:00.000000Z",
19        "updated_at": "2025-10-17T15:30:00.000000Z"
20    },
21    "errors": null
22}

Example in JavaScript

1async function addEvent(eventData) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/event/add', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json',
8            'Content-Type': 'application/json'
9        },
10        body: JSON.stringify(eventData)
11    });
12
13    const data = await response.json();
14
15    if (data.success) {
16        console.log('Event created:', data.data);
17        alert('Event scheduled successfully');
18        return data.data;
19    } else {
20        console.error('Error:', data.message);
21        throw new Error(data.message);
22    }
23}
24
25// Usage - Create follow-up event
26await addEvent({
27    name: 'Follow-up Call',
28    subject: 'Discuss product demo with client',
29    date: '2025-10-20',
30    time: '14:00',
31    sender_number: '+12025559999',
32    alert_before: 30,
33    participant_number: '+12025551234',
34    participant_email: 'client@example.com'
35});
36
37// Usage - Create event from form
38function scheduleEventFromForm() {
39    const form = document.getElementById('event-form');
40    const formData = new FormData(form);
41
42    const eventData = {
43        name: formData.get('name'),
44        subject: formData.get('subject'),
45        date: formData.get('date'),
46        time: formData.get('time'),
47        sender_number: formData.get('sender_number'),
48        alert_before: parseInt(formData.get('alert_before')),
49        participant_number: formData.get('participant_number'),
50        participant_email: formData.get('participant_email')
51    };
52
53    addEvent(eventData);
54}

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.

POST: /api/v1/inbox/export

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/export" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Exported successfully",
5    "data": {
6        "path": "inbox/chats_2025_10_17_15_30_45.csv",
7        "url": "https://your-cdn.com/inbox/chats_2025_10_17_15_30_45.csv"
8    },
9    "errors": null
10}

Example in JavaScript

1async function exportInbox() {
2    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/export', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json'
8        }
9    });
10
11    const data = await response.json();
12
13    if (data.success) {
14        console.log('Export successful:', data.data);
15
16        // Download the file
17        const link = document.createElement('a');
18        link.href = data.data.url;
19        link.download = 'inbox_export.csv';
20        document.body.appendChild(link);
21        link.click();
22        document.body.removeChild(link);
23
24        alert('Inbox exported successfully');
25        return data.data;
26    } else {
27        console.error('Error:', data.message);
28        throw new Error(data.message);
29    }
30}
31
32// Usage

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.

POST: /api/v1/inbox/to-folder/create

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idintegerYesID of the contact to move (must exist in contacts table)
folder_idintegerYesID of the destination folder

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/to-folder/create" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "contact_id": 567,
8    "folder_id": 5
9  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Contact moved to folder successfully.",
5    "data": {
6        "id": 567,
7        "user_id": 1,
8        "first_name": "John",
9        "last_name": "Doe",
10        "number": "+12025551234",
11        "email": "john.doe@example.com",
12        "company": "Acme Corp",
13        "folder_id": 5,
14        "created_at": "2025-09-15T10:30:00.000000Z",
15        "updated_at": "2025-10-17T15:45:00.000000Z"
16    },
17    "errors": null
18}

Example in JavaScript

1async function moveContactToFolder(contactId, folderId) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/to-folder/create', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json',
8            'Content-Type': 'application/json'
9        },
10        body: JSON.stringify({
11            contact_id: contactId,
12            folder_id: folderId
13        })
14    });
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Contact moved:', data.data);
20        alert('Contact moved to folder successfully');
21        return data.data;
22    } else {
23        console.error('Error:', data.message);
24        throw new Error(data.message);
25    }
26}
27
28// Usage
29await moveContactToFolder(567, 5);
30
31// Usage - Create folder selector dropdown
32function createFolderMoveAction(folders, contactId) {
33    const select = document.createElement('select');
34    select.innerHTML = 'Move to folder...';
35
36    folders.forEach(folder => {
37        const option = document.createElement('option');
38        option.value = folder.id;
39        option.textContent = folder.name;
40        select.appendChild(option);
41    });
42
43    select.addEventListener('change', async (e) => {
44        if (e.target.value) {
45            await moveContactToFolder(contactId, parseInt(e.target.value));
46        }
47    });
48
49    return select;
50}

Important Notes

  • Contact must exist in your contacts
  • Updates the Contact's folder_id field
  • 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.

POST: /api/v1/inbox/note/add

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Request Body Parameters

ParameterTypeRequiredDescription
contact_idintegerYesID of the contact to add note to
notestringYesNote content

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/inbox/note/add" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "contact_id": 567,
8    "note": "VIP customer - priority support. Interested in enterprise plan."
9  }'

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Note added successfully.",
5    "data": {
6        "id": 123,
7        "contact_id": 567,
8        "note": "VIP customer - priority support. Interested in enterprise plan.",
9        "created_at": "2025-10-17T15:50:00.000000Z",
10        "updated_at": "2025-10-17T15:50:00.000000Z"
11    },
12    "errors": null
13}

Example in JavaScript

1async function addInboxNote(contactId, noteText) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/note/add', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json',
8            'Content-Type': 'application/json'
9        },
10        body: JSON.stringify({
11            contact_id: contactId,
12            note: noteText
13        })
14    });
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Note added:', data.data);
20        return data.data;
21    } else {
22        console.error('Error:', data.message);
23        throw new Error(data.message);
24    }
25}
26
27// Usage
28await 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.

DELETE: /api/v1/inbox/note/delete/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesID of the note to delete

Example Request

1curl -X DELETE "https://api.texttorrent.com/api/v1/inbox/note/delete/123" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Note deleted successfully.",
5    "data": {
6        "id": 123,
7        "contact_id": 567,
8        "note": "VIP customer - priority support. Interested in enterprise plan.",
9        "created_at": "2025-10-17T15:50:00.000000Z",
10        "updated_at": "2025-10-17T15:50:00.000000Z"
11    },
12    "errors": null
13}

Example in JavaScript

1async function deleteInboxNote(noteId) {
2    if (!confirm('Delete this note?')) {
3        return;
4    }
5
6    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/note/delete/"$"{noteId}', {
7        method: 'DELETE',
8        headers: {
9            'X-API-SID': 'SID....................................',
10            'X-API-PUBLIC-KEY': 'PK.....................................',
11            'Accept': 'application/json'
12        }
13    });
14
15    const data = await response.json();
16
17    if (data.success) {
18        console.log('Note deleted successfully');
19        return true;
20    } else {
21        console.error('Error:', data.message);
22        throw new Error(data.message);
23    }
24}
25
26// Usage
27await 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
ℹ️ About Sub-Accounts: Sub-accounts are users created under your main account. They share your credits but can have restricted permissions and daily limits. All sub-account activities are linked to your parent account.

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.

GET: /api/v1/user/sub-account/list

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)
searchstringNoSearch by first name or last name
sortBystringNoField to sort by (default: id)
sortTypestringNoSort direction: asc or desc (default: desc)

Example Request - Get All Sub-Accounts

1curl -X GET "https://api.texttorrent.com/api/v1/user/sub-account/list" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - Search and Sort

1curl -X GET "https://api.texttorrent.com/api/v1/user/sub-account/list?search=john&sortBy=first_name&sortType=asc&limit=20" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Retrieved successfully",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "id": 101,
10                "first_name": "John",
11                "last_name": "Smith",
12                "username": "johnsmith",
13                "email": "john.smith@example.com",
14                "type": "sub",
15                "parent_id": 1,
16                "status": 1,
17                "role": 2,
18                "permissions": ["view_contacts", "send_messages"],
19                "daily_sms_limit": 500,
20                "admin_approval": 1,
21                "created_at": "2025-09-15T10:30:00.000000Z",
22                "updated_at": "2025-10-17T14:20:00.000000Z",
23                "role": {
24                    "id": 2,
25                    "name": "Sales Agent"
26                }
27            },
28            {
29                "id": 102,
30                "first_name": "Sarah",
31                "last_name": "Johnson",
32                "username": "sarahjohnson",
33                "email": "sarah.johnson@example.com",
34                "type": "sub",
35                "parent_id": 1,
36                "status": 1,
37                "role": 3,
38                "permissions": ["view_contacts", "send_messages", "view_analytics"],
39                "daily_sms_limit": 1000,
40                "admin_approval": 1,
41                "created_at": "2025-09-20T11:15:00.000000Z",
42                "updated_at": "2025-10-16T09:30:00.000000Z",
43                "role": {
44                    "id": 3,
45                    "name": "Marketing Manager"
46                }
47            }
48        ],
49        "first_page_url": "https://api.texttorrent.com/api/v1/user/sub-account/list?page=1",
50        "from": 1,
51        "last_page": 3,
52        "last_page_url": "https://api.texttorrent.com/api/v1/user/sub-account/list?page=3",
53        "next_page_url": "https://api.texttorrent.com/api/v1/user/sub-account/list?page=2",
54        "path": "https://api.texttorrent.com/api/v1/user/sub-account/list",
55        "per_page": 10,
56        "prev_page_url": null,
57        "to": 10,
58        "total": 25
59    },
60    "errors": null
61}

Sub-Account Fields

FieldTypeDescription
typestringAlways "sub" for sub-accounts
parent_idintegerID of the parent account (your account)
statusintegerAccount status: 1 = active, 0 = inactive
permissionsarrayList of permission strings assigned to this sub-account
daily_sms_limitintegerMaximum SMS messages this sub-account can send per day
admin_approvalintegerAdmin approval status: 1 = approved, 0 = pending

Example in JavaScript

1async function getSubAccounts(options = {}) {
2    const params = new URLSearchParams({
3        page: options.page || 1,
4        limit: options.limit || 10
5    });
6
7    if (options.search) params.append('search', options.search);
8    if (options.sortBy) params.append('sortBy', options.sortBy);
9    if (options.sortType) params.append('sortType', options.sortType);
10
11    const response = await fetch(
12        'https://api.texttorrent.com/api/v1/user/sub-account/list?"$"{params}',
13        {
14            method: 'GET',
15            headers: {
16                'X-API-SID': 'SID....................................',
17                'X-API-PUBLIC-KEY': 'PK.....................................',
18                'Accept': 'application/json'
19            }
20        }
21    );
22
23    const data = await response.json();
24
25    if (data.success) {
26        console.log('Sub-accounts:', data.data.data);
27        console.log('Total sub-accounts: "$"{data.data.total}');
28        return data.data;
29    } else {
30        console.error('Error:', data.message);
31        throw new Error(data.message);
32    }
33}
34
35// Usage - Get all sub-accounts
36const subAccounts = await getSubAccounts();
37
38// Usage - Search sub-accounts
39const searchResults = await getSubAccounts({ search: 'john' });
40
41// Usage - Sort by name ascending
42const sorted = await getSubAccounts({ sortBy: 'first_name', sortType: 'asc' });

Important Notes

  • Only returns sub-accounts where parent_id matches 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.

POST: /api/v1/user/sub-account/store

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Request Body Parameters

ParameterTypeRequiredDescription
first_namestringYesFirst name (max 20 characters)
last_namestringYesLast name (max 20 characters)
usernamestringYesUnique username for login
emailstringYesUnique email address
passwordstringYesPassword (minimum 6 characters)
confirm_passwordstringYesMust match password
roleintegerYesRole ID (must exist in user_roles table)
permissionsarrayNoArray of permission strings
sms_limitintegerNoDaily SMS limit (default: 0 = unlimited)

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/user/sub-account/store" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "first_name": "John",
8    "last_name": "Smith",
9    "username": "johnsmith",
10    "email": "john.smith@example.com",
11    "password": "SecurePass123",
12    "confirm_password": "SecurePass123",
13    "role": 2,
14    "permissions": ["view_contacts", "send_messages", "view_inbox"],
15    "sms_limit": 500
16  }'

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Sub Account Created Successfully",
5    "data": {
6        "id": 103,
7        "first_name": "John",
8        "last_name": "Smith",
9        "username": "johnsmith",
10        "email": "john.smith@example.com",
11        "type": "sub",
12        "parent_id": 1,
13        "status": 1,
14        "role": 2,
15        "permissions": ["view_contacts", "send_messages", "view_inbox"],
16        "daily_sms_limit": 500,
17        "admin_approval": 1,
18        "created_at": "2025-10-17T16:00:00.000000Z",
19        "updated_at": "2025-10-17T16:00:00.000000Z"
20    },
21    "errors": null
22}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "username": ["The username has already been taken."],
8        "email": ["The email has already been taken."],
9        "password": ["The password must be at least 6 characters."],
10        "confirm_password": ["The confirm password and password must match."],
11        "role": ["The selected role is invalid."]
12    }
13}

Example in JavaScript

1async function createSubAccount(accountData) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/user/sub-account/store', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json',
8            'Content-Type': 'application/json'
9        },
10        body: JSON.stringify(accountData)
11    });
12
13    const data = await response.json();
14
15    if (data.success) {
16        console.log('Sub-account created:', data.data);
17        alert('Sub-account created successfully! Welcome email sent.');
18        return data.data;
19    } else {
20        console.error('Error:', data.message, data.errors);
21        throw new Error(data.message);
22    }
23}
24
25// Usage
26await createSubAccount({
27    first_name: 'John',
28    last_name: 'Smith',
29    username: 'johnsmith',
30    email: 'john.smith@example.com',
31    password: 'SecurePass123',
32    confirm_password: 'SecurePass123',
33    role: 2,
34    permissions: ['view_contacts', 'send_messages', 'view_inbox'],
35    sms_limit: 500
36});

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.

GET: /api/v1/user/sub-account/show/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesSub-account user ID

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/user/sub-account/show/101" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Sub Account Retrieved Successfully",
5    "data": {
6        "id": 101,
7        "first_name": "John",
8        "last_name": "Smith",
9        "username": "johnsmith",
10        "email": "john.smith@example.com",
11        "type": "sub",
12        "parent_id": 1,
13        "status": 1,
14        "role": 2,
15        "permissions": ["view_contacts", "send_messages", "view_inbox"],
16        "daily_sms_limit": 500,
17        "admin_approval": 1,
18        "created_at": "2025-09-15T10:30:00.000000Z",
19        "updated_at": "2025-10-17T14:20:00.000000Z"
20    },
21    "errors": null
22}

Error Response - Not Found (404 Not Found)

1{
2    "code": 404,
3    "success": false,
4    "message": "Sub Account Not Found",
5    "data": [],
6    "errors": null
7}

Example in JavaScript

1async function getSubAccountDetails(subAccountId) {
2    const response = await fetch(
3        'https://api.texttorrent.com/api/v1/user/sub-account/show/"$"{subAccountId}',
4        {
5            method: 'GET',
6            headers: {
7                'X-API-SID': 'SID....................................',
8                'X-API-PUBLIC-KEY': 'PK.....................................',
9                'Accept': 'application/json'
10            }
11        }
12    );
13
14    const data = await response.json();
15
16    if (data.success) {
17        console.log('Sub-account details:', data.data);
18        return data.data;
19    } else {
20        console.error('Error:', data.message);
21        throw new Error(data.message);
22    }
23}
24
25// Usage
26const subAccount = await getSubAccountDetails(101);

Important Notes

  • Only returns sub-accounts that belong to your account (parent_id check)
  • 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.

PUT: /api/v1/user/sub-account/update/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesSub-account user ID to update

Request Body Parameters

ParameterTypeRequiredDescription
first_namestringYesFirst name (max 20 characters)
last_namestringYesLast name (max 20 characters)
usernamestringYesUsername (unique, except current user)
emailstringYesEmail address (unique, except current user)
passwordstringNoNew password (minimum 6 characters)
confirm_passwordstringNo*Required if password is provided, must match password
roleintegerYesRole ID (must exist in user_roles table)
permissionsarrayNoArray of permission strings
sms_limitintegerNoDaily SMS limit (default: 0 = unlimited)

Example Request - Update Without Password

1curl -X PUT "https://api.texttorrent.com/api/v1/user/sub-account/update/101" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "first_name": "John",
8    "last_name": "Smith",
9    "username": "johnsmith",
10    "email": "john.smith@example.com",
11    "role": 2,
12    "permissions": ["view_contacts", "send_messages", "view_inbox", "view_analytics"],
13    "sms_limit": 1000
14  }'

Example Request - Update With Password

1curl -X PUT "https://api.texttorrent.com/api/v1/user/sub-account/update/101" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "first_name": "John",
8    "last_name": "Smith",
9    "username": "johnsmith",
10    "email": "john.smith@example.com",
11    "password": "NewSecurePass456",
12    "confirm_password": "NewSecurePass456",
13    "role": 2,
14    "permissions": ["view_contacts", "send_messages", "view_inbox"],
15    "sms_limit": 1000
16  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Sub Account Updated Successfully",
5    "data": {
6        "id": 101,
7        "first_name": "John",
8        "last_name": "Smith",
9        "username": "johnsmith",
10        "email": "john.smith@example.com",
11        "type": "sub",
12        "parent_id": 1,
13        "status": 1,
14        "role": 2,
15        "permissions": ["view_contacts", "send_messages", "view_inbox", "view_analytics"],
16        "daily_sms_limit": 1000,
17        "admin_approval": 1,
18        "created_at": "2025-09-15T10:30:00.000000Z",
19        "updated_at": "2025-10-17T16:15:00.000000Z"
20    },
21    "errors": null
22}

Error Response - Not Found (404 Not Found)

1{
2    "code": 404,
3    "success": false,
4    "message": "Sub Account Not Found",
5    "data": [],
6    "errors": null
7}

Example in JavaScript

1async function updateSubAccount(subAccountId, accountData) {
2    const response = await fetch(
3        'https://api.texttorrent.com/api/v1/user/sub-account/update/"$"{subAccountId}',
4        {
5            method: 'PUT',
6            headers: {
7                'X-API-SID': 'SID....................................',
8                'X-API-PUBLIC-KEY': 'PK.....................................',
9                'Accept': 'application/json',
10                'Content-Type': 'application/json'
11            },
12            body: JSON.stringify(accountData)
13        }
14    );
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Sub-account updated:', data.data);
20        alert('Sub-account updated successfully!');
21        return data.data;
22    } else {
23        console.error('Error:', data.message, data.errors);
24        throw new Error(data.message);
25    }
26}
27
28// Usage - Update without password
29await updateSubAccount(101, {
30    first_name: 'John',
31    last_name: 'Smith',
32    username: 'johnsmith',
33    email: 'john.smith@example.com',
34    role: 2,
35    permissions: ['view_contacts', 'send_messages', 'view_inbox', 'view_analytics'],
36    sms_limit: 1000
37});
38
39// Usage - Update with password
40await updateSubAccount(101, {
41    first_name: 'John',
42    last_name: 'Smith',
43    username: 'johnsmith',
44    email: 'john.smith@example.com',
45    password: 'NewSecurePass456',
46    confirm_password: 'NewSecurePass456',
47    role: 2,
48    permissions: ['view_contacts', 'send_messages'],
49    sms_limit: 1000
50});

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.

DELETE: /api/v1/user/sub-account/delete/{id}
⚠️ Warning: Deleting a sub-account is permanent and cannot be undone. Make sure you want to remove this user before proceeding.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesSub-account user ID to delete

Example Request

1curl -X DELETE "https://api.texttorrent.com/api/v1/user/sub-account/delete/101" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Sub Account Deleted Successfully",
5    "data": [],
6    "errors": null
7}

Error Response - Not Found (404 Not Found)

1{
2    "code": 404,
3    "success": false,
4    "message": "Sub Account Not Found",
5    "data": [],
6    "errors": null
7}

Example in JavaScript

1async function deleteSubAccount(subAccountId) {
2    if (!confirm('Delete this sub-account? This action cannot be undone.')) {
3        return;
4    }
5
6    const response = await fetch(
7        'https://api.texttorrent.com/api/v1/user/sub-account/delete/"$"{subAccountId}',
8        {
9            method: 'DELETE',
10            headers: {
11                'X-API-SID': 'SID....................................',
12                'X-API-PUBLIC-KEY': 'PK.....................................',
13                'Accept': 'application/json'
14            }
15        }
16    );
17
18    const data = await response.json();
19
20    if (data.success) {
21        console.log('Sub-account deleted successfully');
22        alert('Sub-account has been deleted');
23        return true;
24    } else {
25        console.error('Error:', data.message);
26        alert('Failed to delete sub-account');
27        return false;
28    }
29}
30
31// Usage
32await 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.

PATCH: /api/v1/user/sub-account/status/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesSub-account user ID

Request Body Parameters

ParameterTypeRequiredDescription
statusintegerYesNew status: 1 = active, 0 = inactive

Example Request - Disable Sub-Account

1curl -X PATCH "https://api.texttorrent.com/api/v1/user/sub-account/status/101" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "status": 0
8  }'

Example Request - Enable Sub-Account

1curl -X PATCH "https://api.texttorrent.com/api/v1/user/sub-account/status/101" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "status": 1
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Sub Account Status Updated Successfully",
5    "data": {
6        "id": 101,
7        "first_name": "John",
8        "last_name": "Smith",
9        "username": "johnsmith",
10        "email": "john.smith@example.com",
11        "type": "sub",
12        "parent_id": 1,
13        "status": 0,
14        "role": 2,
15        "permissions": ["view_contacts", "send_messages"],
16        "daily_sms_limit": 500,
17        "admin_approval": 1,
18        "created_at": "2025-09-15T10:30:00.000000Z",
19        "updated_at": "2025-10-17T16:30:00.000000Z"
20    },
21    "errors": null
22}

Error Response - Not Found (404 Not Found)

1{
2    "code": 404,
3    "success": false,
4    "message": "Sub Account Not Found",
5    "data": [],
6    "errors": null
7}

Example in JavaScript

1async function changeSubAccountStatus(subAccountId, status) {
2    const statusText = status === 1 ? 'enable' : 'disable';
3
4    if (!confirm('Are you sure you want to "$"{statusText} this sub-account?')) {
5        return;
6    }
7
8    const response = await fetch(
9        'https://api.texttorrent.com/api/v1/user/sub-account/status/"$"{subAccountId}',
10        {
11            method: 'PATCH',
12            headers: {
13                'X-API-SID': 'SID....................................',
14                'X-API-PUBLIC-KEY': 'PK.....................................',
15                'Accept': 'application/json',
16                'Content-Type': 'application/json'
17            },
18            body: JSON.stringify({ status })
19        }
20    );
21
22    const data = await response.json();
23
24    if (data.success) {
25        console.log('Status updated:', data.data);
26        alert('Sub-account "$"{statusText}d successfully');
27        return data.data;
28    } else {
29        console.error('Error:', data.message);
30        throw new Error(data.message);
31    }
32}
33
34// Usage - Disable sub-account
35await changeSubAccountStatus(101, 0);
36
37// Usage - Enable sub-account
38await changeSubAccountStatus(101, 1);
39
40// Usage - Toggle status
41async function toggleSubAccountStatus(subAccount) {
42    const newStatus = subAccount.status === 1 ? 0 : 1;
43    return await changeSubAccountStatus(subAccount.id, newStatus);
44}

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.

POST: /api/v1/user/sub-account/login-as-sub-user/{id}
ℹ️ About Login as Sub-User: This feature generates a temporary access token for the sub-account, allowing you to impersonate them without their password. Use responsibly and only for support purposes.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesSub-account user ID to log in as

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/user/sub-account/login-as-sub-user/101" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Logged in as Sub Account Successfully",
5    "data": {
6        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
7    },
8    "errors": null
9}

Error Response - Not Found (404 Not Found)

1{
2    "code": 404,
3    "success": false,
4    "message": "Sub Account Not Found",
5    "data": [],
6    "errors": null
7}

Example in JavaScript

1async function loginAsSubUser(subAccountId) {
2    const response = await fetch(
3        'https://api.texttorrent.com/api/v1/user/sub-account/login-as-sub-user/"$"{subAccountId}',
4        {
5            method: 'POST',
6            headers: {
7                'X-API-SID': 'SID....................................',
8                'X-API-PUBLIC-KEY': 'PK.....................................',
9                'Accept': 'application/json'
10            }
11        }
12    );
13
14    const data = await response.json();
15
16    if (data.success) {
17        console.log('Sub-user token generated:', data.data.token);
18
19        // Store the token
20        localStorage.setItem('sub_user_token', data.data.token);
21
22        // Redirect to dashboard as sub-user
23        window.location.href = '/dashboard?as_sub_user=true';
24
25        return data.data.token;
26    } else {
27        console.error('Error:', data.message);
28        throw new Error(data.message);
29    }
30}
31
32// Usage
33await loginAsSubUser(101);
34
35// Usage - With confirmation
36async function impersonateSubUser(subAccountId, subAccountName) {
37    if (confirm('Log in as "$"{subAccountName}? You'll be viewing the system from their perspective.')) {
38        const token = await loginAsSubUser(subAccountId);
39        console.log('Now viewing as sub-user');
40    }
41}

Example in PHP

1<?php
2$subAccountId = 101;
3
4$ch = curl_init();
5curl_setopt($ch, CURLOPT_URL, "https://api.texttorrent.com/api/v1/user/sub-account/login-as-sub-user/{$subAccountId}");
6curl_setopt($ch, CURLOPT_POST, true);
7curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
8curl_setopt($ch, CURLOPT_HTTPHEADER, [
9    'X-API-SID: SID....................................',
10    'X-API-PUBLIC-KEY: PK.....................................',
11    'Accept: application/json'
12]);
13
14$response = curl_exec($ch);
15curl_close($ch);
16
17$data = json_decode($response, true);
18
19if ($data['success']) {
20    $token = $data['data']['token'];
21
22    // Store token in session for sub-user access
23    $_SESSION['sub_user_token'] = $token;
24    $_SESSION['is_impersonating'] = true;
25
26    // Redirect to dashboard
27    header('Location: /dashboard');
28    exit;
29} else {
30    echo "Error: " . $data['message'];
31}
32?>

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

1class SubAccountManager {
2    constructor(apiSid, apiKey) {
3        this.apiSid = apiSid;
4        this.apiKey = apiKey;
5        this.baseUrl = 'https://api.texttorrent.com/api/v1/user/sub-account';
6    }
7
8    async list(options = {}) {
9        const params = new URLSearchParams(options);
10        return await this.apiCall('GET', '/list?"$"{params}
11        ');
12    }
13
14    async create(accountData) {
15        return await this.apiCall('POST', '/store', accountData);
16    }
17
18    async get(id) {
19        return await this.apiCall('GET', '/show/"$"{id}');
20    }
21
22    async update(id, accountData) {
23        return await this.apiCall('PUT', '/update/"$"{id}', accountData);
24    }
25
26    async delete(id) {
27        return await this.apiCall('DELETE', '/delete/"$"{id}');
28    }
29
30    async changeStatus(id, status) {
31        return await this.apiCall('PATCH', '/status/"$"{id}', { status });
32    }
33
34    async loginAs(id) {
35        return await this.apiCall('POST', '/login-as-sub-user/"$"{id}');
36    }
37
38    async apiCall(method, endpoint, data = null) {
39        const headers = {
40            'X-API-SID': this.apiSid,
41            'X-API-PUBLIC-KEY': this.apiKey,
42            'Accept': 'application/json'
43        };
44
45        if (data && method !== 'GET') {
46            headers['Content-Type'] = 'application/json';
47        }
48
49        const options = {
50            method,
51            headers,
52            body: data && method !== 'GET' ? JSON.stringify(data) : null
53        };
54
55        const response = await fetch(this.baseUrl + endpoint, options);
56        const result = await response.json();
57
58        if (!result.success) {
59            throw new Error(result.message);
60        }
61
62        return result;
63    }
64}
65
66// Usage
67const subAccountMgr = new SubAccountManager('SID....................................', 'PK.....................................');
68
69// List sub-accounts
70const accounts = await subAccountMgr.list({ search: 'john' });
71
72// Create new sub-account
73const newAccount = await subAccountMgr.create({
74    first_name: 'Jane',
75    last_name: 'Doe',
76    username: 'janedoe',
77    email: 'jane.doe@example.com',
78    password: 'SecurePass123',
79    confirm_password: 'SecurePass123',
80    role: 2,
81    permissions: ['view_contacts', 'send_messages'],
82    sms_limit: 500
83});
84
85// Update sub-account
86await subAccountMgr.update(101, {
87    first_name: 'John',
88    last_name: 'Smith',
89    username: 'johnsmith',
90    email: 'john.smith@example.com',
91    role: 2,
92    permissions: ['view_contacts', 'send_messages', 'view_analytics'],
93    sms_limit: 1000
94});
95
96// Disable sub-account
97await subAccountMgr.changeStatus(101, 0);
98
99// Login as sub-user
100const 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

RolePermissionsDaily Limit
Sales Agentview_contacts, send_messages, view_inbox500
Marketing Managerview_contacts, send_messages, view_inbox, view_analytics, create_campaigns1000
Support Repview_contacts, send_messages, view_inbox, manage_notes300
AdminAll permissions0 (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
ℹ️ About Credits: Bulk messaging consumes credits based on the number of segments per message and total contacts. SMS is charged per segment (160 GSM-7 or 70 UCS-2 characters), while MMS has a fixed credit cost. The API automatically calculates and deducts required credits.
⚠️ Trial Accounts: This feature is not available during the trial period. Trial accounts will receive an error when attempting to create campaigns.

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.

GET: /api/v1/campaigning/bulk

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
search_numberstringNoSearch/filter contact lists by name
search_contactstringNoSearch/filter active numbers

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/bulk" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - With Search

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/bulk?search_number=customers&search_contact=+1" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Data fetched successfully",
5    "data": {
6        "contactLists": [
7            {
8                "id": 45,
9                "name": "VIP Customers",
10                "bookmarked": 1,
11                "total_contacts": 1250
12            },
13            {
14                "id": 42,
15                "name": "Newsletter Subscribers",
16                "bookmarked": 0,
17                "total_contacts": 3420
18            }
19        ],
20        "activeNumbers": [
21            {
22                "id": 12,
23                "number": "+15551234567",
24                "friendly_name": "Main Business Line",
25                "capabilities": {"sms": true, "mms": true, "voice": true},
26                "status": 1
27            },
28            {
29                "id": 15,
30                "number": "+15559876543",
31                "friendly_name": "Marketing Line",
32                "capabilities": {"sms": true, "mms": true, "voice": false},
33                "status": 1
34            }
35        ],
36        "subUsers": [
37            {
38                "id": 1,
39                "name": "John Smith (2)",
40                "numbers": ["+15551234567", "+15559876543"]
41            },
42            {
43                "id": 101,
44                "name": "Sarah Johnson (1)",
45                "numbers": ["+15556543210"]
46            }
47        ]
48    },
49    "errors": null
50}

Response Fields

FieldTypeDescription
contactListsarrayAvailable contact lists with total contact counts
activeNumbersarrayActive phone numbers available for sending (status=1)
subUsersarraySub-accounts and their associated active phone numbers
total_contactsintegerNumber of contacts in each list
capabilitiesobjectNumber capabilities (sms, mms, voice)

Example in JavaScript

1async function getCampaignData(searchOptions = {}) {
2    const params = new URLSearchParams();
3
4    if (searchOptions.contactList) {
5        params.append('search_number', searchOptions.contactList);
6    }
7    if (searchOptions.phoneNumber) {
8        params.append('search_contact', searchOptions.phoneNumber);
9    }
10
11    const queryString = params.toString() ? '?"$"{params} : '';
12
13    const response = await fetch(
14        'https://api.texttorrent.com/api/v1/campaigning/bulk"$"{queryString}',
15        {
16            method: 'GET',
17            headers: {
18                'X-API-SID': 'SID....................................',
19                'X-API-PUBLIC-KEY': 'PK.....................................',
20                'Accept': 'application/json'
21            }
22        }
23    );
24
25    const data = await response.json();
26
27    if (data.success) {
28        console.log('Contact Lists:', data.data.contactLists);
29        console.log('Active Numbers:', data.data.activeNumbers);
30        console.log('Sub Users:', data.data.subUsers);
31        return data.data;
32    } else {
33        console.error('Error:', data.message);
34        throw new Error(data.message);
35    }
36}
37
38// Usage - Get all data
39const campaignData = await getCampaignData();
40
41// Usage - Search contact lists
42const filtered = await getCampaignData({
43    contactList: 'customers',
44    phoneNumber: '+1'
45});

Important Notes

  • Only returns contact lists where user_id matches 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.

POST: /api/v1/campaigning/bulk
⚠️ Credit Requirements: You must have sufficient credits before creating a campaign. The system will automatically calculate required credits and check your balance. If auto-recharge is enabled, it will trigger when credits fall below 1000.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: multipart/form-data

Request Body Parameters

ParameterTypeRequiredDescription
campaign_namestringYesCampaign name (max 255 characters)
contact_list_idintegerYesID of contact list to send to (must exist)
template_idintegerYesID of message template (must exist)
sms_typestringYesMessage type: sms or mms
sms_bodystringYesMessage content (max 1600 characters)
numbersarrayYesArray of phone numbers to send from (max 15 chars each)
filefileNo*Media file for MMS (required if sms_type=mms)
appended_messagestringNoAdditional text appended to message (max 1600 chars)
number_poolbooleanNoEnable number pool distribution
batch_processbooleanNoEnable batch processing
opt_out_linkbooleanNoInclude opt-out link in message
round_robin_campaignbooleanNoEnable round-robin number rotation
batch_sizeintegerNoNumber of messages per batch (if batch_process enabled)
batch_frequencyintegerNoMinutes between batches (if batch_process enabled)
selected_datedateNoSchedule date (YYYY-MM-DD format)
selected_timestringNoSchedule time (HH:MM format)
phone_numbersstringNoComma-separated phone numbers
selected_usersarrayNoArray of sub-user IDs to use their numbers

Example Request - Simple SMS Campaign

1curl -X POST "https://api.texttorrent.com/api/v1/campaigning/bulk" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -F "campaign_name=Spring Sale Announcement" 
6  -F "contact_list_id=45" 
7  -F "template_id=12" 
8  -F "sms_type=sms" 
9  -F "sms_body=Hi {first_name}, don't miss our Spring Sale! Get 20% off all items. Shop now!" 
10  -F "numbers[]=+15551234567" 
11  -F "numbers[]=+15559876543" 
12  -F "opt_out_link=true"

Example Request - MMS Campaign with File

1curl -X POST "https://api.texttorrent.com/api/v1/campaigning/bulk" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -F "campaign_name=Product Launch" 
6  -F "contact_list_id=45" 
7  -F "template_id=12" 
8  -F "sms_type=mms" 
9  -F "sms_body=Check out our new product!" 
10  -F "file=@/path/to/product-image.jpg" 
11  -F "numbers[]=+15551234567" 
12  -F "opt_out_link=true"

Example Request - Scheduled Campaign with Batch Processing

1curl -X POST "https://api.texttorrent.com/api/v1/campaigning/bulk" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -F "campaign_name=Holiday Greetings" 
6  -F "contact_list_id=45" 
7  -F "template_id=12" 
8  -F "sms_type=sms" 
9  -F "sms_body=Happy Holidays from our team!" 
10  -F "numbers[]=+15551234567" 
11  -F "batch_process=true" 
12  -F "batch_size=100" 
13  -F "batch_frequency=30" 
14  -F "selected_date=2025-12-25" 
15  -F "selected_time=09:00"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Campaign created successfully",
5    "data": {
6        "campaign_id": 567,
7        "total_credit": 1875,
8        "total_contacts": 1250,
9        "total_segments": 1
10    },
11    "errors": null
12}

Error Response - Insufficient Credits (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "You do not have enough credits to perform this action.",
5    "data": null,
6    "errors": null
7}

Error Response - Trial Account (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "You cannot use this feature during the trial period.",
5    "data": null,
6    "errors": null
7}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "campaign_name": ["The campaign name field is required."],
8        "contact_list_id": ["The selected contact list id is invalid."],
9        "template_id": ["The selected template id is invalid."],
10        "sms_type": ["The sms type must be either sms or mms."],
11        "file": ["The file field is required when sms type is mms."],
12        "numbers": ["The numbers field is required."]
13    }
14}

Example in JavaScript

1async function createCampaign(campaignData, mediaFile = null) {
2    const formData = new FormData();
3
4    // Required fields
5    formData.append('campaign_name', campaignData.campaign_name);
6    formData.append('contact_list_id', campaignData.contact_list_id);
7    formData.append('template_id', campaignData.template_id);
8    formData.append('sms_type', campaignData.sms_type);
9    formData.append('sms_body', campaignData.sms_body);
10
11    // Numbers array
12    campaignData.numbers.forEach(number => {
13        formData.append('numbers[]', number);
14    });
15
16    // Optional fields
17    if (campaignData.appended_message) {
18        formData.append('appended_message', campaignData.appended_message);
19    }
20    if (campaignData.number_pool) {
21        formData.append('number_pool', campaignData.number_pool);
22    }
23    if (campaignData.batch_process) {
24        formData.append('batch_process', campaignData.batch_process);
25        formData.append('batch_size', campaignData.batch_size);
26        formData.append('batch_frequency', campaignData.batch_frequency);
27    }
28    if (campaignData.opt_out_link) {
29        formData.append('opt_out_link', campaignData.opt_out_link);
30    }
31    if (campaignData.round_robin_campaign) {
32        formData.append('round_robin_campaign', campaignData.round_robin_campaign);
33    }
34    if (campaignData.selected_date) {
35        formData.append('selected_date', campaignData.selected_date);
36        formData.append('selected_time', campaignData.selected_time);
37    }
38
39    // MMS file
40    if (mediaFile && campaignData.sms_type === 'mms') {
41        formData.append('file', mediaFile);
42    }
43
44    const response = await fetch('https://api.texttorrent.com/api/v1/campaigning/bulk', {
45        method: 'POST',
46        headers: {
47            'X-API-SID': 'SID....................................',
48            'X-API-PUBLIC-KEY': 'PK.....................................',
49            'Accept': 'application/json'
50        },
51        body: formData
52    });
53
54    const data = await response.json();
55
56    if (data.success) {
57        console.log('Campaign created:', data.data);
58        alert('Campaign created! ID: "$"{data.data.campaign_id}, Credits: "$"{data.data.total_credit}');
59        return data.data;
60    } else {
61        console.error('Error:', data.message, data.errors);
62        throw new Error(data.message);
63    }
64}
65
66// Usage - Simple SMS campaign
67await createCampaign({
68    campaign_name: 'Spring Sale Announcement',
69    contact_list_id: 45,
70    template_id: 12,
71    sms_type: 'sms',
72    sms_body: 'Hi {first_name}, don't miss our Spring Sale! Get 20% off all items.',
73    numbers: ['+15551234567', '+15559876543'],
74    opt_out_link: true
75});
76
77// Usage - Scheduled campaign
78await createCampaign({
79    campaign_name: 'Holiday Greetings',
80    contact_list_id: 45,
81    template_id: 12,
82    sms_type: 'sms',
83    sms_body: 'Happy Holidays from our team!',
84    numbers: ['+15551234567'],
85    batch_process: true,
86    batch_size: 100,
87    batch_frequency: 30,
88    selected_date: '2025-12-25',
89    selected_time: '09:00'
90});
91
92// Usage - MMS campaign with file
93const fileInput = document.getElementById('mms-file');
94await createCampaign({
95    campaign_name: 'Product Launch',
96    contact_list_id: 45,
97    template_id: 12,
98    sms_type: 'mms',
99    sms_body: 'Check out our new product!',
100    numbers: ['+15551234567']
101}, fileInput.files[0]);

Example in PHP

1<?php
2$ch = curl_init();
3
4$campaignData = [
5    'campaign_name' => 'Spring Sale Announcement',
6    'contact_list_id' => 45,
7    'template_id' => 12,
8    'sms_type' => 'sms',
9    'sms_body' => 'Hi {first_name}, don't miss our Spring Sale! Get 20% off all items.',
10    'numbers' => ['+15551234567', '+15559876543'],
11    'opt_out_link' => true
12];
13
14// Convert numbers array to proper format
15$postFields = $campaignData;
16$postFields['numbers'] = $campaignData['numbers'];
17
18curl_setopt($ch, CURLOPT_URL, 'https://api.texttorrent.com/api/v1/campaigning/bulk');
19curl_setopt($ch, CURLOPT_POST, true);
20curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postFields));
21curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
22curl_setopt($ch, CURLOPT_HTTPHEADER, [
23    'X-API-SID: SID....................................',
24    'X-API-PUBLIC-KEY: PK.....................................',
25    'Accept: application/json'
26]);
27
28$response = curl_exec($ch);
29curl_close($ch);
30
31$data = json_decode($response, true);
32
33if ($data['success']) {
34    echo "Campaign created! ID: {$data['data']['campaign_id']}
35";
36    echo "Credits consumed: {$data['data']['total_credit']}
37";
38    echo "Total contacts: {$data['data']['total_contacts']}
39";
40} else {
41    echo "Error: {$data['message']}
42";
43}
44?>

Understanding Message Segments

EncodingSingle SegmentMulti-SegmentCredit per Segment
GSM-7 (Standard)Up to 160 characters153 characters each1 credit
UCS-2 (Unicode)Up to 70 characters67 characters each1 credit
MMSN/AN/AFixed 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 option here 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 and last name here placeholders
  • Spin Text: Use variations like hi ; hello here 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

1class CampaignManager {
2    constructor(apiSid, apiKey) {
3        this.apiSid = apiSid;
4        this.apiKey = apiKey;
5        this.baseUrl = 'https://api.texttorrent.com/api/v1/campaigning';
6    }
7
8    async getCampaignData(search = {}) {
9        const params = new URLSearchParams(search);
10        const response = await fetch(
11            '"$"{this.baseUrl}/bulk?"$"{params}"',
12            {
13                method: 'GET',
14                headers: this.getHeaders()
15            }
16        );
17        return await this.handleResponse(response);
18    }
19
20    async createCampaign(campaignData, file = null) {
21        const formData = new FormData();
22
23        // Add all campaign data
24        Object.keys(campaignData).forEach(key => {
25            if (Array.isArray(campaignData[key])) {
26                campaignData[key].forEach(value => {
27                    formData.append('"$"{key}[]', value);
28                });
29            } else if (campaignData[key] !== null && campaignData[key] !== undefined) {
30                formData.append(key, campaignData[key]);
31            }
32        });
33
34        // Add file for MMS
35        if (file) {
36            formData.append('file', file);
37        }
38
39        const response = await fetch('"$"{this.baseUrl}/bulk', {
40            method: 'POST',
41            headers: {
42                'X-API-SID': this.apiSid,
43                'X-API-PUBLIC-KEY': this.apiKey,
44                'Accept': 'application/json'
45            },
46            body: formData
47        });
48
49        return await this.handleResponse(response);
50    }
51
52    getHeaders() {
53        return {
54            'X-API-SID': this.apiSid,
55            'X-API-PUBLIC-KEY': this.apiKey,
56            'Accept': 'application/json'
57        };
58    }
59
60    async handleResponse(response) {
61        const data = await response.json();
62        if (!data.success) {
63            throw new Error(data.message);
64        }
65        return data.data;
66    }
67}
68
69// Usage
70const manager = new CampaignManager('SID....................................', 'PK.....................................');
71
72// Get campaign data
73const data = await manager.getCampaignData({ search_number: 'customers' });
74console.log('Available contact lists:', data.contactLists);
75
76// Create immediate SMS campaign
77const campaign = await manager.createCampaign({
78    campaign_name: 'Flash Sale Alert',
79    contact_list_id: 45,
80    template_id: 12,
81    sms_type: 'sms',
82    sms_body: 'Hi {first_name}! Flash sale ends in 2 hours. Shop now!',
83    numbers: ['+15551234567', '+15559876543'],
84    opt_out_link: true,
85    round_robin_campaign: true
86});
87
88console.log('Campaign "$"{campaign.campaign_id} created!');
89console.log('Credits consumed: "$"{campaign.total_credit}');
90console.log('Messages queued: "$"{campaign.total_contacts}');
91
92// Create scheduled campaign with batches
93const scheduled = await manager.createCampaign({
94    campaign_name: 'Weekly Newsletter',
95    contact_list_id: 45,
96    template_id: 12,
97    sms_type: 'sms',
98    sms_body: 'This week's top stories: {spin_text}',
99    numbers: ['+15551234567'],
100    opt_out_link: true,
101    batch_process: true,
102    batch_size: 100,
103    batch_frequency: 30,
104    selected_date: '2025-10-20',
105    selected_time: '09:00'
106});
107
108console.log('Scheduled campaign created:', scheduled);

Credit Estimation Formula

1function estimateCampaignCredits(messageBody, totalContacts, smsType = 'sms') {
2    if (smsType === 'mms') {
3        // MMS has fixed cost + SMS cost if body included
4        const mmsCredit = 3; // Example MMS cost
5        const smsSegments = messageBody ? calculateSegments(messageBody) : 0;
6        return (mmsCredit + smsSegments) * totalContacts;
7    }
8
9    const segments = calculateSegments(messageBody);
10    return segments * totalContacts;
11}
12
13function calculateSegments(message) {
14    const isGSM7 = /^[-]*$/u.test(message);
15    const length = message.length;
16
17    if (isGSM7) {
18        return length <= 160 ? 1 : Math.ceil(length / 153);
19    } else {
20        return length <= 70 ? 1 : Math.ceil(length / 67);
21    }
22}
23
24// Usage
25const message = 'Hi {first_name}, don't miss our Spring Sale!';
26const contacts = 1250;
27const credits = estimateCampaignCredits(message, contacts);
28console.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 SizeBatch ProcessingBatch SizeFrequencyNumber Pool
< 100 contactsNot neededN/AN/A1-2 numbers
100 - 500 contactsOptional50-10015-30 min2-3 numbers
500 - 2000 contactsRecommended100-20020-30 min3-5 numbers
2000+ contactsRequired200-50030-60 min5+ 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
ℹ️ About Campaign Status: Campaigns can have different statuses:Scheduled (waiting for scheduled time),Processing (currently sending),Completed (all messages sent),Paused (temporarily stopped), andFailed (delivery issues).

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.

GET: /api/v1/campaigning/analytic

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)
searchstringNoSearch campaigns by name
statusstringNoFilter by status: Scheduled, Processing, Completed, Paused, Failed

Example Request - Get All Campaigns

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - Filter by Status

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic?status=Completed&limit=20" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - Search Campaigns

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic?search=Spring Sale" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Campaigns fetched successfully",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "id": 567,
10                "campaign_name": "Spring Sale Announcement",
11                "status": "Completed",
12                "contact_list_id": 45,
13                "contact_list_name": "VIP Customers",
14                "first_name": "John",
15                "last_name": "Smith",
16                "email": "john.smith@example.com",
17                "phone": "+15551234567",
18                "created_at": "2025-10-15T14:30:00.000000Z"
19            },
20            {
21                "id": 568,
22                "campaign_name": "Holiday Greetings",
23                "status": "Scheduled",
24                "contact_list_id": 42,
25                "contact_list_name": "Newsletter Subscribers",
26                "first_name": "John",
27                "last_name": "Smith",
28                "email": "john.smith@example.com",
29                "phone": "+15551234567",
30                "created_at": "2025-10-16T09:15:00.000000Z"
31            },
32            {
33                "id": 569,
34                "campaign_name": "Product Launch",
35                "status": "Processing",
36                "contact_list_id": 45,
37                "contact_list_name": "VIP Customers",
38                "first_name": "John",
39                "last_name": "Smith",
40                "email": "john.smith@example.com",
41                "phone": "+15551234567",
42                "created_at": "2025-10-17T10:00:00.000000Z"
43            }
44        ],
45        "first_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic?page=1",
46        "from": 1,
47        "last_page": 5,
48        "last_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic?page=5",
49        "next_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic?page=2",
50        "path": "https://api.texttorrent.com/api/v1/campaigning/analytic",
51        "per_page": 10,
52        "prev_page_url": null,
53        "to": 10,
54        "total": 47
55    },
56    "errors": null
57}

Campaign Status Values

StatusDescription
ScheduledCampaign is waiting for scheduled delivery time
ProcessingCampaign is currently sending messages
CompletedAll messages have been sent
PausedCampaign has been temporarily paused
FailedCampaign encountered errors during sending

Example in JavaScript

1async function listCampaigns(options = {}) {
2    const params = new URLSearchParams({
3        page: options.page || 1,
4        limit: options.limit || 10
5    });
6
7    if (options.search) params.append('search', options.search);
8    if (options.status) params.append('status', options.status);
9
10    const response = await fetch(
11        'https://api.texttorrent.com/api/v1/campaigning/analytic?"$"{params}',
12        {
13            method: 'GET',
14            headers: {
15                'X-API-SID': 'SID....................................',
16                'X-API-PUBLIC-KEY': 'PK.....................................',
17                'Accept': 'application/json'
18            }
19        }
20    );
21
22    const data = await response.json();
23
24    if (data.success) {
25        console.log('Campaigns:', data.data.data);
26        console.log('Total campaigns: "$"{data.data.total}');
27        return data.data;
28    } else {
29        console.error('Error:', data.message);
30        throw new Error(data.message);
31    }
32}
33
34// Usage - Get all campaigns
35const campaigns = await listCampaigns();
36
37// Usage - Filter by status
38const completed = await listCampaigns({ status: 'Completed' });
39
40// Usage - Search campaigns
41const searchResults = await listCampaigns({ search: 'Spring Sale' });
42
43// Usage - Pagination
44const page2 = await listCampaigns({ page: 2, limit: 20 });

Important Notes

  • Only returns campaigns where user_id matches 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.

GET: /api/v1/campaigning/analytic/count

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/count" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Campaigns count fetched successfully",
5    "data": {
6        "total": 47,
7        "scheduled": 5,
8        "processing": 2,
9        "completed": 38,
10        "paused": 1,
11        "failed": 1
12    },
13    "errors": null
14}

Response Fields

FieldTypeDescription
totalintegerTotal number of campaigns
scheduledintegerCampaigns waiting for scheduled time
processingintegerCampaigns currently sending messages
completedintegerCampaigns that finished sending
pausedintegerCampaigns that are paused
failedintegerCampaigns that encountered errors

Example in JavaScript

1async function getCampaignCounts() {
2    const response = await fetch(
3        'https://api.texttorrent.com/api/v1/campaigning/analytic/count',
4        {
5            method: 'GET',
6            headers: {
7                'X-API-SID': 'SID....................................',
8                'X-API-PUBLIC-KEY': 'PK.....................................',
9                'Accept': 'application/json'
10            }
11        }
12    );
13
14    const data = await response.json();
15
16    if (data.success) {
17        console.log('Campaign Statistics:');
18        console.log('Total: "$"{data.data.total}');
19        console.log('Scheduled: "$"{data.data.scheduled}');
20        console.log('Processing: "$"{data.data.processing}');
21        console.log('Completed: "$"{data.data.completed}');
22        console.log('Paused: "$"{data.data.paused}');
23        console.log('Failed: "$"{data.data.failed}');
24        return data.data;
25    } else {
26        console.error('Error:', data.message);
27        throw new Error(data.message);
28    }
29}
30
31// Usage
32const stats = await getCampaignCounts();
33
34// Display as percentages
35const successRate = ((stats.completed / stats.total) * 100).toFixed(2);
36console.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.

GET: /api/v1/campaigning/analytic/details/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesCampaign ID

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Campaigns fetched successfully",
5    "data": {
6        "id": 567,
7        "campaign_name": "Spring Sale Announcement",
8        "contact_list_id": 45,
9        "credits_consumed": 1875,
10        "total_participants": 1250,
11        "status": "Completed",
12        "created_at": "2025-10-15T14:30:00.000000Z"
13    },
14    "errors": null
15}

Response Fields

FieldTypeDescription
credits_consumedintegerTotal credits used for this campaign
total_participantsintegerNumber of contacts in the campaign
contact_list_idintegerID of the contact list used

Example in JavaScript

1async function getCampaignDetails(campaignId) {
2    const response = await fetch(
3        'https://api.texttorrent.com/api/v1/campaigning/analytic/details/"$"{campaignId}',
4        {
5            method: 'GET',
6            headers: {
7                'X-API-SID': 'SID....................................',
8                'X-API-PUBLIC-KEY': 'PK.....................................',
9                'Accept': 'application/json'
10            }
11        }
12    );
13
14    const data = await response.json();
15
16    if (data.success) {
17        console.log('Campaign Details:', data.data);
18        console.log('Credits: "$"{data.data.credits_consumed}');
19        console.log('Participants: "$"{data.data.total_participants}');
20        return data.data;
21    } else {
22        console.error('Error:', data.message);
23        throw new Error(data.message);
24    }
25}
26
27// Usage
28const 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.

GET: /api/v1/campaigning/analytic/details/{id}/list

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesCampaign ID

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)
searchstringNoSearch by sender or receiver number
statusstringNoFilter by status: Completed, Processing, Paused, Scheduled, Failed

Example Request - Get All Messages

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - Filter by Status

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?status=Failed" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example Request - Search by Number

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?search=+1555" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Campaigns fetched successfully",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "id": 12345,
10                "bulk_message_id": 567,
11                "send_from": "+15551234567",
12                "send_from_name": "John Smith - Main Business Line",
13                "send_to": "+15559876543",
14                "send_to_name": "Jane Doe",
15                "status": 1,
16                "send_status": "delivered",
17                "execute_at": "2025-10-15T14:30:00.000000Z",
18                "created_at": "2025-10-15T14:29:45.000000Z",
19                "updated_at": "2025-10-15T14:30:15.000000Z"
20            },
21            {
22                "id": 12346,
23                "bulk_message_id": 567,
24                "send_from": "+15551234567",
25                "send_from_name": "John Smith - Main Business Line",
26                "send_to": "+15551112222",
27                "send_to_name": "Bob Wilson",
28                "status": 1,
29                "send_status": "sent",
30                "execute_at": "2025-10-15T14:30:05.000000Z",
31                "created_at": "2025-10-15T14:29:45.000000Z",
32                "updated_at": "2025-10-15T14:30:20.000000Z"
33            },
34            {
35                "id": 12347,
36                "bulk_message_id": 567,
37                "send_from": "+15559876543",
38                "send_from_name": "John Smith - Marketing Line",
39                "send_to": "+15553334444",
40                "send_to_name": "Alice Johnson",
41                "status": 1,
42                "send_status": "failed",
43                "execute_at": "2025-10-15T14:30:10.000000Z",
44                "created_at": "2025-10-15T14:29:45.000000Z",
45                "updated_at": "2025-10-15T14:30:25.000000Z"
46            }
47        ],
48        "first_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?page=1",
49        "from": 1,
50        "last_page": 125,
51        "last_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?page=125",
52        "next_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?page=2",
53        "path": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list",
54        "per_page": 10,
55        "prev_page_url": null,
56        "to": 10,
57        "total": 1250
58    },
59    "errors": null
60}

Message Status Values

Status CodeSend StatusFilter StatusDescription
0N/AScheduledMessage waiting for scheduled time (execute_at > now)
1pendingProcessingMessage is being processed/sent
1deliveredCompletedMessage successfully delivered to recipient
1sentCompletedMessage sent (delivery confirmation pending)
1failedFailedMessage failed to send
1undeliveredFailedMessage sent but not delivered
5N/APausedMessage paused by user

Response Fields

FieldTypeDescription
send_fromstringPhone number message was sent from
send_from_namestringUser and friendly name of sender number
send_tostringRecipient phone number
send_to_namestringRecipient name from contacts
statusintegerProcessing status code (0, 1, 5)
send_statusstringDelivery status (pending, sent, delivered, failed, undelivered)
execute_atdatetimeWhen message was/will be sent

Example in JavaScript

1async function getCampaignMessages(campaignId, options = {}) {
2    const params = new URLSearchParams({
3        page: options.page || 1,
4        limit: options.limit || 10
5    });
6
7    if (options.search) params.append('search', options.search);
8    if (options.status) params.append('status', options.status);
9
10    const response = await fetch(
11        'https://api.texttorrent.com/api/v1/campaigning/analytic/details/"$"{campaignId}/list?"$"{params}',
12        {
13            method: 'GET',
14            headers: {
15                'X-API-SID': 'SID....................................',
16                'X-API-PUBLIC-KEY': 'PK.....................................',
17                'Accept': 'application/json'
18            }
19        }
20    );
21
22    const data = await response.json();
23
24    if (data.success) {
25        console.log('Messages:', data.data.data);
26        console.log('Total messages: "$"{data.data.total}');
27        return data.data;
28    } else {
29        console.error('Error:', data.message);
30        throw new Error(data.message);
31    }
32}
33
34// Usage - Get all messages
35const messages = await getCampaignMessages(567);
36
37// Usage - Get failed messages
38const failed = await getCampaignMessages(567, { status: 'Failed' });
39
40// Usage - Search by number
41const search = await getCampaignMessages(567, { search: '+1555' });
42
43// Usage - Pagination
44const 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_from and send_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.

GET: /api/v1/campaigning/analytic/details/{id}/count

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesCampaign ID

Example Request

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/count" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Campaigns count fetched successfully",
5    "data": {
6        "total": 1250,
7        "scheduled": 0,
8        "processing": 5,
9        "completed": 1230,
10        "paused": 0,
11        "failed": 15
12    },
13    "errors": null
14}

Response Fields

FieldTypeDescription
totalintegerTotal number of messages in campaign
scheduledintegerMessages waiting for scheduled time (status=0, execute_at > now)
processingintegerMessages being processed (status=1, send_status=pending)
completedintegerMessages delivered or sent (send_status=delivered or sent)
pausedintegerMessages paused by user (status=5)
failedintegerMessages that failed (send_status=failed or undelivered)

Example in JavaScript

1async function getCampaignMessageCounts(campaignId) {
2    const response = await fetch(
3        'https://api.texttorrent.com/api/v1/campaigning/analytic/details/"$"{campaignId}/count',
4        {
5            method: 'GET',
6            headers: {
7                'X-API-SID': 'SID....................................',
8                'X-API-PUBLIC-KEY': 'PK.....................................',
9                'Accept': 'application/json'
10            }
11        }
12    );
13
14    const data = await response.json();
15
16    if (data.success) {
17        console.log('Message Statistics:');
18        console.log('Total: "$"{data.data.total}');
19        console.log('Completed: "$"{data.data.completed}');
20        console.log('Failed: "$"{data.data.failed}');
21
22        // Calculate delivery rate
23        const deliveryRate = ((data.data.completed / data.data.total) * 100).toFixed(2);
24        console.log('Delivery rate: "$"{deliveryRate}%');
25
26        return data.data;
27    } else {
28        console.error('Error:', data.message);
29        throw new Error(data.message);
30    }
31}
32
33// Usage
34const stats = await getCampaignMessageCounts(567);
35
36// Display metrics
37function displayCampaignMetrics(stats) {
38    const deliveryRate = ((stats.completed / stats.total) * 100).toFixed(2);
39    const failureRate = ((stats.failed / stats.total) * 100).toFixed(2);
40
41    console.log('Campaign Performance:');
42    console.log('Total Messages: "$"{stats.total}');
43    console.log('  Delivery Rate: "$"{deliveryRate}%');
44    console.log('  Failure Rate: "$"{failureRate}%');
45    console.log('  Still Processing: "$"{stats.processing}');
46}
47
48displayCampaignMetrics(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

1class CampaignAnalytics {
2    constructor(apiSid, apiKey) {
3        this.apiSid = apiSid;
4        this.apiKey = apiKey;
5        this.baseUrl = 'https://api.texttorrent.com/api/v1/campaigning/analytic';
6    }
7
8    async getCampaignOverview() {
9        const [campaigns, counts] = await Promise.all([
10            this.listCampaigns({ limit: 5 }),
11            this.getCampaignCounts()
12        ]);
13
14        return {
15            recentCampaigns: campaigns.data,
16            statistics: counts
17        };
18    }
19
20    async getCampaignPerformance(campaignId) {
21        const [details, messageCounts, messages] = await Promise.all([
22            this.getCampaignDetails(campaignId),
23            this.getMessageCounts(campaignId),
24            this.getMessages(campaignId, { limit: 100 })
25        ]);
26
27        const deliveryRate = (messageCounts.completed / messageCounts.total * 100).toFixed(2);
28        const failureRate = (messageCounts.failed / messageCounts.total * 100).toFixed(2);
29
30        return {
31            campaign: details,
32            metrics: {
33                total: messageCounts.total,
34                completed: messageCounts.completed,
35                failed: messageCounts.failed,
36                deliveryRate: '"$"{deliveryRate}%',
37                failureRate: '"$"{failureRate}%',
38                creditsPerMessage: (details.credits_consumed / messageCounts.total).toFixed(2)
39            },
40            recentMessages: messages.data
41        };
42    }
43
44    async listCampaigns(options = {}) {
45        return await this.apiCall('GET', '', options);
46    }
47
48    async getCampaignCounts() {
49        return await this.apiCall('GET', '/count');
50    }
51
52    async getCampaignDetails(id) {
53        return await this.apiCall('GET', '/details/"$"{id}');
54    }
55
56    async getMessages(id, options = {}) {
57        return await this.apiCall('GET', '/details/"$"{id}/list', options);
58    }
59
60    async getMessageCounts(id) {
61        return await this.apiCall('GET', '/details/"$"{id}/count');
62    }
63
64    async apiCall(method, endpoint, params = {}) {
65        const queryString = new URLSearchParams(params).toString();
66        const url = '"$:{this.baseUrl}"$"{endpoint}"$"{queryString ? '"?""$"{queryString}' : ''}';
67
68        const response = await fetch(url, {
69            method,
70            headers: {
71                'X-API-SID': this.apiSid,
72                'X-API-PUBLIC-KEY': this.apiKey,
73                'Accept': 'application/json'
74            }
75        });
76
77        const data = await response.json();
78        if (!data.success) throw new Error(data.message);
79        return data.data;
80    }
81}
82
83// Usage - Dashboard Overview
84const analytics = new CampaignAnalytics('SID....................................', 'PK.....................................');
85
86const overview = await analytics.getCampaignOverview();
87console.log('Recent Campaigns:', overview.recentCampaigns);
88console.log('Campaign Stats:', overview.statistics);
89
90// Usage - Detailed Campaign Analysis
91const performance = await analytics.getCampaignPerformance(567);
92console.log('Campaign:', performance.campaign.campaign_name);
93console.log('Delivery Rate:', performance.metrics.deliveryRate);
94console.log('Failure Rate:', performance.metrics.failureRate);
95console.log('Cost per Message:', performance.metrics.creditsPerMessage);
96
97// Usage - Find Problem Campaigns
98const campaigns = await analytics.listCampaigns({ status: 'Failed' });
99for (const campaign of campaigns.data) {
100    const counts = await analytics.getMessageCounts(campaign.id);
101    const failureRate = (counts.failed / counts.total * 100).toFixed(2);
102    console.log('"$"{campaign.campaign_name}: "$"{failureRate}% failure rate');
103}

Key Performance Indicators (KPIs)

KPICalculationGood Target
Delivery Rate(Completed / Total) × 100> 95%
Failure Rate(Failed / Total) × 100< 5%
Cost per MessageCredits Consumed / Total Messages1-3 credits
Campaign Completion TimeLast Message - First MessageDepends 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
ℹ️ About Templates: Templates can contain placeholder variables like {first_name} and {last_name}, and spin text variations like hi ; hello here for personalized messaging. Assigned users can only access templates assigned to them.

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.

GET: /api/v1/campaigning/template

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results per page (default: 10)
pageintegerNoPage number for pagination (default: 1)
searchstringNoSearch templates by name
sort_bystringNoField to sort by (default: created_at)
sort_directionstringNoSort direction: ASC or DESC (default: DESC)
emailstringNoBase64 encoded email address of a sub-user. Main accounts can use this to retrieve message templates belonging to their sub-users.
ℹ️ Sub-Account Access: Main account holders can view message templates of their sub-users by providing the sub-user's email address (Base64 encoded) via the email parameter. If the provided email does not belong to a sub-user associated with your main account, a 403 Forbidden response will be returned.

Example 1: Get All Templates (Main Account)

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/template" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Example 2: Retrieve Sub-User's Message Templates

1# First, encode the sub-user's email in Base64
2# Example: subuser@example.com → c3VidXNlckBleGFtcGxlLmNvbQ==
3
4curl -X GET "https://api.texttorrent.com/api/v1/campaigning/template?email=c3VidXNlckBleGFtcGxlLmNvbQ==" 
5  -H "X-API-SID: SID...................................." 
6  -H "X-API-PUBLIC-KEY: PK....................................." 
7  -H "Accept: application/json"

Example 3: Search and Sort

1curl -X GET "https://api.texttorrent.com/api/v1/campaigning/template?search=welcome&sort_by=template_name&sort_direction=ASC" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Templates retrieved successfully",
5    "data": {
6        "current_page": 1,
7        "data": [
8            {
9                "id": 12,
10                "template_name": "Welcome Message",
11                "status": 1,
12                "assigned_users": [101, 102, 103],
13                "preview_message": "Hi {first_name}, welcome to our service! We're excited to have you.",
14                "created_at": "2025-09-20T10:30:00.000000Z",
15                "user_name": "John Smith",
16                "assignees": [
17                    {
18                        "id": 101,
19                        "name": "Sarah Johnson",
20                        "email": "sarah.johnson@example.com"
21                    },
22                    {
23                        "id": 102,
24                        "name": "Mike Davis",
25                        "email": "mike.davis@example.com"
26                    },
27                    {
28                        "id": 103,
29                        "name": "Emily Brown",
30                        "email": "emily.brown@example.com"
31                    }
32                ]
33            },
34            {
35                "id": 15,
36                "template_name": "Flash Sale Alert",
37                "status": 1,
38                "assigned_users": [],
39                "preview_message": "{Hi|Hello|Hey} {first_name}! Flash sale ends in 2 hours. Shop now!",
40                "created_at": "2025-10-10T14:15:00.000000Z",
41                "user_name": "John Smith",
42                "assignees": []
43            },
44            {
45                "id": 18,
46                "template_name": "Order Confirmation",
47                "status": 0,
48                "assigned_users": [101],
49                "preview_message": "Thank you for your order! Your order number is {order_id}. We'll notify you when it ships.",
50                "created_at": "2025-10-15T09:45:00.000000Z",
51                "user_name": "John Smith",
52                "assignees": [
53                    {
54                        "id": 101,
55                        "name": "Sarah Johnson",
56                        "email": "sarah.johnson@example.com"
57                    }
58                ]
59            }
60        ],
61        "first_page_url": "https://api.texttorrent.com/api/v1/campaigning/template?page=1",
62        "from": 1,
63        "last_page": 3,
64        "last_page_url": "https://api.texttorrent.com/api/v1/campaigning/template?page=3",
65        "next_page_url": "https://api.texttorrent.com/api/v1/campaigning/template?page=2",
66        "path": "https://api.texttorrent.com/api/v1/campaigning/template",
67        "per_page": 10,
68        "prev_page_url": null,
69        "to": 10,
70        "total": 28
71    },
72    "errors": null
73}

Error Response - Unauthorized Sub-User Access (403 Forbidden)

1{
2    "code": 403,
3    "success": false,
4    "message": "Unauthorized access",
5    "data": null,
6    "errors": null
7}

Response Fields

FieldTypeDescription
statusintegerTemplate status: 1 = active, 0 = inactive
assigned_usersarrayArray of user IDs who can access this template
preview_messagestringTemplate content with variables and spin text
user_namestringName of user who created the template
assigneesarrayFull details of assigned users (id, name, email)

Example in JavaScript

1async function listTemplates(options = {}) {
2    const params = new URLSearchParams({
3        page: options.page || 1,
4        limit: options.limit || 10
5    });
6
7    if (options.search) params.append('search', options.search);
8    if (options.sort_by) params.append('sort_by', options.sort_by);
9    if (options.sort_direction) params.append('sort_direction', options.sort_direction);
10
11    const response = await fetch(
12        'https://api.texttorrent.com/api/v1/campaigning/template?"$"{params}',
13        {
14            method: 'GET',
15            headers: {
16                'X-API-SID': 'SID....................................',
17                'X-API-PUBLIC-KEY': 'PK.....................................',
18                'Accept': 'application/json'
19            }
20        }
21    );
22
23    const data = await response.json();
24
25    if (data.success) {
26        console.log('Templates:', data.data.data);
27        console.log('Total templates: "$"{data.data.total}');
28        return data.data;
29    } else {
30        console.error('Error:', data.message);
31        throw new Error(data.message);
32    }
33}
34
35// Usage - Get all templates
36const templates = await listTemplates();
37
38// Usage - Search templates
39const searchResults = await listTemplates({ search: 'welcome' });
40
41// Usage - Sort by name
42const sorted = await listTemplates({
43    sort_by: 'template_name',
44    sort_direction: 'ASC'
45});

Important Notes

  • Only returns templates where user_id matches 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_users means template is available to all
  • Sub-accounts will only see their own templates when no email parameter is provided
  • Sub-User Access: Main accounts can retrieve message templates for their sub-users by providing the Base64 encoded email in the email parameter
  • To encode email in Base64: echo -n "subuser@example.com" | base64 (Linux/Mac) or use an online Base64 encoder
  • Attempting to access data for an email that is not associated with your sub-users will result in a 403 Forbidden error

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.

POST: /api/v1/campaigning/template

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Request Body Parameters

ParameterTypeRequiredDescription
template_namestringYesTemplate name (max 255 characters)
preview_messagestringYesTemplate message content
statusbooleanYesTemplate status: true = active, false = inactive
assigneesarrayNoArray of user IDs to assign template to

Example Request - Simple Template

1curl -X POST "https://api.texttorrent.com/api/v1/campaigning/template" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "template_name": "Welcome Message",
8    "preview_message": "Hi {first_name}, welcome to our service! We'''re excited to have you.",
9    "status": true
10  }'

Example Request - With Spin Text and Assignees

1curl -X POST "https://api.texttorrent.com/api/v1/campaigning/template" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "template_name": "Flash Sale Alert",
8    "preview_message": "{Hi|Hello|Hey} {first_name}! Flash sale ends in 2 hours. {Shop now|Don'''t miss out|Hurry}!",
9    "status": true,
10    "assignees": [101, 102, 103]
11  }'

Success Response (201 Created)

1{
2    "code": 201,
3    "success": true,
4    "message": "Template created successfully",
5    "data": {
6        "id": 25,
7        "user_id": 1,
8        "template_name": "Welcome Message",
9        "preview_message": "Hi {first_name}, welcome to our service! We're excited to have you.",
10        "status": 1,
11        "assigned_users": [],
12        "created_at": "2025-10-17T16:45:00.000000Z",
13        "updated_at": "2025-10-17T16:45:00.000000Z"
14    },
15    "errors": null
16}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "template_name": ["The template name field is required."],
8        "preview_message": ["The preview message field is required."],
9        "status": ["The status field is required."],
10        "assignees.0": ["The selected assignees.0 is invalid."]
11    }
12}

Example in JavaScript

1async function createTemplate(templateData) {
2    const response = await fetch('https://api.texttorrent.com/api/v1/campaigning/template', {
3        method: 'POST',
4        headers: {
5            'X-API-SID': 'SID....................................',
6            'X-API-PUBLIC-KEY': 'PK.....................................',
7            'Accept': 'application/json',
8            'Content-Type': 'application/json'
9        },
10        body: JSON.stringify(templateData)
11    });
12
13    const data = await response.json();
14
15    if (data.success) {
16        console.log('Template created:', data.data);
17        alert('Template "$"{data.data.template_name}" created successfully!');
18        return data.data;
19    } else {
20        console.error('Error:', data.message, data.errors);
21        throw new Error(data.message);
22    }
23}
24
25// Usage - Simple template
26await createTemplate({
27    template_name: 'Welcome Message',
28    preview_message: 'Hi {first_name}, welcome to our service!',
29    status: true
30});
31
32// Usage - With spin text and assignees
33await createTemplate({
34    template_name: 'Flash Sale Alert',
35    preview_message: '{Hi|Hello|Hey} {first_name}! Flash sale ends in 2 hours!',
36    status: true,
37    assignees: [101, 102, 103]
38});

Supported Variables

VariableDescriptionExample Output
{first_name}Contact's first nameJohn
{last_name}Contact's last nameSmith
{option1|option2}Spin text (random selection){option1|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.

PUT: /api/v1/campaigning/template/{id}

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesTemplate ID to update

Request Body Parameters

ParameterTypeRequiredDescription
template_namestringYesTemplate name (max 255 characters)
preview_messagestringYesTemplate message content
statusbooleanYesTemplate status: true = active, false = inactive
assigneesarrayNoArray of user IDs to assign template to

Example Request

1curl -X PUT "https://api.texttorrent.com/api/v1/campaigning/template/12" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "template_name": "Updated Welcome Message",
8    "preview_message": "Hi {first_name}, thank you for joining us! We'''re thrilled to have you on board.",
9    "status": true,
10    "assignees": [101, 102]
11  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Template updated successfully",
5    "data": {
6        "id": 12,
7        "user_id": 1,
8        "template_name": "Updated Welcome Message",
9        "preview_message": "Hi {first_name}, thank you for joining us! We're thrilled to have you on board.",
10        "status": 1,
11        "assigned_users": [101, 102],
12        "created_at": "2025-09-20T10:30:00.000000Z",
13        "updated_at": "2025-10-17T16:50:00.000000Z"
14    },
15    "errors": null
16}

Example in JavaScript

1async function updateTemplate(templateId, templateData) {
2    const response = await fetch(
3        'https://api.texttorrent.com/api/v1/campaigning/template/"$"{templateId}',
4        {
5            method: 'PUT',
6            headers: {
7                'X-API-SID': 'SID....................................',
8                'X-API-PUBLIC-KEY': 'PK.....................................',
9                'Accept': 'application/json',
10                'Content-Type': 'application/json'
11            },
12            body: JSON.stringify(templateData)
13        }
14    );
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Template updated:', data.data);
20        alert('Template updated successfully!');
21        return data.data;
22    } else {
23        console.error('Error:', data.message, data.errors);
24        throw new Error(data.message);
25    }
26}
27
28// Usage
29await updateTemplate(12, {
30    template_name: 'Updated Welcome Message',
31    preview_message: 'Hi {first_name}, thank you for joining us!',
32    status: true,
33    assignees: [101, 102]
34});

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.

DELETE: /api/v1/campaigning/template/{id}
⚠️ Warning: Deleting a template is permanent and cannot be undone. Make sure you want to remove this template before proceeding.

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json

Path Parameters

ParameterTypeRequiredDescription
idintegerYesTemplate ID to delete

Example Request

1curl -X DELETE "https://api.texttorrent.com/api/v1/campaigning/template/12" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json"

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Template deleted successfully",
5    "data": [],
6    "errors": null
7}

Example in JavaScript

1async function deleteTemplate(templateId) {
2    if (!confirm('Delete this template? This action cannot be undone.')) {
3        return;
4    }
5
6    const response = await fetch(
7        'https://api.texttorrent.com/api/v1/campaigning/template/"$"{templateId}',
8        {
9            method: 'DELETE',
10            headers: {
11                'X-API-SID': 'SID....................................',
12                'X-API-PUBLIC-KEY': 'PK.....................................',
13                'Accept': 'application/json'
14            }
15        }
16    );
17
18    const data = await response.json();
19
20    if (data.success) {
21        console.log('Template deleted successfully');
22        alert('Template has been deleted');
23        return true;
24    } else {
25        console.error('Error:', data.message);
26        alert('Failed to delete template');
27        return false;
28    }
29}
30
31// Usage
32await 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.

POST: /api/v1/campaigning/template/export

Request Headers

1X-API-SID: SID....................................
2X-API-PUBLIC-KEY: PK.....................................
3Accept: application/json
4Content-Type: application/json

Request Body Parameters

ParameterTypeRequiredDescription
template_idsarrayYesArray of template IDs to export

Example Request

1curl -X POST "https://api.texttorrent.com/api/v1/campaigning/template/export" 
2  -H "X-API-SID: SID...................................." 
3  -H "X-API-PUBLIC-KEY: PK....................................." 
4  -H "Accept: application/json" 
5  -H "Content-Type: application/json" 
6  -d '{
7    "template_ids": [12, 15, 18, 25]
8  }'

Success Response (200 OK)

1{
2    "code": 200,
3    "success": true,
4    "message": "Templates exported successfully",
5    "data": {
6        "path": "inbox_template/inbox_template_1729180800.xlsx",
7        "url": "https://texttorrent.nyc3.digitaloceanspaces.com/inbox_template/inbox_template_1729180800.xlsx"
8    },
9    "errors": null
10}

Validation Errors (422 Unprocessable Entity)

1{
2    "code": 422,
3    "success": false,
4    "message": "Validation Error",
5    "data": null,
6    "errors": {
7        "template_ids": ["The template ids field is required."],
8        "template_ids.0": ["The selected template ids.0 is invalid."]
9    }
10}

Example in JavaScript

1async function exportTemplates(templateIds) {
2    const response = await fetch(
3        'https://api.texttorrent.com/api/v1/campaigning/template/export',
4        {
5            method: 'POST',
6            headers: {
7                'X-API-SID': 'SID....................................',
8                'X-API-PUBLIC-KEY': 'PK.....................................',
9                'Accept': 'application/json',
10                'Content-Type': 'application/json'
11            },
12            body: JSON.stringify({ template_ids: templateIds })
13        }
14    );
15
16    const data = await response.json();
17
18    if (data.success) {
19        console.log('Export successful:', data.data);
20
21        // Download the file
22        window.open(data.data.url, '_blank');
23
24        return data.data;
25    } else {
26        console.error('Error:', data.message, data.errors);
27        throw new Error(data.message);
28    }
29}
30
31// Usage - Export selected templates
32await exportTemplates([12, 15, 18, 25]);
33
34// Usage - Export all templates from list
35const templates = await listTemplates();
36const allIds = templates.data.map(t => t.id);
37await exportTemplates(allIds);

Example in PHP

1<?php
2$templateIds = [12, 15, 18, 25];
3
4$ch = curl_init();
5curl_setopt($ch, CURLOPT_URL, 'https://api.texttorrent.com/api/v1/campaigning/template/export');
6curl_setopt($ch, CURLOPT_POST, true);
7curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['template_ids' => $templateIds]));
8curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
9curl_setopt($ch, CURLOPT_HTTPHEADER, [
10    'X-API-SID: SID....................................',
11    'X-API-PUBLIC-KEY: PK.....................................',
12    'Accept: application/json',
13    'Content-Type: application/json'
14]);
15
16$response = curl_exec($ch);
17curl_close($ch);
18
19$data = json_decode($response, true);
20
21if ($data['success']) {
22    $downloadUrl = $data['data']['url'];
23    echo "Download URL: {$downloadUrl}
24";
25
26    // Optionally download the file
27    file_put_contents('templates.xlsx', file_get_contents($downloadUrl));
28} else {
29    echo "Error: {$data['message']}
30";
31}
32?>

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 here 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

1class TemplateManager {
2    constructor(apiSid, apiKey) {
3        this.apiSid = apiSid;
4        this.apiKey = apiKey;
5        this.baseUrl = 'https://api.texttorrent.com/api/v1/campaigning/template';
6    }
7
8    async list(options = {}) {
9        return await this.apiCall('GET', '', options);
10    }
11
12    async create(templateData) {
13        return await this.apiCall('POST', '', {}, templateData);
14    }
15
16    async update(id, templateData) {
17        return await this.apiCall('PUT', '/"$"{id}', {}, templateData);
18    }
19
20    async delete(id) {
21        return await this.apiCall('DELETE', '/"$"{id}');
22    }
23
24    async export(templateIds) {
25        return await this.apiCall('POST', '/export', {}, { template_ids: templateIds });
26    }
27
28    async search(query) {
29        return await this.list({ search: query });
30    }
31
32    async getActiveTemplates() {
33        const templates = await this.list({ limit: 100 });
34        return templates.data.filter(t => t.status === 1);
35    }
36
37    async apiCall(method, endpoint, params = {}, body = null) {
38        const queryString = new URLSearchParams(params).toString();
39        const url = '"$"{this.baseUrl}"$"{endpoint}"$"{queryString ? '?"$"{queryString}' : ''}';
40
41        const options = {
42            method,
43            headers: {
44                'X-API-SID': this.apiSid,
45                'X-API-PUBLIC-KEY': this.apiKey,
46                'Accept': 'application/json'
47            }
48        };
49
50        if (body && (method === 'POST' || method === 'PUT')) {
51            options.headers['Content-Type'] = 'application/json';
52            options.body = JSON.stringify(body);
53        }
54
55        const response = await fetch(url, options);
56        const data = await response.json();
57
58        if (!data.success) throw new Error(data.message);
59        return data.data;
60    }
61}
62
63// Usage
64const manager = new TemplateManager('SID....................................', 'PK.....................................');
65
66// List all templates
67const templates = await manager.list();
68
69// Search templates
70const searchResults = await manager.search('welcome');
71
72// Create new template
73const newTemplate = await manager.create({
74    template_name: 'Order Confirmation',
75    preview_message: 'Hi {first_name}, your order #{order_id} is confirmed!',
76    status: true,
77    assignees: [101]
78});
79
80// Update template
81await manager.update(12, {
82    template_name: 'Updated Name',
83    preview_message: 'Updated message',
84    status: true,
85    assignees: []
86});
87
88// Export templates
89const exportData = await manager.export([12, 15, 18]);
90console.log('Download:', exportData.url);
91
92// Get only active templates
93const active = await manager.getActiveTemplates();
94console.log('"$"{active.length} active templates');

Template Variables Reference

Variable TypeFormatExampleOutput
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 TypeExample MessageAssignees
Welcome MessageHi {first_name}, welcome to our service! We're excited to have you.All users
Order ConfirmationThank 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