1. Getting Started 1.1 Base URL 1.2 HTTP Response Codes 2. Authentication 2.1 API Key Authentication 2.2 Authentication Errors 3. Contact Module 3.1 Contact List Management 3.1.1 Get All Contact Lists 3.1.2 Create Contact List 3.1.3 Update Contact List 3.1.4 Toggle Bookmark Status 3.1.5 Delete Contact List 3.1.6 Get Contacts in a List 3.2 Contact Number Management 3.2.1 Verify Numbers 3.2.2 Validate Phone Numbers 3.2.3 Get Contact Details 3.3 Contact Notes 3.3.1 Add Contact Note 3.3.2 Update Contact Note 3.3.3 Delete Contact Note 3.4 Contact Folders 3.4.1 List Contact Folders 3.4.2 Create Contact Folder 3.4.3 Update Contact Folder 3.4.4 Delete Contact Folder 3.4.5 Assign Contact to Folder 3.5 Contact CRUD Operations 3.5.1 Add Contact 3.5.2 Update Contact 3.5.3 Delete Contacts 3.5.4 Import Contacts from CSV 3.6 Contact Validator 3.6.1 Get Validation History 3.6.2 Process Validation Cleanup 3.6.3 Export Validation Results 3.6.4 Delete Validation Record 3.7 Contact Import History 3.7.1 Get Import History 3.7.2 Download Skipped Numbers PDF 3.8 Blocked List Management 3.8.1 Get Blocked List 3.8.2 Add to Blocked List 3.8.3 Remove from Blocked List 3.8.4 Delete Blocked Contacts 3.8.5 Export Blocked List 3.8.6 Best Practices 3.9 Opt-Out Words Management 3.9.1 Get Opt-Out Words 3.9.2 Create Opt-Out Word 3.9.3 Update Opt-Out Word 3.9.4 Delete Opt-Out Words 3.9.5 Export Opt-Out Words 3.9.6 Best Practices 4. Inbox & Messaging 4.1 Get Inbox Messages 4.2 Get Last Chat 4.3 Get Message Templates 4.4 Get Active Numbers 4.5 Get Receiver Numbers 4.6 Get Chat Details 4.7 Delete Chat 4.8 Best Practices 4.9 Send Message 4.10 Start New Chat 4.11 Generate AI Replies 4.12 Block Contact 4.13 Unblock Contact 4.14 Add Event 4.15 Export Inbox 4.16 Move Contact to Folder 4.17 Add Inbox Note 4.18 Delete Inbox Note 5. Sub-Account Management 5.1 List Sub-Accounts 5.2 Create Sub-Account 5.3 Get Sub-Account Details 5.4 Update Sub-Account 5.5 Delete Sub-Account 5.6 Change Sub-Account Status 5.7 Login as Sub-User 5.8 Best Practices 6. Bulk Messaging & Campaigns 6.1 Get Campaign Data 6.2 Create Campaign 6.3 Best Practices 7. Campaign Analytics 7.1 List Campaigns 7.2 Get Campaign Counts 7.3 Get Campaign Details 7.4 Get Message List 7.5 Get Message Counts 7.6 Best Practices 8. Message Templates 8.1 List Message Templates 8.2 Create Message Template 8.3 Update Message Template 8.4 Delete Message Template 8.5 Export Message Templates 8.6 Best Practices

Text Torrent API Documentation

Last updated: January 25, 2025
bar icon

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)

Code Status Description Use Case
200 OK The request was successful GET, PUT, PATCH requests that successfully retrieve or update data
201 Created The resource was successfully created POST requests that create new resources (contacts, messages, sub-accounts)

Client Error Codes (4xx)

Code Status Description Common Causes
400 Bad Request The request was malformed or invalid Missing required parameters, invalid data format, malformed JSON
401 Unauthorized Authentication failed or credentials are missing Missing or invalid X-API-SID or X-API-PUBLIC-KEY headers
403 Forbidden Valid authentication but insufficient permissions Trying to access resources you don't have permission for, disabled API access
404 Not Found The requested resource does not exist Invalid endpoint URL, resource ID doesn't exist, deleted resource
422 Unprocessable Entity The request was well-formed but contains semantic errors Validation errors, insufficient credits, business logic violations
429 Too Many Requests Rate limit exceeded More than 60 requests per minute from the same API key

Server Error Codes (5xx)

Code Status Description What to Do
500 Internal Server Error An error occurred on the server Retry the request after a few moments. Contact support if the issue persists
503 Service Unavailable The server is temporarily unavailable Wait and retry. The service may be under maintenance

Response Structure

All API responses follow a consistent JSON structure:

Success Response Format:

{
  "code": 200,
  "success": true,
  "message": "Operation completed successfully",
  "data": {
    // Response data here
  },
  "errors": null
}

Error Response Format:

{
  "code": 422,
  "success": false,
  "message": "Validation Error",
  "data": null,
  "errors": {
    "field_name": [
      "Error message for this field"
    ]
  }
}

Common Error Scenarios

1. Insufficient Credits (422):

{
  "code": 422,
  "success": false,
  "message": "You do not have enough credits to perform this action.",
  "data": null,
  "errors": null
}

2. Resource Not Found (404):

{
  "code": 404,
  "success": false,
  "message": "Sub Account Not Found",
  "data": null,
  "errors": []
}

3. Trial Limitation (422):

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

4. Rate Limit Exceeded (429):

{
  "code": 429,
  "success": false,
  "message": "Too Many Requests. Please try again in 60 seconds.",
  "data": null,
  "errors": null
}

Best Practices for Error Handling

  • Always check the 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 and X-API-PUBLIC-KEY headers. Unauthenticated requests will receive a 401 Unauthorized response.

Rate Limiting: There is a rate limit of 60 requests per minute. If you exceed this limit, you will receive a 429 Too Many Requests status with a Retry-After header indicating when you can try again.

API Key Authentication

All API requests must be authenticated using two headers: X-API-SID and X-API-PUBLIC-KEY. These credentials are unique to your account and provide secure access to the Text Torrent API.

Finding Your API Credentials

Your API credentials (SID and Public Key) are generated automatically when your account is created. You can find them in your account settings or API dashboard.

  • X-API-SID: Your Account SID (e.g., SID....................................)
  • X-API-PUBLIC-KEY: Your Public API Key (e.g., PK.....................................)
⚠️ 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:

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Example: Get User Information (/me)

The /me endpoint returns information about the authenticated user.

Endpoint: GET https://api.texttorrent.com/api/v1/user/auth/me

Example cURL Request:

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

Success Response (200 OK):

{
  "code": 200,
  "success": true,
  "message": "Successfully fetched user information",
  "data": {
    "id": 1,
    "first_name": "John",
    "last_name": "Doe",
    "username": "johndoe",
    "email": "john@example.com",
    "credits": 1000,
    "plan": {
      "package_name": "pro",
      "status": "Active"
    }
  }
}

Example in PHP:

$sid = 'SID....................................';
$publicKey = 'PK.....................................';

$ch = curl_init('https://api.texttorrent.com/api/v1/user/auth/me');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-SID: ' . $sid,
    'X-API-PUBLIC-KEY: ' . $publicKey,
    'Content-Type: application/json',
    'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

Example in JavaScript:

const sid = 'SID....................................';
const publicKey = 'PK.....................................';

fetch('https://api.texttorrent.com/api/v1/user/auth/me', {
  method: 'GET',
  headers: {
    'X-API-SID': sid,
    'X-API-PUBLIC-KEY': publicKey,
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

API Key Best Practices

  • Store your API credentials in environment variables, not in your code
  • Use different API keys for development and production environments
  • Never expose your API credentials in client-side JavaScript or mobile apps
  • Rotate your API credentials periodically for enhanced security
  • Monitor your API usage regularly for any suspicious activity
  • If you suspect your credentials have been compromised, regenerate them immediately in your account settings

Authentication Error Responses

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

Status Code Error Description
401 Unauthorized No authentication credentials provided or invalid credentials
403 Forbidden Valid credentials but insufficient permissions for the requested resource
429 Too Many Requests Rate limit exceeded (60 requests per minute)

401 Unauthorized Response Example:

{
  "status": false,
  "message": "Unauthorized",
  "errors": []
}

429 Rate Limit Response Example:

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

403 Forbidden Response Example:

{
  "status": false,
  "message": "Unauthorized",
  "errors": []
}

Contact Module

The Contact Module provides a comprehensive set of APIs to manage all aspects of your contacts, including list management, individual contact operations, validation, folders, notes, and more. This module is the foundation for organizing and maintaining your contact database.

Module Capabilities:

  • Contact List Management: Create, organize, and manage contact lists
  • Contact Operations: Add, update, delete, and search individual contacts
  • Contact Validation: Verify phone numbers and clean up invalid contacts
  • Folder Organization: Group contacts into custom folders
  • Notes & Annotations: Add notes and track contact interactions
  • Blocked List: Manage blacklisted contacts
  • Opt-Out Management: Configure and track opt-out preferences

3.1 Contact List Management

The Contact List Management API provides comprehensive endpoints to organize and manage your contact lists. Contact lists help you group contacts for better organization and targeted messaging campaigns.

Key Features:

  • Create, update, and delete contact lists
  • Bookmark/favorite important lists for quick access
  • View all lists with contact counts
  • Toggle bookmark status on lists
  • Delete contacts in bulk by list type

Get All Contact Lists

Retrieve all contact lists associated with your account, including the total count of non-blacklisted contacts in each list.

GET /api/v1/contact/list

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Query Parameters

This endpoint does not require any query parameters.

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact list retrieved",
    "data": [
        {
            "id": 45,
            "user_id": 1,
            "name": "VIP Customers",
            "bookmarked": 1,
            "created_at": "2025-01-15T10:30:00.000000Z",
            "total_contacts": 150
        },
        {
            "id": 44,
            "user_id": 1,
            "name": "Newsletter Subscribers",
            "bookmarked": 0,
            "created_at": "2025-01-14T14:22:00.000000Z",
            "total_contacts": 523
        },
        {
            "id": 43,
            "user_id": 1,
            "name": "Event Attendees",
            "bookmarked": 0,
            "created_at": "2025-01-10T09:15:00.000000Z",
            "total_contacts": 89
        }
    ],
    "errors": null
}

Response Fields

Field Type Description
id integer Unique identifier for the contact list
user_id integer ID of the user who owns this list
name string Name of the contact list (max 15 characters)
bookmarked boolean Whether the list is bookmarked/favorited (1 = true, 0 = false)
created_at string Timestamp when the list was created (ISO 8601 format)
total_contacts integer Total number of non-blacklisted contacts in this list

Notes

  • Lists are ordered by ID in descending order (newest first)
  • Only non-blacklisted contacts are counted in total_contacts
  • Sub-accounts will only see their own lists

Create Contact List

Create a new contact list to organize your contacts. List names must be unique within your account and cannot exceed 15 characters.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
name string Yes Name of the contact list (max 15 characters, must be unique)

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact list created successfully",
    "data": {
        "id": 46,
        "user_id": 1,
        "name": "New Leads 2025",
        "bookmarked": 0,
        "created_at": "2025-10-17T12:34:56.000000Z",
        "updated_at": "2025-10-17T12:34:56.000000Z"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "name": [
            "The name field is required."
        ]
    }
}

Common Validation Errors

  • Required: "The name field is required."
  • Duplicate: "The name has already been taken."
  • Max Length: "The name must not be greater than 15 characters."

Update Contact List

Update the name of an existing contact list. The new name must be unique within your account and cannot exceed 15 characters.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
list_id integer Yes ID of the contact list to update (must exist)
name string Yes New name for the contact list (max 15 characters, must be unique)

Example Request

curl -X PUT "https://api.texttorrent.com/api/v1/contact/list/update/bookmark" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "list_id": 46,
    "name": "Hot Leads 2025"
  }'

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact list updated successfully",
    "data": {
        "id": 46,
        "user_id": 1,
        "name": "Hot Leads 2025",
        "bookmarked": 0,
        "created_at": "2025-10-17T12:34:56.000000Z",
        "updated_at": "2025-10-17T13:45:22.000000Z"
    },
    "errors": null
}

Error Response - List Not Found (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "list_id": [
            "The selected list id is invalid."
        ]
    }
}

Common Validation Errors

  • Required Fields: "The list_id field is required." / "The name field is required."
  • Invalid ID: "The selected list id is invalid."
  • Duplicate Name: "The name has already been taken."
  • Max Length: "The name must not be greater than 15 characters."

Toggle Bookmark Status

Toggle the bookmark/favorite status of a contact list. Bookmarked lists can be displayed prominently in your application for quick access to frequently used lists.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

URL Parameters

Parameter Type Required Description
id integer Yes ID of the contact list to toggle

Example Request - Bookmark a List

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact list updated successfully",
    "data": {
        "id": 46,
        "user_id": 1,
        "name": "Hot Leads 2025",
        "bookmarked": 1,
        "created_at": "2025-10-17T12:34:56.000000Z",
        "updated_at": "2025-10-17T13:45:22.000000Z"
    },
    "errors": null
}

Error Response - List Not Found (200 with error)

{
    "code": 400,
    "success": false,
    "message": "Failed to update contact list",
    "data": null,
    "errors": []
}

Notes

  • If 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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
list_id integer Conditional ID of the contact list to delete (required if type is not provided)
type string Conditional Type of contacts to delete: default, blacklisted, or unlisted

Delete Types Explained

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

curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "list_id": 46
  }'

Success Response - Specific List (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact list deleted successfully",
    "data": null,
    "errors": null
}

Example 2: Delete All Default Contacts

curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "default"
  }'

Success Response - Default Contacts (200 OK)

{
    "code": 200,
    "success": true,
    "message": "All default contacts deleted successfully",
    "data": null,
    "errors": null
}

Example 3: Delete All Blacklisted Contacts

curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "blacklisted"
  }'

Success Response - Blacklisted Contacts (200 OK)

{
    "code": 200,
    "success": true,
    "message": "All blacklisted contacts deleted successfully",
    "data": null,
    "errors": null
}

Example 4: Delete All Unlisted Contacts

curl -X DELETE "https://api.texttorrent.com/api/v1/contact/list/delete/bookmark" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "unlisted"
  }'

Success Response - Unlisted Contacts (200 OK)

{
    "code": 200,
    "success": true,
    "message": "All unlisted contacts deleted successfully",
    "data": null,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "list_id": [
            "The selected list id is invalid."
        ]
    }
}

Common Validation Errors

  • Invalid List ID: "The selected list id is invalid."
  • Invalid Type: "The selected type is invalid." (must be: default, blacklisted, or unlisted)

Important Notes

  • You must provide either list_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

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

URL Parameters

Parameter Type Required Description
listId integer Yes ID of the contact list to retrieve contacts from

Query Parameters

Parameter Type Required Default Description
limit integer No 10 Number of contacts per page (pagination)
search string No - Search term to filter contacts by first name, last name, or phone number
sort_list_id string/integer No - Filter by list type: specific list ID, unlisted, or blacklisted
page integer No 1 Page number for pagination

Example 1: Basic Request

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

Example 2: With Pagination

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

Example 3: With Search

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

Example 4: Filter by Unlisted Contacts

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

Example 5: Filter by Blacklisted Contacts

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contacts retrieved",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 1234,
                "first_name": "John",
                "last_name": "Doe",
                "number": "+19292223344",
                "email": "john.doe@example.com",
                "company": "Tech Solutions Inc",
                "valid": 1,
                "validation_process": 1,
                "folder_id": null
            },
            {
                "id": 1235,
                "first_name": "Jane",
                "last_name": "Smith",
                "number": "+19293334455",
                "email": "jane.smith@example.com",
                "company": "Marketing Pro",
                "valid": 1,
                "validation_process": 1,
                "folder_id": 5
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=1",
        "from": 1,
        "last_page": 5,
        "last_page_url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=5",
        "links": [
            {
                "url": null,
                "label": "« Previous",
                "active": false
            },
            {
                "url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=2",
                "label": "2",
                "active": false
            }
        ],
        "next_page_url": "https://api.texttorrent.com/api/v1/contact/46/contacts?page=2",
        "path": "https://api.texttorrent.com/api/v1/contact/46/contacts",
        "per_page": 10,
        "prev_page_url": null,
        "to": 10,
        "total": 50
    },
    "errors": null
}

Contact Response Fields

Field Type Description
id integer Unique identifier for the contact
first_name string Contact's first name
last_name string Contact's last name
number string Contact's phone number
email string Contact's email address (nullable)
company string Contact's company name (nullable)
valid boolean Whether the contact's phone number is valid (1 = valid, 0 = invalid)
validation_process boolean Whether the contact has been through validation (1 = yes, 0 = no)
folder_id integer ID of the folder this contact belongs to (nullable)

Search Behavior

  • Search is case-insensitive and uses partial matching (LIKE query)
  • Searches across: first_name, last_name, 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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
contact_id array Yes Array of contact IDs to verify (each ID must exist in contacts table)

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Number validation result",
    "data": {
        "total_numbers_selected": 5,
        "already_validated": 2,
        "available_validation": 3,
        "credits": 997
    },
    "errors": null
}

Response Fields

Field Type Description
total_numbers_selected integer Total number of contacts selected for verification
already_validated integer Count of contacts that have already been validated
available_validation integer Count of contacts that need validation (not yet validated)
credits integer Your remaining credits after validation (each validation costs 1 credit)

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_id": [
            "The contact id field is required."
        ]
    }
}

Common Validation Errors

  • Required: "The contact id field is required."
  • Invalid Format: "The contact id must be an array."
  • Invalid IDs: "The selected contact id.0 is invalid." (when contact doesn't exist)

Important Notes

  • Each validation costs 1 credit per contact
  • Already validated contacts don't count toward credit deduction
  • Use this endpoint before calling the validate endpoint to confirm costs
  • Sub-accounts use their parent account's credits
  • Response shows credits after deduction (what you'll have left)

3.2.2 Validate Phone Numbers

Validates selected phone numbers to verify their validity, detect mobile vs landline, and clean up your contact database. This process requires an active subscription and sufficient credits.

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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
contact_ids array Yes Array of contact IDs to validate (each ID must exist)
validator_credits number Yes Total credits required for validation (minimum: 0)
available_validation integer Yes Number of contacts available for validation (from verify endpoint)

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Number validation started successfully",
    "data": {
        "total_numbers_selected": 3,
        "available_validation": 3,
        "credits": 997
    },
    "errors": null
}

Response Fields

Field Type Description
total_numbers_selected integer Total number of contacts selected for validation
available_validation integer Number of contacts that will be validated
credits integer Your remaining credits after validation

Error Response - Insufficient Credits (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "You do not have enough credits to perform this action.",
    "data": null,
    "errors": []
}

Error Response - No Active Subscription (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "You do not have an active subscription to perform this action.",
    "data": null,
    "errors": []
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_ids": [
            "The contact ids field is required."
        ]
    }
}

Common Validation Errors

  • Required Fields: "The contact ids field is required." / "The validator credits field is required." / "The available validation field is required."
  • Invalid Format: "The contact ids must be an array." / "The validator credits must be a number."
  • Invalid Value: "The validator credits must be at least 0."
  • Invalid IDs: "The selected contact ids.0 is invalid." (when contact doesn't exist)

Validation Process

  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

// Step 1: Verify numbers and get cost estimate
const verifyResponse = await fetch('https://api.texttorrent.com/api/v1/contact/number/verify/confirmation', {
    method: 'POST',
    headers: {
        'X-API-SID': 'SID....................................',
        'X-API-PUBLIC-KEY': 'PK.....................................',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        contact_id: [1234, 1235, 1236]
    })
});

const verifyData = await verifyResponse.json();
console.log('Credits after validation:', verifyData.data.credits);
console.log('Contacts to validate:', verifyData.data.available_validation);

// Step 2: If user confirms, proceed with validation
if (confirm(`Validate ${verifyData.data.available_validation} contacts for ${verifyData.data.available_validation} credits?`)) {
    const validateResponse = await fetch('https://api.texttorrent.com/api/v1/contact/number/validate', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            contact_ids: [1234, 1235, 1236],
            validator_credits: verifyData.data.available_validation,
            available_validation: verifyData.data.available_validation
        })
    });

    const validateData = await validateResponse.json();
    console.log('Validation started:', validateData.message);
}

3.2.3 Get Contact Details

Retrieve detailed information about a specific contact, including personal information, associated notes, and formatted display data. This endpoint provides a complete view of a contact's profile.

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

Request Headers

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

URL Parameters

Parameter Type Required Description
contactId integer Yes Unique identifier of the contact to retrieve

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact details retrieved successfully",
    "data": {
        "id": 1234,
        "first_name": "John",
        "last_name": "Doe",
        "full_name": "John Doe",
        "imgWords": "JD",
        "email": "john.doe@example.com",
        "number": "+19292223344",
        "company": "Tech Solutions Inc",
        "notes": [
            {
                "id": 1,
                "contact_id": 1234,
                "note": "Follow up on product demo",
                "created_at": "2025-10-15T10:30:00.000000Z",
                "updated_at": "2025-10-15T10:30:00.000000Z"
            },
            {
                "id": 2,
                "contact_id": 1234,
                "note": "Interested in enterprise plan",
                "created_at": "2025-10-16T14:20:00.000000Z",
                "updated_at": "2025-10-16T14:20:00.000000Z"
            }
        ]
    },
    "errors": null
}

Response Fields

Field Type Description
id integer Unique identifier for the contact
first_name string Contact's first name
last_name string Contact's last name
full_name string Contact's full name (first + last)
imgWords string Initials for avatar display (first letter of first + last name)
email string Contact's email address (nullable)
number string Contact's phone number with country code
company string Contact's company name (nullable)
notes array Array of note objects associated with this contact

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

{
    "code": 200,
    "success": true,
    "message": "Contact not found",
    "data": null,
    "errors": null
}

Notes Array Structure

Each note object in the notes array contains:

Field Type Description
id integer Unique identifier for the note
contact_id integer ID of the contact this note belongs to
note string The note text content
created_at string Timestamp when note was created (ISO 8601)
updated_at string Timestamp when note was last updated (ISO 8601)

Use Cases

  • Display full contact profile in your application
  • Show contact details modal/popup
  • Retrieve contact information before messaging
  • View contact history and associated notes
  • Get avatar initials for UI display (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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
contact_id integer Yes ID of the contact to add note to (must exist in contacts table)
note string Yes The note content (max 2000 characters)

Example Request

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

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Note added successfully",
    "data": {
        "id": 567,
        "contact_id": 1234,
        "note": "Follow up on product demo scheduled for next week. Customer interested in enterprise plan.",
        "created_at": "2025-10-17T14:30:22.000000Z",
        "updated_at": "2025-10-17T14:30:22.000000Z"
    },
    "errors": null
}

Response Fields

Field Type Description
id integer Unique identifier for the note
contact_id integer ID of the contact this note belongs to
note string The note content
created_at string Timestamp when the note was created (ISO 8601 format)
updated_at string Timestamp when the note was last updated (ISO 8601 format)

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_id": [
            "The contact id field is required."
        ],
        "note": [
            "The note field is required."
        ]
    }
}

Common Validation Errors

  • Required Fields: "The contact id field is required." / "The note field is required."
  • Invalid Contact: "The selected contact id is invalid." (contact doesn't exist)
  • Max Length: "The note must not be greater than 2000 characters."
  • Invalid Type: "The note must be a string."

Use Cases

  • Record customer conversation summaries
  • Add follow-up reminders
  • Track customer preferences and interests
  • Note important dates or deadlines
  • Document customer issues or requests

Example in JavaScript

async function addContactNote(contactId, noteText) {
    const response = await fetch('https://api.texttorrent.com/api/v1/contact/number/note/add', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            contact_id: contactId,
            note: noteText
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Note added:', data.data);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

3.3.2 Update Contact Note

Update the content of an existing contact note. This allows you to modify or add information to previously created notes.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
contact_note_id integer Yes ID of the note to update (must exist in contact_notes table)
note string Yes The updated note content (max 2000 characters)

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Note updated successfully",
    "data": null,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_note_id": [
            "The selected contact note id is invalid."
        ]
    }
}

Common Validation Errors

  • Required Fields: "The contact note id field is required." / "The note field is required."
  • Invalid Note ID: "The selected contact note id is invalid." (note doesn't exist)
  • Max Length: "The note must not be greater than 2000 characters."
  • Invalid Type: "The note must be a string."

Error Response - Update Failed (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Failed to update note",
    "data": null,
    "errors": []
}

Important Notes

  • The response does not return the updated note data (data is null)
  • To see the updated note, retrieve the contact details using GET /contact/number/{contactId}
  • Only the note content can be updated; the contact_id remains unchanged
  • The updated_at timestamp is automatically updated

Example in PHP

$noteId = 567;
$updatedNote = 'Product demo completed successfully...';

$ch = curl_init('https://api.texttorrent.com/api/v1/contact/number/note/update');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-SID: SID....................................',
    'X-API-PUBLIC-KEY: PK.....................................',
    'Content-Type: application/json',
    'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'contact_note_id' => $noteId,
    'note' => $updatedNote
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
if ($data['success']) {
    echo 'Note updated successfully';
}

3.3.3 Delete Contact Note

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

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

Request Headers

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

URL Parameters

Parameter Type Required Description
id integer Yes ID of the note to delete

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Note deleted successfully",
    "data": null,
    "errors": null
}

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

{
    "code": 400,
    "success": false,
    "message": "Failed to delete note",
    "data": null,
    "errors": []
}

Important Notes

  • The note is permanently deleted and cannot be recovered
  • No validation error is returned if the note doesn't exist; instead a 400 error is returned
  • Consider implementing a confirmation step in your application before deletion
  • The response does not return the deleted note data

Example in JavaScript

async function deleteContactNote(noteId) {
    // Show confirmation dialog
    if (!confirm('Are you sure you want to delete this note?')) {
        return;
    }

    const response = await fetch(`https://api.texttorrent.com/api/v1/contact/number/note/${noteId}`, {
        method: 'DELETE',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Note deleted successfully');
        // Refresh the contact details to show updated notes list
        await refreshContactDetails();
    } else {
        console.error('Error:', data.message);
        alert('Failed to delete note');
    }
}

// Usage
deleteContactNote(567);

Best Practices

  • Always show a confirmation dialog before deleting notes
  • Consider implementing soft deletes for better data recovery options
  • Log deletions for audit purposes in your application
  • Refresh the contact details view after successful deletion
  • Handle error cases gracefully in your UI

Notes Management Best Practices

Organizing Notes Effectively

  • Use Timestamps: Include dates in your notes for time-sensitive information
  • Be Specific: Add actionable details rather than vague descriptions
  • Keep Updated: Regularly update notes after customer interactions
  • Tag Actions: Use prefixes like "TODO:", "FOLLOW-UP:", "COMPLETED:" for clarity

Note Character Limit

Each note supports up to 2000 characters. If you need to store more information:

  • Create multiple notes for the same contact
  • Summarize older conversations and keep detailed notes for recent interactions
  • Use the contact's company field for organizational details

Retrieving Notes

Notes are automatically included when you call the Get Contact Details endpoint (GET /api/v1/contact/number/{contactId}). The response includes all notes associated with the contact in the notes array.

Example: Complete Note Workflow

// 1. Add a new note
const newNote = await addContactNote(1234, 'Initial contact - interested in demo');

// 2. Later, update the note with more details
await updateContactNote(newNote.id, 'Initial contact - demo scheduled for Oct 20, 2PM EST');

// 3. Retrieve all notes for the contact
const contact = await getContactDetails(1234);
console.log('All notes:', contact.notes);

// 4. If needed, delete outdated notes
await deleteContactNote(oldNoteId);

3.4 Contact Folders

The Contact Folders API provides endpoints to organize contacts into custom folders (also called tags in some responses). Folders help you categorize and group contacts for better organization and targeted communication.

Key Features:

  • Create custom folders to organize contacts
  • List all folders with search functionality
  • Update folder names
  • Delete folders when no longer needed
  • Assign contacts to folders
  • Search folders by name

3.4.1 List Contact Folders

Retrieve all contact folders associated with your account. Optionally filter folders using a search query to find specific folders by name.

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

Request Headers

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

Query Parameters

Parameter Type Required Description
search string No Search term to filter folders by name (partial match)

Example Request - Get All Folders

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

Example Request - Search Folders

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Folders retrieved successfully",
    "data": [
        {
            "id": 5,
            "name": "VIP Clients",
            "created_at": "2025-09-10T08:30:00.000000Z"
        },
        {
            "id": 8,
            "name": "Hot Leads",
            "created_at": "2025-10-01T14:22:00.000000Z"
        },
        {
            "id": 12,
            "name": "Newsletter Subscribers",
            "created_at": "2025-10-15T09:15:00.000000Z"
        }
    ],
    "errors": null
}

Response Fields

Field Type Description
id integer Unique identifier for the folder
name string Name of the folder
created_at string Timestamp when the folder was created (ISO 8601 format)

Error Response - Failed to Retrieve (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Failed to retrieve folders",
    "data": null,
    "errors": []
}

Search Behavior

  • Search is case-insensitive
  • Performs partial matching (LIKE query with wildcards)
  • Example: searching "VIP" will match "VIP Clients", "Super VIP", "VIP Members"
  • Returns empty array if no folders match the search

Important Notes

  • Only folders belonging to your account are returned
  • Sub-accounts see only their own folders
  • Folders are returned in the order they were created
  • No pagination - all matching folders are returned

3.4.2 Create Contact Folder

Create a new folder to organize your contacts. Folder names can be up to 255 characters and help you categorize contacts for better management.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
folder_name string Yes Name of the folder to create (max 255 characters)

Example Request

curl -X POST "https://api.texttorrent.com/api/v1/contact/number/folder/create" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "folder_name": "VIP Clients"
  }'

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Folder created successfully",
    "data": {
        "id": 15,
        "name": "VIP Clients",
        "user_id": 1,
        "created_at": "2025-10-17T15:30:45.000000Z",
        "updated_at": "2025-10-17T15:30:45.000000Z"
    },
    "errors": null
}

Response Fields

Field Type Description
id integer Unique identifier for the newly created folder
name string Name of the folder
user_id integer ID of the user who owns this folder
created_at string Timestamp when the folder was created (ISO 8601 format)
updated_at string Timestamp when the folder was last updated (ISO 8601 format)

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "folder_name": [
            "The folder name field is required."
        ]
    }
}

Common Validation Errors

  • Required: "The folder name field is required."
  • Max Length: "The folder name must not be greater than 255 characters."
  • Invalid Type: "The folder name must be a string."

Error Response - Creation Failed (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Failed to create folder",
    "data": null,
    "errors": []
}

Example in JavaScript

async function createFolder(folderName) {
    const response = await fetch('https://api.texttorrent.com/api/v1/contact/number/folder/create', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            folder_name: folderName
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Folder created:', data.data);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
createFolder('VIP Clients');

3.4.3 Update Contact Folder

Update the name of an existing contact folder. Note that folder responses may show "Tag" in messages (folders and tags are used interchangeably in the system).

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

URL Parameters

Parameter Type Required Description
id integer Yes ID of the folder to update

Request Body Parameters

Parameter Type Required Description
folder_name string Yes New name for the folder (max 255 characters)

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Tag updated successfully",
    "data": {
        "id": 15,
        "name": "Premium VIP Clients",
        "user_id": 1,
        "created_at": "2025-10-17T15:30:45.000000Z",
        "updated_at": "2025-10-17T16:20:30.000000Z"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "folder_name": [
            "The folder name field is required."
        ]
    }
}

Error Response - Update Failed (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Failed to update folder",
    "data": null,
    "errors": []
}

Important Notes

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

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

URL Parameters

Parameter Type Required Description
id integer Yes ID of the folder to delete

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Tag deleted successfully",
    "data": null,
    "errors": null
}

Error Response - Delete Failed (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Failed to delete tag",
    "data": null,
    "errors": []
}

Important Notes

  • The response message says "Tag deleted successfully" (folders/tags are interchangeable)
  • Deleting a folder does NOT delete contacts assigned to it
  • Contacts in the deleted folder will have their folder_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

async function deleteFolder(folderId) {
    if (!confirm('Are you sure you want to delete this folder? Contacts will not be deleted.')) {
        return;
    }

    const response = await fetch(`https://api.texttorrent.com/api/v1/contact/number/folder/${folderId}`, {
        method: 'DELETE',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Folder deleted successfully');
        // Refresh folder list
        await loadFolders();
    } else {
        console.error('Error:', data.message);
        alert('Failed to delete folder');
    }
}

// Usage
deleteFolder(15);

3.4.5 Assign Contact to Folder

Assign a contact to a specific folder for better organization. You can also use this endpoint to move a contact from one folder to another by providing a different folder ID.

POST /api/v1/contact/add/folder

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
contact_id integer Yes ID of the contact to assign (must exist in contacts table)
folder_id integer Yes ID of the folder to assign contact to (must exist in contact_folders table)

Example Request

curl -X POST "https://api.texttorrent.com/api/v1/contact/add/folder" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "contact_id": 1234,
    "folder_id": 15
  }'

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact added to folder successfully",
    "data": {
        "id": 1234,
        "first_name": "John",
        "last_name": "Doe",
        "number": "+19292223344",
        "email": "john.doe@example.com",
        "company": "Tech Solutions Inc",
        "list_id": 45,
        "folder_id": 15,
        "user_id": 1,
        "blacklisted": 0,
        "valid": 1,
        "validation_process": 1,
        "created_at": "2025-09-15T10:30:00.000000Z",
        "updated_at": "2025-10-17T16:45:22.000000Z"
    },
    "errors": null
}

Response Fields

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

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_id": [
            "The selected contact id is invalid."
        ],
        "folder_id": [
            "The selected folder id is invalid."
        ]
    }
}

Common Validation Errors

  • Required Fields: "The contact id field is required." / "The folder id field is required."
  • Invalid Contact: "The selected contact id is invalid." (contact doesn't exist)
  • Invalid Folder: "The selected folder id is invalid." (folder doesn't exist)

Error Response - Assignment Failed (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Contact removed from folder successfully",
    "data": null,
    "errors": []
}

Note: The error message says "Contact removed from folder successfully" even though it's an error response. This appears to be a bug in the implementation.

Use Cases

  • Organize new contacts into appropriate folders
  • Move contacts between folders (change folder_id)
  • Tag contacts for specific campaigns
  • Group contacts by category, status, or priority

Important Notes

  • A contact can only be in one folder at a time
  • Assigning a contact to a new folder automatically removes it from the previous folder
  • Both contact and folder must exist and belong to your account
  • Returns the complete updated contact object
  • The contact's updated_at timestamp is automatically updated

Example in PHP

function assignContactToFolder($contactId, $folderId) {
    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/add/folder');

    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-API-SID: SID....................................',
        'X-API-PUBLIC-KEY: PK.....................................',
        'Content-Type: application/json',
        'Accept: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'contact_id' => $contactId,
        'folder_id' => $folderId
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);

    if ($data['success']) {
        echo 'Contact assigned to folder successfully';
        return $data['data'];
    } else {
        echo 'Error: ' . $data['message'];
        return null;
    }
}

// Usage
assignContactToFolder(1234, 15);

Folder Management Best Practices

Organizing with Folders

  • Use Descriptive Names: Choose clear, meaningful folder names like "Hot Leads Q4" instead of "Folder 1"
  • Keep It Simple: Don't create too many folders - aim for 5-10 main categories
  • Combine with Lists: Use both lists and folders for multi-level organization
  • Regular Cleanup: Delete unused folders to keep your workspace organized

Folder vs List

When to use Folders:

  • Sub-categorization within a list (e.g., "VIP", "Premium", "Standard" within "Customers" list)
  • Temporary groupings (e.g., "October Campaign", "Webinar Attendees")
  • Status-based organization (e.g., "Active", "Inactive", "Follow-up Needed")

When to use Lists:

  • Main categorization of contacts (e.g., "Customers", "Prospects", "Partners")
  • Long-term organization
  • Campaign targets

Common Folder Organization Patterns

List: Customers
├── Folder: VIP Clients
├── Folder: Regular Customers
└── Folder: Churned Customers

List: Leads
├── Folder: Hot Leads
├── Folder: Warm Leads
└── Folder: Cold Leads

List: Event Contacts
├── Folder: 2025 Q1 Webinar
├── Folder: 2025 Q2 Conference
└── Folder: 2025 Q3 Workshop

Workflow Example

// Complete folder workflow
async function organizeFolderWorkflow() {
    // 1. Create folders for organization
    const vipFolder = await createFolder('VIP Clients');
    const hotLeadsFolder = await createFolder('Hot Leads');

    // 2. List all folders to verify
    const allFolders = await listFolders();
    console.log('All folders:', allFolders);

    // 3. Assign contacts to folders
    await assignContactToFolder(1234, vipFolder.id);
    await assignContactToFolder(1235, hotLeadsFolder.id);

    // 4. Search for specific folder
    const vipFolders = await listFolders('VIP');
    console.log('VIP folders:', vipFolders);

    // 5. Update folder name if needed
    await updateFolder(vipFolder.id, 'Premium VIP Clients');

    // 6. Clean up unused folders
    // await deleteFolder(oldFolderId);
}

3.5 Contact CRUD Operations

The Contact CRUD (Create, Read, Update, Delete) API provides comprehensive endpoints to manage individual contacts in your account. These endpoints allow you to add new contacts, update existing contact information, delete contacts, and import contacts in bulk from CSV files.

Key Features:

  • Add individual contacts with detailed information
  • Update contact details (name, phone, email, company)
  • Bulk delete multiple contacts at once
  • Import contacts from CSV files with custom column mapping
  • Support for blacklisted and unlisted contacts
  • List size limit: 10,000 contacts per list
  • Automatic US phone number formatting (+1 prefix)
ℹ️ 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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
first_name string Yes Contact's first name (max 50 characters)
last_name string No Contact's last name (max 50 characters)
number string Yes Phone number (7-14 digits, automatically prefixed with +1)
email string No Email address (max 100 characters, must be valid email format)
company string No Company name (max 100 characters)
list_id integer Yes ID of the contact list (must exist in contact_lists table)
type string No Contact type: default, blacklisted, or unlisted

Contact Types

  • default: Normal contact added to the specified list (default behavior)
  • blacklisted: Contact is blacklisted and won't receive messages (list_id is ignored)
  • unlisted: Contact exists but is not assigned to any list (list_id is ignored)

Example Request

curl -X POST "https://api.texttorrent.com/api/v1/contact/add" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "first_name": "John",
    "last_name": "Doe",
    "number": "2025551234",
    "email": "john.doe@example.com",
    "company": "Tech Solutions Inc",
    "list_id": 45,
    "type": "default"
  }'

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Contact added successfully",
    "data": {
        "id": 5678,
        "first_name": "John",
        "last_name": "Doe",
        "number": "+12025551234",
        "email": "john.doe@example.com",
        "company": "Tech Solutions Inc",
        "list_id": 45,
        "folder_id": null,
        "user_id": 1,
        "blacklisted": 0,
        "valid": 0,
        "validation_process": 0,
        "created_at": "2025-10-17T18:30:45.000000Z",
        "updated_at": "2025-10-17T18:30:45.000000Z"
    },
    "errors": null
}

Response Fields

Field Type Description
id integer Unique identifier for the contact
number string Phone number with +1 prefix automatically added
list_id integer|null ID of the list (null for blacklisted/unlisted contacts)
blacklisted integer 1 if blacklisted, 0 otherwise
valid integer 1 if number validated, 0 otherwise
validation_process integer 1 if validation completed, 0 otherwise

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "first_name": [
            "The first name field is required."
        ],
        "number": [
            "The number field is required.",
            "The number format is invalid."
        ],
        "list_id": [
            "The selected list id is invalid."
        ]
    }
}

Common Validation Errors

  • Required Fields: "The first name field is required." / "The number field is required." / "The list id field is required."
  • Invalid Format: "The number format is invalid." (must match phone number regex)
  • Invalid List: "The selected list id is invalid." (list doesn't exist)
  • Max Length: "The first name must not be greater than 50 characters."
  • Email Format: "The email must be a valid email address."
  • Invalid Type: "The selected type is invalid." (must be default, blacklisted, or unlisted)

Error Response - List Full (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "List can't have more than 10000 contacts.",
    "data": null,
    "errors": null
}

Error Response - Failed to Add (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Failed to add contact",
    "data": null,
    "errors": []
}

Example in JavaScript

async function addContact(contactData) {
    const response = await fetch('https://api.texttorrent.com/api/v1/contact/add', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            first_name: contactData.firstName,
            last_name: contactData.lastName,
            number: contactData.phoneNumber,
            email: contactData.email,
            company: contactData.company,
            list_id: contactData.listId,
            type: contactData.type || 'default'
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Contact added:', data.data);
        return data.data;
    } else {
        console.error('Error:', data.message);
        if (data.errors) {
            Object.entries(data.errors).forEach(([field, messages]) => {
                console.error(`${field}:`, messages.join(', '));
            });
        }
        throw new Error(data.message);
    }
}

// Usage
addContact({
    firstName: 'John',
    lastName: 'Doe',
    phoneNumber: '2025551234',
    email: 'john.doe@example.com',
    company: 'Tech Solutions Inc',
    listId: 45
});

Important Notes

  • Phone numbers are automatically prefixed with "+1" - do not include it in your request
  • Each list is limited to 10,000 contacts maximum
  • Blacklisted contacts ignore the list_id parameter and set blacklisted flag to 1
  • Unlisted contacts ignore the list_id parameter but don't set blacklisted flag
  • Phone number must match regex: ^\+?[0-9]{1,4}?[0-9]{7,14}$
  • New contacts have valid=0 and validation_process=0 by default

3.5.2 Update Contact

Update an existing contact's information. You can modify the contact's name, phone number, email, and company details. The phone number will be automatically formatted with the +1 prefix.

PUT /api/v1/contact/{id}

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

URL Parameters

Parameter Type Required Description
id integer Yes ID of the contact to update

Request Body Parameters

Parameter Type Required Description
first_name string Yes Contact's first name (max 50 characters)
last_name string No Contact's last name (max 50 characters)
mobile_number string Yes Phone number (max 20 characters, automatically prefixed with +1)
email string No Email address (max 100 characters, must be valid email format)
company string No Company name (max 100 characters)

Example Request

curl -X PUT "https://api.texttorrent.com/api/v1/contact/5678" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "first_name": "John",
    "last_name": "Smith",
    "mobile_number": "2025559999",
    "email": "john.smith@newcompany.com",
    "company": "New Tech Corp"
  }'

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact updated successfully",
    "data": {
        "id": 5678,
        "first_name": "John",
        "last_name": "Smith",
        "number": "+12025559999",
        "email": "john.smith@newcompany.com",
        "company": "New Tech Corp",
        "list_id": 45,
        "folder_id": null,
        "user_id": 1,
        "blacklisted": 0,
        "valid": 0,
        "validation_process": 0,
        "created_at": "2025-10-17T18:30:45.000000Z",
        "updated_at": "2025-10-17T19:15:22.000000Z"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "first_name": [
            "The first name field is required."
        ],
        "mobile_number": [
            "The mobile number field is required."
        ],
        "email": [
            "The email must be a valid email address."
        ]
    }
}

Error Response - Update Failed (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Failed to update contact",
    "data": null,
    "errors": []
}

Important Notes

  • Field name is mobile_number (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

function updateContact($contactId, $contactData) {
    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/' . $contactId);

    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-API-SID: SID....................................',
        'X-API-PUBLIC-KEY: PK.....................................',
        'Content-Type: application/json',
        'Accept: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'first_name' => $contactData['first_name'],
        'last_name' => $contactData['last_name'] ?? null,
        'mobile_number' => $contactData['mobile_number'],
        'email' => $contactData['email'] ?? null,
        'company' => $contactData['company'] ?? null
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);

    if ($data['success']) {
        echo 'Contact updated successfully';
        return $data['data'];
    } else {
        echo 'Error: ' . $data['message'];
        return null;
    }
}

// Usage
updateContact(5678, [
    'first_name' => 'John',
    'last_name' => 'Smith',
    'mobile_number' => '2025559999',
    'email' => 'john.smith@newcompany.com',
    'company' => 'New Tech Corp'
]);

3.5.3 Delete Contacts

Delete one or more contacts from your account. This endpoint supports bulk deletion by accepting an array of contact IDs. Deleted contacts are permanently removed and cannot be recovered.

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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
contact_ids array Yes Array of contact IDs to delete (all must exist in contacts table)

Example Request - Delete Single Contact

curl -X DELETE "https://api.texttorrent.com/api/v1/contact/" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "contact_ids": [5678]
  }'

Example Request - Bulk Delete

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contacts deleted successfully",
    "data": null,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_ids": [
            "The contact ids field is required.",
            "The contact ids must be an array."
        ],
        "contact_ids.0": [
            "The selected contact ids.0 is invalid."
        ]
    }
}

Common Validation Errors

  • Required: "The contact ids field is required."
  • Invalid Type: "The contact ids must be an array."
  • Invalid ID: "The selected contact ids.X is invalid." (contact doesn't exist)
  • Invalid Format: Each element must be an integer

Error Response - Delete Failed (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Failed to delete contacts",
    "data": null,
    "errors": []
}

Example in JavaScript

async function deleteContacts(contactIds) {
    // Confirm deletion
    if (!confirm(`Are you sure you want to delete ${contactIds.length} contact(s)? This cannot be undone.`)) {
        return;
    }

    const response = await fetch('https://api.texttorrent.com/api/v1/contact/', {
        method: 'DELETE',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            contact_ids: contactIds
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Contacts deleted:', contactIds.length);
        // Refresh contact list
        await loadContacts();
    } else {
        console.error('Error:', data.message);
        alert('Failed to delete contacts');
    }
}

// Usage - Delete single contact
deleteContacts([5678]);

// Usage - Bulk delete
deleteContacts([5678, 5679, 5680, 5681]);

Important Notes

  • Supports bulk deletion - provide multiple contact IDs in the array
  • All provided contact IDs must exist and belong to your account
  • Deletion is permanent and cannot be undone
  • Associated data (notes, folder assignments) will also be removed
  • If any contact ID is invalid, the entire operation fails
  • No partial deletions - it's all or nothing

3.5.4 Import Contacts from CSV

Import contacts in bulk from CSV files. This endpoint supports custom column mapping, allowing you to specify which CSV columns contain first name, last name, phone number, email, company, and up to 3 additional custom fields. The import process is chunked for large files.

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

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

Request Parameters (multipart/form-data)

Parameter Type Required Description
chunks[] file[] Yes Array of CSV file chunks (for large file uploads)
file_names[] string[] Yes Array of file names corresponding to each chunk (max 255 chars each)
total_records integer Yes Total number of records in the CSV file
file_size numeric Yes Total file size in bytes
list_id integer No ID of existing list to import contacts into (creates new list if omitted)
first_name_column string Yes CSV column name containing first names (max 255 chars)
last_name_column string No CSV column name containing last names (max 255 chars)
email_address_column string No CSV column name containing email addresses (max 255 chars)
company_column string No CSV column name containing company names (max 255 chars)
phone_number_column string Yes CSV column name containing phone numbers (max 255 chars)
additional_1_column string No CSV column name for additional field 1 (max 255 chars)
additional_2_column string No CSV column name for additional field 2 (max 255 chars)
additional_3_column string No CSV column name for additional field 3 (max 255 chars)

List Creation Behavior

  • If list_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

curl -X POST "https://api.texttorrent.com/api/v1/contact/import" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Accept: application/json" \
  -F "chunks[]=@contacts.csv" \
  -F "file_names[]=contacts.csv" \
  -F "total_records=150" \
  -F "file_size=25600" \
  -F "list_id=45" \
  -F "first_name_column=First Name" \
  -F "last_name_column=Last Name" \
  -F "email_address_column=Email" \
  -F "company_column=Company" \
  -F "phone_number_column=Phone"

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contacts imported successfully",
    "data": {
        "imported_count": 150,
        "list_id": 45,
        "list_name": "Customers"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "chunks": [
            "The chunks field is required.",
            "The chunks must be an array."
        ],
        "first_name_column": [
            "The first name column field is required."
        ],
        "phone_number_column": [
            "The phone number column field is required."
        ],
        "list_id": [
            "The selected list id is invalid."
        ]
    }
}

Download Sample CSV

Get a sample CSV file to understand the expected format:

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

Sample Download Response

{
    "code": 200,
    "success": true,
    "message": "Sample file download",
    "data": {
        "url": "https://api.texttorrent.com/files/Sample Contact File.csv"
    },
    "errors": null
}

Example in JavaScript with File Upload

async function importContacts(file, columnMapping, listId = null) {
    const formData = new FormData();

    // Add file chunks (simplified - single file in this example)
    formData.append('chunks[]', file);
    formData.append('file_names[]', file.name);
    formData.append('total_records', 100); // Calculate from CSV
    formData.append('file_size', file.size);

    // Add list ID if provided
    if (listId) {
        formData.append('list_id', listId);
    }

    // Add column mappings
    formData.append('first_name_column', columnMapping.firstName);
    formData.append('phone_number_column', columnMapping.phoneNumber);

    // Optional columns
    if (columnMapping.lastName) {
        formData.append('last_name_column', columnMapping.lastName);
    }
    if (columnMapping.email) {
        formData.append('email_address_column', columnMapping.email);
    }
    if (columnMapping.company) {
        formData.append('company_column', columnMapping.company);
    }

    const response = await fetch('https://api.texttorrent.com/api/v1/contact/import', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
            // Note: Don't set Content-Type for FormData, browser sets it automatically
        },
        body: formData
    });

    const data = await response.json();

    if (data.success) {
        console.log('Import successful:', data.data);
        alert(`Successfully imported ${data.data.imported_count} contacts`);
        return data.data;
    } else {
        console.error('Import failed:', data.message);
        throw new Error(data.message);
    }
}

// Usage with file input
document.getElementById('csvFile').addEventListener('change', (e) => {
    const file = e.target.files[0];
    importContacts(file, {
        firstName: 'First Name',
        lastName: 'Last Name',
        phoneNumber: 'Phone',
        email: 'Email',
        company: 'Company'
    }, 45);
});

Important Notes

  • CSV files can be chunked for large uploads (send as array of chunks)
  • Column names are case-sensitive and must match CSV headers exactly
  • Only first_name_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

// Complete contact management workflow
class ContactManager {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1';
    }

    async addContact(contact) {
        // Validate before adding
        if (!this.validatePhone(contact.number)) {
            throw new Error('Invalid phone number format');
        }

        // Check list size
        const listSize = await this.getListSize(contact.list_id);
        if (listSize >= 10000) {
            throw new Error('List is full (10,000 contacts max)');
        }

        // Add contact
        return await this.apiCall('POST', '/contact/add', contact);
    }

    async updateContact(id, updates) {
        return await this.apiCall('PUT', `/contact/${id}`, updates);
    }

    async deleteContacts(contactIds) {
        if (!confirm(`Delete ${contactIds.length} contact(s)?`)) {
            return;
        }
        return await this.apiCall('DELETE', '/contact/', { contact_ids: contactIds });
    }

    async importFromCSV(file, mapping, listId) {
        const formData = new FormData();
        formData.append('chunks[]', file);
        formData.append('file_names[]', file.name);
        formData.append('total_records', await this.countCSVRows(file));
        formData.append('file_size', file.size);

        if (listId) formData.append('list_id', listId);

        Object.entries(mapping).forEach(([key, value]) => {
            if (value) formData.append(key, value);
        });

        return await this.apiCall('POST', '/contact/import', formData, true);
    }

    validatePhone(number) {
        return /^[0-9]{10}$/.test(number);
    }

    async getListSize(listId) {
        const result = await this.apiCall('GET', `/contact/${listId}/contacts`);
        return result.data.total || 0;
    }

    async countCSVRows(file) {
        const text = await file.text();
        return text.split('\n').length - 1; // Exclude header
    }

    async apiCall(method, endpoint, data = null, isFormData = false) {
        const headers = {
            'X-API-SID': this.apiSid,
            'X-API-PUBLIC-KEY': this.apiKey,
            'Accept': 'application/json'
        };

        if (!isFormData) {
            headers['Content-Type'] = 'application/json';
        }

        const options = {
            method,
            headers,
            body: data ? (isFormData ? data : JSON.stringify(data)) : null
        };

        const response = await fetch(this.baseUrl + endpoint, options);
        return await response.json();
    }
}

// Usage
const manager = new ContactManager('SID....................................', 'PK.....................................');

// Add contact
await manager.addContact({
    first_name: 'John',
    last_name: 'Doe',
    number: '2025551234',
    email: 'john@example.com',
    list_id: 45
});

// Update contact
await manager.updateContact(5678, {
    first_name: 'John',
    mobile_number: '2025559999',
    email: 'john.new@example.com'
});

// Delete contacts
await manager.deleteContacts([5678, 5679]);

// Import from CSV
const fileInput = document.getElementById('csvFile');
await manager.importFromCSV(fileInput.files[0], {
    first_name_column: 'First Name',
    phone_number_column: 'Phone',
    email_address_column: 'Email'
}, 45);

3.6 Contact Validator

The Contact Validator API allows you to view and manage phone number validation results. After validating numbers (using the validation endpoint in section 3.2.2), you can retrieve validation history, cleanup invalid numbers from lists, export validation results, and delete validation records.

Key Features:

  • View validation history with detailed results
  • Search validations by list name
  • Process cleanup to remove non-mobile numbers (landline, invalid, etc.)
  • Export validation results to Excel (.xlsx)
  • Delete validation records and associated data
  • Pagination support for large result sets
  • Includes sub-account validation records
ℹ️ 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

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

Query Parameters

Parameter Type Required Description
search string No Search term to filter by list name (partial match)
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)

Example Request - Get All Validations

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

Example Request - With Pagination and Search

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Validator Credits",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 123,
                "list_name": "VIP Clients",
                "total_credits": 150,
                "total_number": 150,
                "total_mobile_numbers": 120,
                "total_landline_numbers": 25,
                "total_other_numbers": 5,
                "invalid_numbers": 0,
                "created_at": "2025-10-15T14:30:00.000000Z",
                "status": "Completed",
                "cleaned": 0
            },
            {
                "id": 122,
                "list_name": "Hot Leads",
                "total_credits": 200,
                "total_number": 200,
                "total_mobile_numbers": 185,
                "total_landline_numbers": 10,
                "total_other_numbers": 3,
                "invalid_numbers": 2,
                "created_at": "2025-10-14T10:15:00.000000Z",
                "status": "Completed",
                "cleaned": 1
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/contact/validator?page=1",
        "from": 1,
        "last_page": 5,
        "last_page_url": "https://api.texttorrent.com/api/v1/contact/validator?page=5",
        "next_page_url": "https://api.texttorrent.com/api/v1/contact/validator?page=2",
        "path": "https://api.texttorrent.com/api/v1/contact/validator",
        "per_page": 10,
        "prev_page_url": null,
        "to": 10,
        "total": 48
    },
    "errors": null
}

Response Fields

Field Type Description
id integer Unique identifier for the validation record
list_name string Name of the contact list that was validated
total_credits integer Number of credits used for this validation
total_number integer Total number of phone numbers validated
total_mobile_numbers integer Count of valid mobile phone numbers
total_landline_numbers integer Count of landline phone numbers
total_other_numbers integer Count of other number types (VoIP, toll-free, etc.)
invalid_numbers integer Count of invalid or unverifiable numbers
status string Validation status (e.g., "Completed", "Processing")
cleaned integer 1 if cleanup processed (non-mobile numbers removed), 0 otherwise
created_at string Timestamp when validation was created (ISO 8601 format)

Pagination Fields

  • current_page: Current page number
  • per_page: Number of items per page
  • total: Total number of validation records
  • last_page: Last available page number
  • next_page_url: URL for next page (null if on last page)
  • prev_page_url: URL for previous page (null if on first page)

Example in JavaScript

async function getValidationHistory(page = 1, limit = 10, search = '') {
    const params = new URLSearchParams({
        page: page,
        limit: limit
    });

    if (search) {
        params.append('search', search);
    }

    const response = await fetch(`https://api.texttorrent.com/api/v1/contact/validator/?${params}`, {
        method: 'GET',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Validation history:', data.data.data);
        console.log(`Page ${data.data.current_page} of ${data.data.last_page}`);
        console.log(`Total validations: ${data.data.total}`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get first page
getValidationHistory();

// Usage - Search with pagination
getValidationHistory(1, 20, 'VIP');

Important Notes

  • Results include validations from your account and all sub-accounts
  • Search filters by list name (case-insensitive, partial match)
  • Results are ordered by ID in descending order (newest first)
  • Use 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

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

URL Parameters

Parameter Type Required Description
id integer Yes ID of the validation record to cleanup

What Gets Deleted

  • Landline numbers: Traditional landline phone numbers
  • VoIP numbers: Internet-based phone numbers
  • Toll-free numbers: 800, 888, 877, etc.
  • Invalid numbers: Numbers that failed validation
  • Other types: Any non-mobile line types

What Gets Kept

  • Mobile numbers: Valid mobile/cell phone numbers

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Cleanup Successful",
    "data": null,
    "errors": null
}

Example in JavaScript

async function processCleanup(validationId) {
    // Confirm cleanup action
    const confirmed = confirm(
        'This will permanently delete all non-mobile numbers from the list. ' +
        'Only mobile numbers will remain. Continue?'
    );

    if (!confirmed) {
        return;
    }

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/contact/validator/process-cleanup/${validationId}`,
        {
            method: 'POST',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Cleanup completed successfully');
        alert('Non-mobile numbers have been removed from the list');
        // Refresh validation history
        await getValidationHistory();
    } else {
        console.error('Cleanup failed:', data.message);
        alert('Failed to process cleanup');
    }
}

// Usage
processCleanup(123);

Important Notes

  • Cleanup is permanent and cannot be undone
  • After cleanup, the validation record is marked as cleaned: 1
  • Only completed validations can be cleaned up
  • Contacts with non-mobile numbers are permanently deleted from the database
  • Associated validation items are also deleted
  • Mobile numbers remain untouched in the list

Use Cases

  • Preparing lists for SMS campaigns (mobile-only)
  • Removing landlines after validation
  • Cleaning up invalid numbers from imported data
  • Ensuring compliance with SMS-only marketing

3.6.3 Export Validation Results

Export validation results to an Excel (.xlsx) file. You can export one or multiple validation records at once. The export includes detailed information about each validated number, including line type, carrier, and validation status.

POST /api/v1/contact/validator/export

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
validation_ids array Yes Array of validation IDs to export (all must exist in number_validations table)

Example Request - Export Single Validation

curl -X POST "https://api.texttorrent.com/api/v1/contact/validator/export" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "validation_ids": [123]
  }'

Example Request - Export Multiple Validations

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Exported successfully",
    "data": {
        "url": "https://your-storage.digitaloceanspaces.com/validation/validation_export_1729180845.xlsx"
    },
    "errors": null
}

Response Fields

Field Type Description
url string Direct download URL for the exported Excel file

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "validation_ids": [
            "The validation ids field is required.",
            "The validation ids must be an array."
        ],
        "validation_ids.0": [
            "The selected validation ids.0 is invalid."
        ]
    }
}

Common Validation Errors

  • Required: "The validation ids field is required."
  • Invalid Type: "The validation ids must be an array."
  • Invalid ID: "The selected validation ids.X is invalid." (validation doesn't exist)
  • Invalid Format: Each element must be an integer

Example in JavaScript

async function exportValidations(validationIds) {
    const response = await fetch('https://api.texttorrent.com/api/v1/contact/validator/export', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            validation_ids: validationIds
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Export successful, downloading...');
        // Trigger download
        window.open(data.data.url, '_blank');
        return data.data.url;
    } else {
        console.error('Export failed:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Export single validation
exportValidations([123]);

// Usage - Export multiple validations
exportValidations([123, 124, 125]);

Example in PHP

function exportValidations($validationIds) {
    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/validator/export');

    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-API-SID: SID....................................',
        'X-API-PUBLIC-KEY: PK.....................................',
        'Content-Type: application/json',
        'Accept: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'validation_ids' => $validationIds
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);

    if ($data['success']) {
        echo 'Download URL: ' . $data['data']['url'];
        return $data['data']['url'];
    } else {
        echo 'Error: ' . $data['message'];
        return null;
    }
}

// Usage
exportValidations([123, 124, 125]);

Export File Contents

The exported Excel file typically includes:

  • Contact name (first name, last name)
  • Phone number
  • Line type (Mobile, Landline, VoIP, etc.)
  • Carrier information
  • Validation status
  • Email address (if available)
  • Company (if available)

Important Notes

  • File is stored on DigitalOcean Spaces (cloud storage)
  • Download URL is publicly accessible - use immediately
  • File name includes timestamp: validation_export_{timestamp}.xlsx
  • Supports bulk export of multiple validation records
  • All validation IDs must exist and belong to your account
  • If any validation ID is invalid, the entire operation fails

3.6.4 Delete Validation Record

Permanently delete a validation record and all associated validation item data. This removes the validation history but does not affect the contacts in the list (unless you've already processed cleanup).

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

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

URL Parameters

Parameter Type Required Description
id integer Yes ID of the validation record to delete

What Gets Deleted

  • Validation record from number_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

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Validation deleted successfully",
    "data": null,
    "errors": null
}

Example in JavaScript

async function deleteValidation(validationId) {
    // Confirm deletion
    if (!confirm('Delete this validation record? This cannot be undone.')) {
        return;
    }

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/contact/validator/${validationId}`,
        {
            method: 'DELETE',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Validation record deleted');
        alert('Validation record deleted successfully');
        // Refresh validation history
        await getValidationHistory();
    } else {
        console.error('Delete failed:', data.message);
        alert('Failed to delete validation record');
    }
}

// Usage
deleteValidation(123);

Important Notes

  • Deletion is permanent and cannot be undone
  • Deletes validation record and all detailed validation items
  • Does NOT delete contacts - they remain in the list
  • If you want to remove invalid numbers, use cleanup endpoint first
  • Useful for cleaning up old validation history

Recommended Workflow

  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

// Complete validation management workflow
class ValidationManager {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1';
    }

    async getHistory(page = 1, search = '') {
        const params = new URLSearchParams({ page, limit: 20 });
        if (search) params.append('search', search);

        return await this.apiCall('GET', `/contact/validator/?${params}`);
    }

    async processCleanup(validationId) {
        const confirmed = confirm(
            'Remove all non-mobile numbers? This cannot be undone.'
        );
        if (!confirmed) return;

        return await this.apiCall('POST', `/contact/validator/process-cleanup/${validationId}`);
    }

    async exportResults(validationIds) {
        const result = await this.apiCall('POST', '/contact/validator/export', {
            validation_ids: validationIds
        });

        if (result.success) {
            // Auto-download
            window.open(result.data.url, '_blank');
        }

        return result;
    }

    async deleteRecord(validationId) {
        const confirmed = confirm('Delete validation record?');
        if (!confirmed) return;

        return await this.apiCall('DELETE', `/contact/validator/${validationId}`);
    }

    async completeWorkflow(validationId) {
        try {
            // 1. Get validation details
            const history = await this.getHistory();
            const validation = history.data.data.find(v => v.id === validationId);

            console.log('Validation stats:', {
                total: validation.total_number,
                mobile: validation.total_mobile_numbers,
                landline: validation.total_landline_numbers,
                invalid: validation.invalid_numbers
            });

            // 2. Export results
            console.log('Exporting results...');
            await this.exportResults([validationId]);

            // 3. Cleanup if needed
            if (validation.total_landline_numbers > 0) {
                console.log('Processing cleanup...');
                await this.processCleanup(validationId);
            }

            // 4. Delete old record
            console.log('Cleaning up history...');
            await this.deleteRecord(validationId);

            console.log('Workflow completed successfully');
        } catch (error) {
            console.error('Workflow failed:', error);
        }
    }

    async apiCall(method, endpoint, data = null) {
        const headers = {
            'X-API-SID': this.apiSid,
            'X-API-PUBLIC-KEY': this.apiKey,
            'Accept': 'application/json'
        };

        if (data) {
            headers['Content-Type'] = 'application/json';
        }

        const response = await fetch(this.baseUrl + endpoint, {
            method,
            headers,
            body: data ? JSON.stringify(data) : null
        });

        return await response.json();
    }
}

// Usage
const validator = new ValidationManager('SID....................................', 'PK.....................................');

// Get validation history
await validator.getHistory(1, 'VIP');

// Process cleanup
await validator.processCleanup(123);

// Export results
await validator.exportResults([123, 124]);

// Delete record
await validator.deleteRecord(123);

// Complete workflow
await validator.completeWorkflow(123);

Cost Management Tips

  • Export validation results before cleanup for record-keeping
  • Monitor validation statistics to identify data quality issues
  • Set up alerts for high invalid number rates
  • Regular cleanup helps reduce wasted message credits
  • Archive old validation records to keep history clean

3.7 Contact Import History

The Contact Import History API provides endpoints to view and manage the history of contact imports performed via CSV file uploads. Track import status, view statistics, and download reports of skipped numbers during the import process.

Key Features:

  • View complete import history with status tracking
  • Monitor import progress (Pending, Processing, Completed, Failed)
  • Track total records imported and skipped
  • Download PDF reports of skipped numbers
  • View remarks and error messages for failed imports
  • Pagination support for large import history
ℹ️ 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

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

Query Parameters

Parameter Type Required Description
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)

Example Request - Get All Imports

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

Example Request - With Pagination

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact imports fetched successfully",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 45,
                "name": "customers.csv",
                "file": "imports/customers_1729180845.csv",
                "status": 2,
                "status_text": "Completed",
                "remarks": null,
                "list_name": "Customers",
                "total_records": 500,
                "skipped": 12,
                "skipped_file_url": "https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx",
                "stored": 488,
                "created_at": "2025-10-15T14:30:00.000000Z"
            },
            {
                "id": 44,
                "name": "leads.csv",
                "file": "imports/leads_1729094400.csv",
                "status": 2,
                "status_text": "Completed",
                "remarks": null,
                "list_name": "Hot Leads",
                "total_records": 250,
                "skipped": 0,
                "skipped_file_url": null,
                "stored": 250,
                "created_at": "2025-10-14T10:15:00.000000Z"
            },
            {
                "id": 43,
                "name": "prospects.csv",
                "file": "imports/prospects_1729008000.csv",
                "status": 3,
                "status_text": "Failed",
                "remarks": "Invalid CSV format",
                "list_name": null,
                "total_records": 0,
                "skipped": 0,
                "skipped_file_url": null,
                "stored": 0,
                "created_at": "2025-10-13T08:00:00.000000Z"
            },
            {
                "id": 42,
                "name": "contacts.csv",
                "file": "imports/contacts_1728921600.csv",
                "status": 1,
                "status_text": "Processing",
                "remarks": null,
                "list_name": "Contacts",
                "total_records": 1000,
                "skipped": 0,
                "skipped_file_url": null,
                "stored": 450,
                "created_at": "2025-10-12T06:00:00.000000Z"
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/contact/import?page=1",
        "from": 1,
        "last_page": 3,
        "last_page_url": "https://api.texttorrent.com/api/v1/contact/import?page=3",
        "next_page_url": "https://api.texttorrent.com/api/v1/contact/import?page=2",
        "path": "https://api.texttorrent.com/api/v1/contact/import",
        "per_page": 10,
        "prev_page_url": null,
        "to": 10,
        "total": 28
    },
    "errors": null
}

Response Fields

Field Type Description
id integer Unique identifier for the import record
name string Original filename of the uploaded CSV
file string Stored file path in the system
status integer Status code: 0=Pending, 1=Processing, 2=Completed, 3=Failed
status_text string Human-readable status: "Pending", "Processing", "Completed", "Failed"
remarks string|null Error message or notes (populated for failed imports)
list_name string|null Name of the contact list where contacts were imported
total_records integer Total number of records in the CSV file
skipped integer Number of records that were skipped during import
skipped_file_url string|null URL to download Excel file containing skipped records (null if no skips)
stored integer Number of contacts successfully imported and stored
created_at string Timestamp when import was initiated (ISO 8601 format)

Import Status Values

Status Code Status Text Description
0 Pending Import queued but not yet started
1 Processing Import currently in progress
2 Completed Import finished successfully
3 Failed Import encountered an error and failed

Understanding Skipped Records

Records may be skipped during import for various reasons:

  • Duplicate Numbers: Phone number already exists in the list
  • Invalid Format: Phone number doesn't match required format
  • Missing Required Fields: First name or phone number is empty
  • List Full: Target list has reached 10,000 contact limit
  • Validation Errors: Data doesn't pass validation rules

Example in JavaScript

async function getImportHistory(page = 1, limit = 10) {
    const params = new URLSearchParams({
        page: page,
        limit: limit
    });

    const response = await fetch(`https://api.texttorrent.com/api/v1/contact/import/?${params}`, {
        method: 'GET',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Import history:', data.data.data);

        // Display summary
        data.data.data.forEach(importRecord => {
            console.log(`Import ${importRecord.id}: ${importRecord.name}`);
            console.log(`  Status: ${importRecord.status_text}`);
            console.log(`  Total: ${importRecord.total_records}, Stored: ${importRecord.stored}, Skipped: ${importRecord.skipped}`);

            if (importRecord.skipped > 0) {
                console.log(`  Skipped file: ${importRecord.skipped_file_url}`);
            }

            if (importRecord.status === 3) {
                console.log(`  Error: ${importRecord.remarks}`);
            }
        });

        console.log(`Page ${data.data.current_page} of ${data.data.last_page}`);
        console.log(`Total imports: ${data.data.total}`);

        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get first page
getImportHistory();

// Usage - Get with pagination
getImportHistory(1, 20);

Important Notes

  • Results are ordered by ID in descending order (newest first)
  • Only imports belonging to your account are returned
  • Import records are created immediately when CSV upload begins
  • Status updates in real-time as import progresses
  • Skipped file URL is only available if records were skipped
  • Failed imports have error details in the remarks field

Monitoring Import Progress

// Poll import status until completion
async function monitorImport(importId) {
    let status = 1; // Processing

    while (status === 0 || status === 1) {
        const history = await getImportHistory();
        const importRecord = history.data.find(imp => imp.id === importId);

        if (!importRecord) {
            throw new Error('Import record not found');
        }

        status = importRecord.status;

        console.log(`Import ${importId}: ${importRecord.status_text} - ${importRecord.stored}/${importRecord.total_records} records`);

        if (status === 2) {
            console.log('Import completed successfully');
            if (importRecord.skipped > 0) {
                console.log(`${importRecord.skipped} records were skipped`);
                console.log(`Download skipped records: ${importRecord.skipped_file_url}`);
            }
            return importRecord;
        } else if (status === 3) {
            console.error('Import failed:', importRecord.remarks);
            throw new Error(importRecord.remarks);
        }

        // Wait 5 seconds before checking again
        await new Promise(resolve => setTimeout(resolve, 5000));
    }
}

// Usage
monitorImport(45);

3.7.2 Download Skipped Numbers PDF

Download a PDF report of contacts that were skipped during a CSV import. The report includes details about why each record was skipped, helping you identify and fix data issues for re-import.

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

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

Request Body Parameters

Parameter Type Required Description
url string Yes URL of the skipped file (obtained from import history skipped_file_url)

Example Request

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

Success Response (200 OK)

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

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

[PDF binary data]

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "url": [
            "The url field is required.",
            "The url must be a valid URL."
        ]
    }
}

Common Validation Errors

  • Required: "The url field is required."
  • Invalid Format: "The url must be a valid URL."

PDF Report Contents

The generated PDF typically includes:

  • Import Summary: Total records, imported, and skipped counts
  • Skipped Records Table: List of all skipped contacts with details
  • Skip Reasons: Why each record was skipped (duplicate, invalid format, etc.)
  • Contact Information: Name, phone number, email, company (if available)
  • Timestamp: When the import was performed

Example in JavaScript

async function downloadSkippedPDF(skippedFileUrl) {
    const response = await fetch('https://api.texttorrent.com/api/v1/contact/import/pdf/download', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/pdf'
        },
        body: JSON.stringify({
            url: skippedFileUrl
        })
    });

    if (response.ok) {
        // Convert response to blob
        const blob = await response.blob();

        // Create download link
        const downloadUrl = window.URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = downloadUrl;
        link.download = `skipped_numbers_${Date.now()}.pdf`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);

        // Clean up
        window.URL.revokeObjectURL(downloadUrl);

        console.log('PDF downloaded successfully');
    } else {
        const error = await response.json();
        console.error('Download failed:', error.message);
        throw new Error(error.message);
    }
}

// Usage
downloadSkippedPDF('https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx');

Example in PHP

function downloadSkippedPDF($skippedFileUrl, $outputPath) {
    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/import/pdf/download');

    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-API-SID: SID....................................',
        'X-API-PUBLIC-KEY: PK.....................................',
        'Content-Type: application/json',
        'Accept: application/pdf'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'url' => $skippedFileUrl
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $pdfContent = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode === 200) {
        file_put_contents($outputPath, $pdfContent);
        echo "PDF saved to: $outputPath";
        return true;
    } else {
        $error = json_decode($pdfContent, true);
        echo "Error: " . ($error['message'] ?? 'Unknown error');
        return false;
    }
}

// Usage
downloadSkippedPDF(
    'https://your-storage.digitaloceanspaces.com/skipped/customers_skipped_1729180845.xlsx',
    'skipped_numbers.pdf'
);

Workflow Example

// Complete workflow: Get import history and download skipped reports
async function reviewImportResults(importId) {
    // 1. Get import history
    const history = await getImportHistory();
    const importRecord = history.data.find(imp => imp.id === importId);

    if (!importRecord) {
        throw new Error('Import not found');
    }

    console.log(`Import: ${importRecord.name}`);
    console.log(`Status: ${importRecord.status_text}`);
    console.log(`Total: ${importRecord.total_records}`);
    console.log(`Stored: ${importRecord.stored}`);
    console.log(`Skipped: ${importRecord.skipped}`);

    // 2. Download skipped numbers PDF if any were skipped
    if (importRecord.skipped > 0 && importRecord.skipped_file_url) {
        console.log('Downloading skipped numbers report...');
        await downloadSkippedPDF(importRecord.skipped_file_url);
        console.log('Review the PDF to see why records were skipped');
    } else {
        console.log('No records were skipped - import was 100% successful!');
    }

    // 3. Handle failed imports
    if (importRecord.status === 3) {
        console.error('Import failed:', importRecord.remarks);
        console.log('Please fix the issues and try importing again');
    }
}

// Usage
reviewImportResults(45);

Important Notes

  • URL must be a valid, accessible skipped file URL from import history
  • PDF is generated on-the-fly from the Excel skipped file
  • Response is a binary PDF file (not JSON)
  • Set Accept: application/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 Reason Fix
Duplicate Number Remove duplicates from CSV or choose different list
Invalid Phone Format Ensure numbers are 10 digits without +1 prefix
Missing First Name Add first names or use placeholder like "Contact"
List Full (10,000 limit) Create new list or cleanup existing list
Invalid Email Format Fix email syntax or leave blank

Complete Import Management Workflow

// Complete import management system
class ImportManager {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1';
    }

    async getHistory(page = 1, limit = 10) {
        const params = new URLSearchParams({ page, limit });
        return await this.apiCall('GET', `/contact/import/?${params}`);
    }

    async monitorImport(importId, onProgress) {
        let status = 1; // Processing
        let lastStored = 0;

        while (status === 0 || status === 1) {
            const history = await this.getHistory();
            const imp = history.data.data.find(i => i.id === importId);

            if (!imp) throw new Error('Import not found');

            status = imp.status;

            // Report progress if changed
            if (imp.stored !== lastStored) {
                lastStored = imp.stored;
                const progress = (imp.stored / imp.total_records * 100).toFixed(1);
                onProgress?.({
                    status: imp.status_text,
                    progress: progress,
                    stored: imp.stored,
                    total: imp.total_records,
                    skipped: imp.skipped
                });
            }

            if (status === 2) {
                // Completed
                return {
                    success: true,
                    import: imp,
                    message: `Import completed: ${imp.stored} contacts imported, ${imp.skipped} skipped`
                };
            } else if (status === 3) {
                // Failed
                return {
                    success: false,
                    import: imp,
                    message: `Import failed: ${imp.remarks}`
                };
            }

            // Wait 5 seconds before checking again
            await new Promise(resolve => setTimeout(resolve, 5000));
        }
    }

    async downloadSkippedPDF(skippedUrl) {
        const response = await fetch(`${this.baseUrl}/contact/import/pdf/download`, {
            method: 'POST',
            headers: {
                'X-API-SID': this.apiSid,
                'X-API-PUBLIC-KEY': this.apiKey,
                'Content-Type': 'application/json',
                'Accept': 'application/pdf'
            },
            body: JSON.stringify({ url: skippedUrl })
        });

        if (response.ok) {
            const blob = await response.blob();
            const url = window.URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.href = url;
            link.download = `skipped_${Date.now()}.pdf`;
            link.click();
            window.URL.revokeObjectURL(url);
            return true;
        }

        throw new Error('Failed to download PDF');
    }

    async handleImportComplete(importId) {
        const history = await this.getHistory();
        const imp = history.data.find(i => i.id === importId);

        if (!imp) throw new Error('Import not found');

        console.log(`Import completed: ${imp.status_text}`);
        console.log(`Stored: ${imp.stored}, Skipped: ${imp.skipped}`);

        // Download skipped report if any
        if (imp.skipped > 0 && imp.skipped_file_url) {
            console.log('Downloading skipped records report...');
            await this.downloadSkippedPDF(imp.skipped_file_url);
            alert(`${imp.skipped} records were skipped. Check the downloaded PDF for details.`);
        } else {
            alert('Import completed successfully with no skipped records!');
        }

        return imp;
    }

    async apiCall(method, endpoint, data = null) {
        const headers = {
            'X-API-SID': this.apiSid,
            'X-API-PUBLIC-KEY': this.apiKey,
            'Accept': 'application/json'
        };

        if (data) headers['Content-Type'] = 'application/json';

        const response = await fetch(this.baseUrl + endpoint, {
            method,
            headers,
            body: data ? JSON.stringify(data) : null
        });

        return await response.json();
    }
}

// Usage
const importMgr = new ImportManager('SID....................................', 'PK.....................................');

// Monitor import with progress updates
await importMgr.monitorImport(45, (progress) => {
    console.log(`${progress.status}: ${progress.progress}% (${progress.stored}/${progress.total})`);
    // Update UI progress bar
    updateProgressBar(progress.progress);
});

// Handle completion
await importMgr.handleImportComplete(45);

Performance Tips

  • Don't poll too frequently (5-10 seconds is sufficient)
  • Cache import history results to reduce API calls
  • Only download skipped PDF when user requests it
  • Show import progress in real-time using stored/total_records
  • Archive old import records to keep history clean

Error Handling

  • Always check 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_by filter to distinguish between the two.

3.8.1 Get Blocked List

Retrieve a paginated list of all blocked contacts for your account. Results can be searched by phone number and filtered by how the contact was blocked (auto-blocked via opt-out words or manually added).

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

Request Headers

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

Query Parameters

Parameter Type Required Description
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)
search string No Search term to filter by phone number (partial match)
added_by string No Filter by blocking method: auto (opt-out words) or manual (manually added)

Example Request - Get All Blocked Contacts

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

Example Request - Search and Filter

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Blocked list fetched successfully",
    "data": {
        "blocked_list": {
            "current_page": 1,
            "data": [
                {
                    "id": 1234,
                    "first_name": "John",
                    "last_name": "Doe",
                    "number": "+12025551234",
                    "email": "john.doe@example.com",
                    "company": null,
                    "user_id": 1,
                    "list_id": null,
                    "folder_id": null,
                    "blacklisted": 1,
                    "blacklisted_by": 1,
                    "blacklisted_at": "2025-10-15T14:30:00.000000Z",
                    "valid": 0,
                    "validation_process": 0,
                    "created_at": "2025-10-15T14:30:00.000000Z",
                    "updated_at": "2025-10-15T14:30:00.000000Z"
                },
                {
                    "id": 1235,
                    "first_name": "Jane",
                    "last_name": "Smith",
                    "number": "+12025555678",
                    "email": null,
                    "company": null,
                    "user_id": 1,
                    "list_id": null,
                    "folder_id": null,
                    "blacklisted": 1,
                    "blacklisted_by": null,
                    "blacklisted_at": "2025-10-14T10:15:00.000000Z",
                    "valid": 0,
                    "validation_process": 0,
                    "created_at": "2025-10-14T10:15:00.000000Z",
                    "updated_at": "2025-10-14T10:15:00.000000Z"
                }
            ],
            "first_page_url": "https://api.texttorrent.com/api/v1/contact/blocked-list?page=1",
            "from": 1,
            "last_page": 5,
            "last_page_url": "https://api.texttorrent.com/api/v1/contact/blocked-list?page=5",
            "next_page_url": "https://api.texttorrent.com/api/v1/contact/blocked-list?page=2",
            "path": "https://api.texttorrent.com/api/v1/contact/blocked-list",
            "per_page": 10,
            "prev_page_url": null,
            "to": 10,
            "total": 48
        },
        "total": 48,
        "total_otp_out": 12
    },
    "errors": null
}

Response Fields

Field Type Description
blocked_list object Paginated list of blocked contacts
total integer Total number of blocked contacts
total_otp_out integer Total number of opt-out words configured

Blocked Contact Fields

Field Type Description
id integer Unique identifier for the contact
number string Blocked phone number (with +1 prefix)
blacklisted integer Always 1 for blocked contacts
blacklisted_by integer|null User ID who manually blocked (null if auto-blocked via opt-out words)
blacklisted_at string Timestamp when contact was blocked (ISO 8601 format)
list_id integer|null Always null for blocked contacts (removed from lists)

Filter Behavior

  • added_by=auto: Returns only contacts auto-blocked via opt-out words (blacklisted_by is null)
  • added_by=manual: Returns only contacts manually added to blocked list (blacklisted_by is not null)
  • No filter: Returns all blocked contacts regardless of how they were blocked

Example in JavaScript

async function getBlockedList(options = {}) {
    const params = new URLSearchParams({
        page: options.page || 1,
        limit: options.limit || 10
    });

    if (options.search) {
        params.append('search', options.search);
    }

    if (options.addedBy) {
        params.append('added_by', options.addedBy);
    }

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/contact/blocked-list/?${params}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Blocked contacts:', data.data.blocked_list.data);
        console.log(`Total blocked: ${data.data.total}`);
        console.log(`Opt-out words configured: ${data.data.total_otp_out}`);

        // Categorize by blocking method
        const manual = data.data.blocked_list.data.filter(c => c.blacklisted_by !== null);
        const auto = data.data.blocked_list.data.filter(c => c.blacklisted_by === null);

        console.log(`Manually blocked: ${manual.length}, Auto-blocked: ${auto.length}`);

        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get all blocked contacts
getBlockedList();

// Usage - Search for specific number
getBlockedList({ search: '555' });

// Usage - Get only manually blocked contacts
getBlockedList({ addedBy: 'manual', limit: 20 });

Important Notes

  • Results are ordered by blacklisted_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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
numbers array Yes Array of phone numbers to block (10 digits, without +1 prefix)

Example Request - Block Single Number

curl -X POST "https://api.texttorrent.com/api/v1/contact/blocked-list/" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "numbers": ["2025551234"]
  }'

Example Request - Block Multiple Numbers

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

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Blocklist create",
    "data": true,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "numbers": [
            "The numbers field is required.",
            "The numbers must be an array."
        ],
        "numbers.0": [
            "The numbers.0 must be a string."
        ]
    }
}

Common Validation Errors

  • Required: "The numbers field is required."
  • Invalid Type: "The numbers must be an array."
  • Invalid Format: "The numbers.X must be a string."

What Happens When Numbers Are Blocked

  • Contact records are created with blacklisted=1
  • Numbers automatically get +1 prefix
  • blacklisted_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

async function addToBlockedList(phoneNumbers) {
    // Validate phone numbers
    const validNumbers = phoneNumbers.filter(num => /^\d{10}$/.test(num));

    if (validNumbers.length === 0) {
        throw new Error('No valid 10-digit phone numbers provided');
    }

    if (validNumbers.length !== phoneNumbers.length) {
        console.warn('Some numbers were invalid and skipped');
    }

    const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            numbers: validNumbers
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log(`Successfully blocked ${validNumbers.length} number(s)`);
        alert(`${validNumbers.length} number(s) added to blocked list`);
        // Refresh blocked list
        await getBlockedList();
        return data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Block single number
addToBlockedList(['2025551234']);

// Usage - Block multiple numbers
addToBlockedList(['2025551234', '2025555678', '2025559999']);

Example in PHP

function addToBlockedList($phoneNumbers) {
    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/blocked-list/');

    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-API-SID: SID....................................',
        'X-API-PUBLIC-KEY: PK.....................................',
        'Content-Type: application/json',
        'Accept: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'numbers' => $phoneNumbers
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);

    if ($data['success']) {
        echo 'Numbers blocked successfully';
        return true;
    } else {
        echo 'Error: ' . $data['message'];
        return false;
    }
}

// Usage
addToBlockedList(['2025551234', '2025555678']);

Important Notes

  • Phone numbers must be 10 digits without +1 prefix (system adds it automatically)
  • Supports bulk blocking - add multiple numbers in one request
  • Blocked numbers receive blacklisted_by = your user ID
  • If number already exists in system, it will be updated to blocked status
  • Blocked contacts are removed from any lists they were in
  • Returns 201 Created status on success

Use Cases

  • Honor manual opt-out requests from customers
  • Block known spam or invalid numbers
  • Comply with do-not-contact lists
  • Block numbers that bounce or cause complaints

3.8.3 Remove from Blocked List

Remove one or more contacts from the blocked list (unblock them). This sets their blacklisted status to 0, allowing them to receive messages again. The contact record remains in the system.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
contact_ids array Yes Array of contact IDs to unblock (must exist in contacts table)

Example Request - Unblock Single Contact

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

Example Request - Unblock Multiple Contacts

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact(s) removed from blocked list successfully",
    "data": null,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_ids": [
            "The contact ids field is required.",
            "The contact ids must be an array."
        ],
        "contact_ids.0": [
            "The selected contact ids.0 is invalid."
        ]
    }
}

Common Validation Errors

  • Required: "The contact ids field is required."
  • Invalid Type: "The contact ids must be an array."
  • Invalid ID: "The selected contact ids.X is invalid." (contact doesn't exist)
  • Invalid Format: Each element must be an integer

What Happens When Contacts Are Unblocked

  • 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

async function removeFromBlockedList(contactIds) {
    // Confirm unblocking
    if (!confirm(`Unblock ${contactIds.length} contact(s)? They will be able to receive messages again.`)) {
        return;
    }

    const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/remove', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            contact_ids: contactIds
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Contacts unblocked successfully');
        alert(`${contactIds.length} contact(s) removed from blocked list`);
        // Refresh blocked list
        await getBlockedList();
    } else {
        console.error('Error:', data.message);
        alert('Failed to unblock contacts');
    }
}

// Usage - Unblock single contact
removeFromBlockedList([1234]);

// Usage - Unblock multiple contacts
removeFromBlockedList([1234, 1235, 1236]);

Important Notes

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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
contact_ids array Yes Array of contact IDs to delete permanently (must exist in contacts table)

Example Request

curl -X DELETE "https://api.texttorrent.com/api/v1/contact/blocked-list/delete" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "contact_ids": [1234, 1235]
  }'

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact(s) deleted successfully",
    "data": null,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_ids": [
            "The contact ids field is required."
        ],
        "contact_ids.0": [
            "The selected contact ids.0 is invalid."
        ]
    }
}

Example in JavaScript

async function deleteBlockedContacts(contactIds) {
    // Confirm deletion
    if (!confirm(
        `Permanently delete ${contactIds.length} blocked contact(s)? ` +
        `This cannot be undone and all contact information will be lost.`
    )) {
        return;
    }

    const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/delete', {
        method: 'DELETE',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            contact_ids: contactIds
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Blocked contacts deleted permanently');
        alert(`${contactIds.length} blocked contact(s) deleted`);
        // Refresh blocked list
        await getBlockedList();
    } else {
        console.error('Error:', data.message);
        alert('Failed to delete contacts');
    }
}

// Usage
deleteBlockedContacts([1234, 1235]);

Important Notes

  • Deletion is permanent and cannot be undone
  • All contact information is completely removed from the database
  • If the number contacts you again, they can be re-added to the system
  • Use unblock endpoint if you want to keep contact record but allow messages
  • Supports bulk deletion - multiple contacts in one request

When to Delete vs Unblock

  • Delete: Clean up old blocked contacts you no longer need to track
  • Unblock: Customer requested to opt back in, keep their information

3.8.5 Export Blocked List

Export blocked contacts to an Excel (.xlsx) file. You can export specific contacts or all blocked contacts. The export includes contact information, phone numbers, and blocking details.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
contact_ids array Yes Array of contact IDs to export (must exist in contacts table)

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "File generated successfully",
    "data": {
        "url": "https://your-storage.digitaloceanspaces.com/blocked-list/blocked_list_export_1729180845.xlsx"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_ids": [
            "The contact ids field is required."
        ],
        "contact_ids.0": [
            "The selected contact ids.0 is invalid."
        ]
    }
}

Export File Contents

The exported Excel file typically includes:

  • Contact name (first name, last name)
  • Phone number
  • Email address
  • Company
  • Blocked date (blacklisted_at)
  • Blocked by (user ID or "Auto" if via opt-out words)
  • Additional contact fields

Example in JavaScript

async function exportBlockedList(contactIds) {
    const response = await fetch('https://api.texttorrent.com/api/v1/contact/blocked-list/export', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            contact_ids: contactIds
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Export successful, downloading...');
        // Trigger download
        window.open(data.data.url, '_blank');
        return data.data.url;
    } else {
        console.error('Export failed:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Export specific contacts
exportBlockedList([1234, 1235, 1236]);

// Usage - Export all blocked contacts (get IDs from list first)
async function exportAllBlocked() {
    const blockedData = await getBlockedList({ limit: 1000 });
    const allIds = blockedData.blocked_list.data.map(contact => contact.id);
    await exportBlockedList(allIds);
}

Important Notes

  • File is stored on DigitalOcean Spaces (cloud storage)
  • Download URL is publicly accessible - use immediately
  • File name includes timestamp: blocked_list_export_{timestamp}.xlsx
  • Supports bulk export of multiple contacts
  • All contact IDs must exist and belong to your account
  • If any contact ID is invalid, the entire operation fails

Use Cases

  • Backup blocked contact list for compliance
  • Generate reports for stakeholders
  • Audit blocked contacts periodically
  • Share blocked list with team members
  • Archive blocked contacts before deletion

Blocked List Management Best Practices

Compliance and Legal

  • Honor Opt-Outs: Always respect customer opt-out requests immediately
  • Document Blocking: Export blocked list regularly for compliance records
  • Automatic Blocking: Configure opt-out words (STOP, UNSUBSCRIBE, etc.) for auto-blocking
  • Manual Review: Periodically review auto-blocked contacts for false positives

Managing Blocked Contacts

  • Use added_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

// Complete blocked list management workflow
class BlockedListManager {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1';
    }

    async getList(options = {}) {
        const params = new URLSearchParams({
            page: options.page || 1,
            limit: options.limit || 10
        });

        if (options.search) params.append('search', options.search);
        if (options.addedBy) params.append('added_by', options.addedBy);

        return await this.apiCall('GET', `/contact/blocked-list/?${params}`);
    }

    async blockNumbers(phoneNumbers) {
        return await this.apiCall('POST', '/contact/blocked-list/', {
            numbers: phoneNumbers
        });
    }

    async unblock(contactIds) {
        return await this.apiCall('POST', '/contact/blocked-list/remove', {
            contact_ids: contactIds
        });
    }

    async delete(contactIds) {
        return await this.apiCall('DELETE', '/contact/blocked-list/delete', {
            contact_ids: contactIds
        });
    }

    async export(contactIds) {
        const result = await this.apiCall('POST', '/contact/blocked-list/export', {
            contact_ids: contactIds
        });

        if (result.success) {
            window.open(result.data.url, '_blank');
        }

        return result;
    }

    async auditBlockedList() {
        // Get all blocked contacts
        const data = await this.getList({ limit: 1000 });
        const blocked = data.data.blocked_list.data;

        console.log(`Total blocked contacts: ${data.data.total}`);
        console.log(`Opt-out words configured: ${data.data.total_otp_out}`);

        // Categorize
        const manual = blocked.filter(c => c.blacklisted_by !== null);
        const auto = blocked.filter(c => c.blacklisted_by === null);

        console.log(`Manually blocked: ${manual.length}`);
        console.log(`Auto-blocked: ${auto.length}`);

        // Export for records
        const allIds = blocked.map(c => c.id);
        await this.export(allIds);

        return { total: data.data.total, manual: manual.length, auto: auto.length };
    }

    async apiCall(method, endpoint, data = null) {
        const headers = {
            'X-API-SID': this.apiSid,
            'X-API-PUBLIC-KEY': this.apiKey,
            'Accept': 'application/json'
        };

        if (data && method !== 'GET') {
            headers['Content-Type'] = 'application/json';
        }

        const options = {
            method,
            headers,
            body: data && method !== 'GET' ? JSON.stringify(data) : null
        };

        const response = await fetch(this.baseUrl + endpoint, options);
        return await response.json();
    }
}

// Usage
const blockedMgr = new BlockedListManager('SID....................................', 'PK.....................................');

// Block numbers
await blockedMgr.blockNumbers(['2025551234', '2025555678']);

// Get blocked list
await blockedMgr.getList({ addedBy: 'manual' });

// Unblock contacts
await blockedMgr.unblock([1234, 1235]);

// Delete contacts
await blockedMgr.delete([1236]);

// Export contacts
await blockedMgr.export([1234, 1235, 1236]);

// Run audit
await blockedMgr.auditBlockedList();

Performance Tips

  • Use pagination for large blocked lists (don't load all at once)
  • Cache blocked list data to reduce API calls
  • Use bulk operations (block/unblock/delete multiple at once)
  • Export periodically rather than fetching full list repeatedly

Common Mistakes to Avoid

  • ❌ Deleting instead of unblocking when customer opts back in
  • ❌ Not backing up before bulk deletions
  • ❌ Ignoring auto-blocked contacts (review periodically)
  • ❌ Not configuring opt-out words for automatic blocking
  • ✅ Export blocked list for compliance documentation
  • ✅ Use unblock to keep customer record intact
  • ✅ Review and categorize blocked contacts regularly

3.9 Opt-Out Words Management

The Opt-Out Words Management API allows you to configure keywords that automatically block contacts when they reply with specific words or phrases. When a contact sends a message containing any of your opt-out words (like "STOP", "UNSUBSCRIBE", "CANCEL"), they are automatically added to the blocked list.

Key Features:

  • Configure custom opt-out keywords and phrases
  • Automatic contact blocking when opt-out words are detected
  • Search and paginate through opt-out words
  • Update existing opt-out words
  • Bulk delete opt-out words
  • Export opt-out word list to CSV
  • Hierarchy support for master/main/sub accounts
ℹ️ 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

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

Query Parameters

Parameter Type Required Description
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)
search string No Search term to filter opt-out words (partial match)

Example Request - Get All Opt-Out Words

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

Example Request - Search and Paginate

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Opt-out words fetched successfully",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 1,
                "user_id": 1,
                "word": "STOP",
                "created_at": "2025-01-15T10:30:00.000000Z",
                "updated_at": "2025-01-15T10:30:00.000000Z"
            },
            {
                "id": 2,
                "user_id": 1,
                "word": "UNSUBSCRIBE",
                "created_at": "2025-01-15T10:31:00.000000Z",
                "updated_at": "2025-01-15T10:31:00.000000Z"
            },
            {
                "id": 3,
                "user_id": 1,
                "word": "CANCEL",
                "created_at": "2025-01-15T10:32:00.000000Z",
                "updated_at": "2025-01-15T10:32:00.000000Z"
            },
            {
                "id": 4,
                "user_id": 1,
                "word": "END",
                "created_at": "2025-01-15T10:33:00.000000Z",
                "updated_at": "2025-01-15T10:33:00.000000Z"
            },
            {
                "id": 5,
                "user_id": 1,
                "word": "QUIT",
                "created_at": "2025-01-15T10:34:00.000000Z",
                "updated_at": "2025-01-15T10:34:00.000000Z"
            },
            {
                "id": 6,
                "user_id": 1,
                "word": "OPT OUT",
                "created_at": "2025-01-15T10:35:00.000000Z",
                "updated_at": "2025-01-15T10:35:00.000000Z"
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/contact/opt-out-word?page=1",
        "from": 1,
        "last_page": 1,
        "last_page_url": "https://api.texttorrent.com/api/v1/contact/opt-out-word?page=1",
        "next_page_url": null,
        "path": "https://api.texttorrent.com/api/v1/contact/opt-out-word",
        "per_page": 10,
        "prev_page_url": null,
        "to": 6,
        "total": 6
    },
    "errors": null
}

Opt-Out Word Fields

Field Type Description
id integer Unique identifier for the opt-out word
user_id integer ID of the user who created this opt-out word
word string The opt-out keyword or phrase (case-insensitive)
created_at string Timestamp when opt-out word was created (ISO 8601 format)
updated_at string Timestamp when opt-out word was last updated (ISO 8601 format)

Example in JavaScript

async function getOptOutWords(options = {}) {
    const params = new URLSearchParams({
        page: options.page || 1,
        limit: options.limit || 10
    });

    if (options.search) {
        params.append('search', options.search);
    }

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/contact/opt-out-word?${params}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Opt-out words:', data.data.data);
        console.log(`Total opt-out words: ${data.data.total}`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get all opt-out words
getOptOutWords();

// Usage - Search for specific word
getOptOutWords({ search: 'STOP' });

// Usage - Get with custom limit
getOptOutWords({ limit: 50 });

Important Notes

  • Results are ordered by created_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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
word string Yes Opt-out keyword or phrase (max 255 characters)

Example Request - Create Single Word

curl -X POST "https://api.texttorrent.com/api/v1/contact/opt-out-word" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "word": "STOP"
  }'

Example Request - Create Phrase

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Opt-out word created successfully",
    "data": {
        "id": 7,
        "user_id": 1,
        "word": "STOP",
        "created_at": "2025-10-17T14:30:00.000000Z",
        "updated_at": "2025-10-17T14:30:00.000000Z"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "word": [
            "The word field is required.",
            "The word must be a string.",
            "The word may not be greater than 255 characters."
        ]
    }
}

Duplicate Word Error (422)

{
    "code": 422,
    "success": false,
    "message": "This word already exists under your account.",
    "data": null,
    "errors": null
}

Hierarchy Conflict Error (422)

{
    "code": 422,
    "success": false,
    "message": "This word already exists under your parent or master account.",
    "data": null,
    "errors": null
}

Common Validation Errors

  • Required: "The word field is required."
  • Invalid Type: "The word must be a string."
  • Too Long: "The word may not be greater than 255 characters."
  • Duplicate: "This word already exists under your account."
  • Hierarchy Conflict: "This word already exists under your parent or master account."

Account Hierarchy Behavior

  • Master Account: If duplicate exists in sub-accounts, reassigns it to master
  • Main Account: If duplicate exists in sub-accounts, reassigns it to main; Checks master account for conflicts
  • Sub Account: Checks both parent main and master accounts for conflicts
  • This prevents the same word from existing multiple times in the hierarchy

Example in JavaScript

async function createOptOutWord(word) {
    // Validate word
    if (!word || word.trim().length === 0) {
        throw new Error('Opt-out word cannot be empty');
    }

    if (word.length > 255) {
        throw new Error('Opt-out word is too long (max 255 characters)');
    }

    const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            word: word.trim().toUpperCase() // Standardize to uppercase
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log(`Opt-out word "${word}" created successfully`);
        alert(`Opt-out word added: ${word}`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        alert(`Failed to add opt-out word: ${data.message}`);
        throw new Error(data.message);
    }
}

// Usage - Create single opt-out word
createOptOutWord('STOP');

// Usage - Create common opt-out words
const commonOptOutWords = ['STOP', 'UNSUBSCRIBE', 'CANCEL', 'END', 'QUIT', 'OPT OUT'];
for (const word of commonOptOutWords) {
    try {
        await createOptOutWord(word);
    } catch (error) {
        console.log(`Skipping "${word}": ${error.message}`);
    }
}

Example in PHP

function createOptOutWord($word) {
    $ch = curl_init('https://api.texttorrent.com/api/v1/contact/opt-out-word');

    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-API-SID: SID....................................',
        'X-API-PUBLIC-KEY: PK.....................................',
        'Content-Type: application/json',
        'Accept: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'word' => strtoupper(trim($word))
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);

    if ($data['success']) {
        echo "Opt-out word created: " . $word;
        return $data['data'];
    } else {
        echo 'Error: ' . $data['message'];
        return false;
    }
}

// Usage
createOptOutWord('STOP');

Important Notes

  • Opt-out word matching is case-insensitive (STOP = stop = Stop)
  • Supports multi-word phrases (e.g., "OPT OUT", "DO NOT CONTACT")
  • Maximum length is 255 characters
  • Leading/trailing whitespace is preserved
  • System prevents duplicate words within account hierarchy

Recommended Opt-Out Words

For compliance and best practices, configure these standard opt-out words:

  • STOP - Most common and legally required
  • UNSUBSCRIBE - Email convention, widely understood
  • CANCEL - Common alternative
  • END - Simple and clear
  • QUIT - Alternative to STOP
  • OPT OUT - Explicit opt-out phrase
  • REMOVE - Common request
  • STOPALL - Used by some carriers

3.9.3 Update Opt-Out Word

Update an existing opt-out word. This allows you to correct typos or change the wording of an opt-out keyword. The system validates uniqueness within the account hierarchy.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Path Parameters

Parameter Type Required Description
id integer Yes ID of the opt-out word to update

Request Body Parameters

Parameter Type Required Description
word string Yes New opt-out keyword or phrase (max 255 characters, must be unique)

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Opt-out word updated successfully",
    "data": {
        "id": 7,
        "user_id": 1,
        "word": "STOP ALL",
        "created_at": "2025-10-17T14:30:00.000000Z",
        "updated_at": "2025-10-17T15:45:00.000000Z"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "word": [
            "The word field is required.",
            "The word has already been taken."
        ]
    }
}

Not Found Error (404)

{
    "code": 404,
    "success": false,
    "message": "Opt-out word not found",
    "data": null,
    "errors": null
}

Example in JavaScript

async function updateOptOutWord(id, newWord) {
    const response = await fetch(`https://api.texttorrent.com/api/v1/contact/opt-out-word/${id}`, {
        method: 'PUT',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            word: newWord.trim().toUpperCase()
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Opt-out word updated successfully');
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
updateOptOutWord(7, 'STOP ALL');

Important Notes

  • The new word must be unique within your account hierarchy
  • Maximum length is 255 characters
  • Updated timestamp is automatically set
  • Validation ensures no conflicts with parent/master accounts

3.9.4 Delete Opt-Out Words

Permanently delete one or more opt-out words from your account. Once deleted, incoming messages with these words will no longer trigger automatic blocking.

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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
ids array Yes Array of opt-out word IDs to delete (must exist in opt_out_words table)

Example Request - Delete Single Word

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

Example Request - Delete Multiple Words

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Opt-out word(s) deleted successfully",
    "data": null,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "ids": [
            "The ids field is required.",
            "The ids must be an array."
        ],
        "ids.0": [
            "The selected ids.0 is invalid."
        ]
    }
}

Common Validation Errors

  • Required: "The ids field is required."
  • Invalid Type: "The ids must be an array."
  • Invalid ID: "The selected ids.X is invalid." (opt-out word doesn't exist)

Example in JavaScript

async function deleteOptOutWords(ids) {
    // Confirm deletion
    if (!confirm(`Delete ${ids.length} opt-out word(s)? This cannot be undone.`)) {
        return;
    }

    const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word/delete', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            ids: ids
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Opt-out words deleted successfully');
        alert(`${ids.length} opt-out word(s) deleted`);
        // Refresh list
        await getOptOutWords();
    } else {
        console.error('Error:', data.message);
        alert('Failed to delete opt-out words');
    }
}

// Usage - Delete single opt-out word
deleteOptOutWords([7]);

// Usage - Delete multiple opt-out words
deleteOptOutWords([7, 8, 9]);

Important Notes

  • Deletion is permanent and cannot be undone
  • Supports bulk deletion - multiple words in one request
  • All IDs must exist and belong to your account
  • After deletion, messages with those words will no longer trigger auto-blocking
  • NEVER delete standard compliance words like "STOP" or "UNSUBSCRIBE"

3.9.5 Export Opt-Out Words

Export opt-out words to a CSV file for backup, audit, or reporting purposes. You can export specific words or all words configured for your account.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Content-Type: application/json
Accept: application/json

Request Body Parameters

Parameter Type Required Description
ids array No Array of opt-out word IDs to export (if omitted, exports all words)

Example Request - Export Specific Words

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

Example Request - Export All Words

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Opt-out words exported successfully",
    "data": {
        "message": "Opt-out words exported successfully",
        "file": "https://your-storage.digitaloceanspaces.com/opt-out-words/opt-out-words-2025-10-17_14-30-45.csv"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "ids": [
            "The ids must be an array."
        ],
        "ids.0": [
            "The ids.0 must be an integer.",
            "The selected ids.0 is invalid."
        ]
    }
}

Export File Format

The exported CSV file typically includes:

  • ID - Unique identifier
  • Word - The opt-out keyword or phrase
  • Created At - When the word was added
  • Updated At - Last modification date

Example in JavaScript

async function exportOptOutWords(ids = null) {
    const body = ids ? { ids } : {};

    const response = await fetch('https://api.texttorrent.com/api/v1/contact/opt-out-word/export', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify(body)
    });

    const data = await response.json();

    if (data.success) {
        console.log('Export successful, downloading...');
        // Trigger download
        window.open(data.data.file, '_blank');
        return data.data.file;
    } else {
        console.error('Export failed:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Export specific opt-out words
exportOptOutWords([1, 2, 3]);

// Usage - Export all opt-out words
exportOptOutWords();

// Usage - Export all with fetched IDs
async function exportAllOptOutWords() {
    const words = await getOptOutWords({ limit: 1000 });
    const allIds = words.data.map(word => word.id);
    await exportOptOutWords(allIds);
}

Important Notes

  • File is stored on DigitalOcean Spaces (cloud storage)
  • Download URL is publicly accessible - use immediately
  • File name includes timestamp: opt-out-words-YYYY-MM-DD_HH-MM-SS.csv
  • If 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/Phrase Priority Reason
STOP Critical Legally required by TCPA, most common opt-out word
STOPALL Critical Used by carriers to stop all messages from account
UNSUBSCRIBE Critical Email convention, widely recognized
CANCEL High Common alternative to STOP
END High Simple and clear opt-out word
QUIT High Common opt-out request
OPT OUT Medium Explicit two-word phrase
REMOVE Medium Common list removal request

Implementation Workflow

// Complete opt-out words management system
class OptOutWordsManager {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1';
    }

    async getAll(options = {}) {
        const params = new URLSearchParams({
            page: options.page || 1,
            limit: options.limit || 50
        });

        if (options.search) params.append('search', options.search);

        return await this.apiCall('GET', `/contact/opt-out-word?${params}`);
    }

    async create(word) {
        return await this.apiCall('POST', '/contact/opt-out-word', { word });
    }

    async update(id, word) {
        return await this.apiCall('PUT', `/contact/opt-out-word${id}`, { word });
    }

    async delete(ids) {
        return await this.apiCall('POST', '/contact/opt-out-worddelete', { ids });
    }

    async export(ids = null) {
        const result = await this.apiCall('POST', '/contact/opt-out-wordexport',
            ids ? { ids } : {}
        );

        if (result.success) {
            window.open(result.data.file, '_blank');
        }

        return result;
    }

    async setupCompliance() {
        // Configure all essential opt-out words for compliance
        const essentialWords = [
            'STOP',
            'STOPALL',
            'UNSUBSCRIBE',
            'CANCEL',
            'END',
            'QUIT',
            'OPT OUT',
            'REMOVE'
        ];

        console.log('Setting up compliance opt-out words...');

        for (const word of essentialWords) {
            try {
                await this.create(word);
                console.log(`✓ Added: ${word}`);
            } catch (error) {
                console.log(`  Skipped ${word}: ${error.message}`);
            }
        }

        console.log('Compliance setup complete!');

        // Verify all words are configured
        const current = await this.getAll({ limit: 100 });
        console.log(`Total opt-out words configured: ${current.data.total}`);

        return current;
    }

    async apiCall(method, endpoint, data = null) {
        const headers = {
            'X-API-SID': this.apiSid,
            'X-API-PUBLIC-KEY': this.apiKey,
            'Accept': 'application/json'
        };

        if (data && method !== 'GET') {
            headers['Content-Type'] = 'application/json';
        }

        const options = {
            method,
            headers,
            body: data && method !== 'GET' ? JSON.stringify(data) : null
        };

        const response = await fetch(this.baseUrl + endpoint, options);
        const result = await response.json();

        if (!result.success) {
            throw new Error(result.message);
        }

        return result;
    }
}

// Usage
const optOutMgr = new OptOutWordsManager('SID....................................', 'PK.....................................');

// Setup compliance words
await optOutMgr.setupCompliance();

// Get all opt-out words
await optOutMgr.getAll();

// Add custom word
await optOutMgr.create('DO NOT CONTACT');

// Update word
await optOutMgr.update(7, 'STOP MESSAGING');

// Delete words
await optOutMgr.delete([7, 8]);

// Export all words for backup
await optOutMgr.export();

Testing Your Opt-Out Words

  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

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

Query Parameters

Parameter Type Required Description
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)
search string No Search by contact name, number, email, company, or message content
folder integer No Filter by folder ID
time string No Filter by time: today, last_week, last_month, last_year
unread boolean No Show only conversations with unread messages (default: false)

Example Request - Get All Conversations

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

Example Request - Search and Filter

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Chats retrieved successfully",
    "data": {
        "current_page": 1,
        "data": [
            {
                "chat_id": 1234,
                "contact_id": 567,
                "first_name": "John",
                "last_name": "Doe",
                "number": "+12025551234",
                "email": "john.doe@example.com",
                "company": "Acme Corp",
                "folder_id": 5,
                "last_message": "Thanks for the update!",
                "last_chat_time": "2025-10-17T14:30:00.000000Z",
                "unread_count": 3,
                "send_by": "contact",
                "avatar_ltr": "JD"
            },
            {
                "chat_id": 1235,
                "contact_id": 568,
                "first_name": "Jane",
                "last_name": "Smith",
                "number": "+12025555678",
                "email": "jane.smith@example.com",
                "company": null,
                "folder_id": null,
                "last_message": "Got it, will check tomorrow",
                "last_chat_time": "2025-10-17T13:15:00.000000Z",
                "unread_count": 0,
                "send_by": "me",
                "avatar_ltr": "JS"
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/inbox?page=1",
        "from": 1,
        "last_page": 5,
        "last_page_url": "https://api.texttorrent.com/api/v1/inbox?page=5",
        "next_page_url": "https://api.texttorrent.com/api/v1/inbox?page=2",
        "path": "https://api.texttorrent.com/api/v1/inbox",
        "per_page": 10,
        "prev_page_url": null,
        "to": 10,
        "total": 48
    },
    "errors": null
}

Conversation Fields

Field Type Description
chat_id integer Unique identifier for the conversation thread
contact_id integer ID of the contact in this conversation
last_message string Most recent message content in the conversation
last_chat_time string Timestamp of the last message (ISO 8601 format)
unread_count integer Number of unread messages in this conversation
send_by string Who sent the last message: me or contact
avatar_ltr string Contact initials for avatar display (e.g., "JD")

Example in JavaScript

async function getInboxMessages(options = {}) {
    const params = new URLSearchParams({
        page: options.page || 1,
        limit: options.limit || 10
    });

    if (options.search) params.append('search', options.search);
    if (options.folder) params.append('folder', options.folder);
    if (options.time) params.append('time', options.time);
    if (options.unread) params.append('unread', 'true');

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/inbox?${params}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Conversations:', data.data.data);
        console.log(`Total conversations: ${data.data.total}`);

        // Count unread messages
        const totalUnread = data.data.data.reduce((sum, chat) => sum + chat.unread_count, 0);
        console.log(`Total unread messages: ${totalUnread}`);

        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get all conversations
getInboxMessages();

// Usage - Get unread conversations only
getInboxMessages({ unread: true });

// Usage - Search conversations
getInboxMessages({ search: 'john' });

// Usage - Filter by folder and time
getInboxMessages({ folder: 5, time: 'last_week' });

Time Filter Options

  • today: Conversations with messages from today
  • last_week: Conversations from the past 7 days
  • last_month: Conversations from the past 30 days
  • last_year: Conversations from the past 365 days

Important Notes

  • Results are ordered by last_chat_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

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

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

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Last chat retrieved successfully",
    "data": {
        "chat_id": 1234,
        "contact_id": 567,
        "contact_name": "John Doe",
        "contact_phone_number": "+12025551234",
        "last_message": "Thanks for the update!",
        "last_chat_time": "2025-10-17T14:30:00.000000Z"
    },
    "errors": null
}

Success Response - No Chats (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Last chat retrieved successfully",
    "data": null,
    "errors": null
}

Example in JavaScript

async function getLastChat() {
    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/last-chat', {
        method: 'GET',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        if (data.data) {
            console.log('Last active chat:', data.data);
            console.log(`Continue conversation with ${data.data.contact_name}`);
            return data.data;
        } else {
            console.log('No previous conversations found');
            return null;
        }
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
const lastChat = await getLastChat();
if (lastChat) {
    // Navigate to the conversation
    window.location.href = `/inbox/${lastChat.chat_id}`;
}

Important Notes

  • Returns the conversation with the most recent updated_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

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

Query Parameters

Parameter Type Required Description
search string No Search templates by name (partial match)

Example Request - Get All Templates

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

Example Request - Search Templates

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Templates retrieved successfully",
    "data": [
        {
            "id": 1,
            "user_id": 1,
            "template_name": "Welcome Message",
            "status": 1,
            "preview_message": "Welcome to our service! We're excited to have you on board.",
            "created_at": "2025-10-01T10:30:00.000000Z"
        },
        {
            "id": 2,
            "user_id": 1,
            "template_name": "Follow Up",
            "status": 1,
            "preview_message": "Just following up on our previous conversation. Let me know if you have any questions!",
            "created_at": "2025-10-05T14:20:00.000000Z"
        },
        {
            "id": 3,
            "user_id": 1,
            "template_name": "Thank You",
            "status": 1,
            "preview_message": "Thank you for your business! We appreciate your support.",
            "created_at": "2025-10-10T09:15:00.000000Z"
        }
    ],
    "errors": null
}

Template Fields

Field Type Description
id integer Unique identifier for the template
template_name string Name/title of the template
status integer Template status: 1 = active, 0 = inactive
preview_message string The template message content
created_at string Timestamp when template was created (ISO 8601 format)

Example in JavaScript

async function getMessageTemplates(searchTerm = null) {
    const params = new URLSearchParams();
    if (searchTerm) params.append('search', searchTerm);

    const url = searchTerm
        ? `https://api.texttorrent.com/api/v1/inbox/templates?${params}`
        : 'https://api.texttorrent.com/api/v1/inbox/templates';

    const response = await fetch(url, {
        method: 'GET',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Message templates:', data.data);

        // Display templates in a dropdown
        const activeTemplates = data.data.filter(t => t.status === 1);
        console.log(`${activeTemplates.length} active templates available`);

        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get all templates
const templates = await getMessageTemplates();

// Usage - Search templates
const welcomeTemplates = await getMessageTemplates('welcome');

// Usage - Create template selector UI
function createTemplateSelector(templates) {
    const select = document.createElement('select');
    select.innerHTML = '';

    templates.forEach(template => {
        if (template.status === 1) {
            const option = document.createElement('option');
            option.value = template.preview_message;
            option.textContent = template.template_name;
            select.appendChild(option);
        }
    });

    select.addEventListener('change', (e) => {
        if (e.target.value) {
            document.getElementById('message-input').value = e.target.value;
        }
    });

    return select;
}

Important Notes

  • Templates are user-specific (only shows templates you created)
  • Search is case-insensitive and performs partial matching on template name
  • Active templates have status=1, inactive have status=0
  • Returns all templates (no pagination)
  • Use templates to save time on frequently sent messages

4.4 Get Active Numbers

Retrieve all active phone numbers in your account that can be used to send messages. Each number includes details about who purchased it, when it was purchased, and its capabilities.

GET /api/v1/inbox/numbers/active

Request Headers

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

Query Parameters

Parameter Type Required Description
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Active numbers retrieved successfully",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 1,
                "user_id": 1,
                "purchased_by_user": "John Admin",
                "purchased_by": 1,
                "status": 1,
                "created_at": "2025-09-15T10:30:00.000000Z",
                "number": "+12025551234",
                "friendly_name": "Sales Line",
                "region": "DC",
                "country": "US",
                "latitude": "38.895",
                "longitude": "-77.036",
                "postal_code": "20001",
                "capabilities": {
                    "voice": true,
                    "sms": true,
                    "mms": true
                },
                "purchased_at": "2025-09-15T10:30:00.000000Z",
                "twilio_number_sid": "PNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                "twilio_service_sid": "MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                "type": "local"
            },
            {
                "id": 2,
                "user_id": 1,
                "purchased_by_user": "Jane Manager",
                "purchased_by": 5,
                "status": 1,
                "created_at": "2025-09-20T14:20:00.000000Z",
                "number": "+12025555678",
                "friendly_name": "Support Line",
                "region": "DC",
                "country": "US",
                "latitude": "38.895",
                "longitude": "-77.036",
                "postal_code": "20001",
                "capabilities": {
                    "voice": true,
                    "sms": true,
                    "mms": true
                },
                "purchased_at": "2025-09-20T14:20:00.000000Z",
                "twilio_number_sid": "PNyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
                "twilio_service_sid": "MGyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
                "type": "local"
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/inbox/numbers/active?page=1",
        "from": 1,
        "last_page": 2,
        "last_page_url": "https://api.texttorrent.com/api/v1/inbox/numbers/active?page=2",
        "next_page_url": "https://api.texttorrent.com/api/v1/inbox/numbers/active?page=2",
        "path": "https://api.texttorrent.com/api/v1/inbox/numbers/active",
        "per_page": 10,
        "prev_page_url": null,
        "to": 10,
        "total": 15
    },
    "errors": null
}

Number Fields

Field Type Description
number string Phone number in E.164 format (e.g., +12025551234)
friendly_name string Custom name for the number (e.g., "Sales Line")
status integer Number status: 1 = active, 0 = inactive
purchased_by_user string Full name of user who purchased the number
capabilities object Number capabilities: voice, sms, mms
type string Number type: local, toll-free, mobile
twilio_number_sid string Twilio number SID

Example in JavaScript

async function getActiveNumbers(options = {}) {
    const params = new URLSearchParams({
        page: options.page || 1,
        limit: options.limit || 10
    });

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/inbox/numbers/active?${params}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Active numbers:', data.data.data);
        console.log(`Total active numbers: ${data.data.total}`);

        // Create sender number dropdown
        const smsCapableNumbers = data.data.data.filter(n => n.capabilities.sms);
        console.log(`${smsCapableNumbers.length} numbers can send SMS`);

        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
const numbers = await getActiveNumbers();

// Usage - Create from-number selector
function createFromNumberSelector(numbers) {
    const select = document.createElement('select');
    select.innerHTML = '';

    numbers.data.forEach(number => {
        if (number.status === 1 && number.capabilities.sms) {
            const option = document.createElement('option');
            option.value = number.number;
            option.textContent = `${number.friendly_name} (${number.number})`;
            select.appendChild(option);
        }
    });

    return select;
}

Important Notes

  • Only returns numbers with status=1 (active)
  • Numbers must belong to your account (user_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

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

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Receiver numbers retrieved successfully",
    "data": [
        {
            "id": 789,
            "user_id": 1,
            "first_name": "Alice",
            "last_name": "Johnson",
            "number": "+12025559876",
            "email": "alice.johnson@example.com",
            "company": "Tech Solutions",
            "list_id": 10,
            "folder_id": null,
            "blacklisted": 0,
            "created_at": "2025-10-15T10:30:00.000000Z",
            "updated_at": "2025-10-15T10:30:00.000000Z"
        },
        {
            "id": 790,
            "user_id": 1,
            "first_name": "Bob",
            "last_name": "Williams",
            "number": "+12025559877",
            "email": "bob.williams@example.com",
            "company": null,
            "list_id": 10,
            "folder_id": 3,
            "blacklisted": 0,
            "created_at": "2025-10-16T14:20:00.000000Z",
            "updated_at": "2025-10-16T14:20:00.000000Z"
        }
    ],
    "errors": null
}

Example in JavaScript

async function getReceiverNumbers() {
    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/numbers/receiver', {
        method: 'GET',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Available receivers:', data.data);
        console.log(`${data.data.length} contacts available for new conversations`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get contacts for starting new conversations
const availableContacts = await getReceiverNumbers();

// Usage - Create "Start New Chat" contact selector
function createNewChatSelector(contacts) {
    const select = document.createElement('select');
    select.innerHTML = '';

    contacts.forEach(contact => {
        const option = document.createElement('option');
        option.value = contact.id;
        option.textContent = `${contact.first_name} ${contact.last_name} (${contact.number})`;
        select.appendChild(option);
    });

    select.addEventListener('change', async (e) => {
        if (e.target.value) {
            // Start new conversation with selected contact
            await startNewChat(e.target.value);
        }
    });

    return select;
}

Important Notes

  • Returns contacts WITHOUT existing conversation threads
  • Limited to 30 contacts (no pagination)
  • Useful for "Start New Chat" functionality
  • Contacts must not be blacklisted
  • Once you message a contact, they move to the inbox and won't appear in this list

4.6 Get Chat Details

Retrieve full details of a specific conversation including contact information, notes, and paginated message history. Messages are automatically marked as read when you view the conversation.

GET /api/v1/inbox/{id}

Request Headers

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

Path Parameters

Parameter Type Required Description
id integer Yes Chat ID (conversation ID)

Query Parameters

Parameter Type Required Description
limit integer No Number of messages per page (default: 10)
page integer No Page number for message pagination (default: 1)

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Chat retrieved successfully",
    "data": {
        "chat": {
            "id": 567,
            "user_id": 1,
            "first_name": "John",
            "last_name": "Doe",
            "number": "+12025551234",
            "email": "john.doe@example.com",
            "company": "Acme Corp",
            "list_id": 10,
            "folder_id": 5,
            "blacklisted": 0,
            "contact_id": 567,
            "from_number": "+12025559999",
            "chat_id": 1234,
            "avatar_ltr": "JD",
            "created_at": "2025-09-15T10:30:00.000000Z",
            "updated_at": "2025-10-17T14:30:00.000000Z",
            "notes": [
                {
                    "id": 1,
                    "note": "VIP customer - priority support"
                },
                {
                    "id": 2,
                    "note": "Interested in premium features"
                }
            ]
        },
        "messages": {
            "current_page": 1,
            "data": [
                {
                    "id": 5001,
                    "chat_id": 1234,
                    "message": "Thanks for the update!",
                    "direction": "inbound",
                    "status": 1,
                    "from_number": "+12025551234",
                    "to_number": "+12025559999",
                    "media_url": null,
                    "created_at": "2025-10-17T14:30:00.000000Z",
                    "updated_at": "2025-10-17T14:30:05.000000Z"
                },
                {
                    "id": 5000,
                    "chat_id": 1234,
                    "message": "Your order has been shipped!",
                    "direction": "outbound",
                    "status": 1,
                    "from_number": "+12025559999",
                    "to_number": "+12025551234",
                    "media_url": null,
                    "created_at": "2025-10-17T14:25:00.000000Z",
                    "updated_at": "2025-10-17T14:25:05.000000Z"
                }
            ],
            "first_page_url": "https://api.texttorrent.com/api/v1/inbox/1234?page=1",
            "from": 1,
            "last_page": 3,
            "last_page_url": "https://api.texttorrent.com/api/v1/inbox/1234?page=3",
            "next_page_url": "https://api.texttorrent.com/api/v1/inbox/1234?page=2",
            "path": "https://api.texttorrent.com/api/v1/inbox/1234",
            "per_page": 10,
            "prev_page_url": null,
            "to": 10,
            "total": 28
        }
    },
    "errors": null
}

Chat Not Found (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Chat not found",
    "data": null,
    "errors": null
}

Message Fields

Field Type Description
direction string inbound (received) or outbound (sent)
status integer 0 = unread, 1 = read
from_number string Sender's phone number
to_number string Recipient's phone number
media_url string|null URL to media attachment (for MMS)

Example in JavaScript

async function getChatDetails(chatId, options = {}) {
    const params = new URLSearchParams({
        page: options.page || 1,
        limit: options.limit || 10
    });

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/inbox/${chatId}?${params}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success && data.data) {
        const { chat, messages } = data.data;

        console.log('Contact:', `${chat.first_name} ${chat.last_name}`);
        console.log('Notes:', chat.notes);
        console.log('Messages:', messages.data);
        console.log(`Total messages: ${messages.total}`);

        return data.data;
    } else {
        console.log('Chat not found');
        return null;
    }
}

// Usage
const chatDetails = await getChatDetails(1234);

// Usage - Load more messages (pagination)
const moreMessages = await getChatDetails(1234, { page: 2, limit: 20 });

// Usage - Display conversation UI
function displayConversation(chatData) {
    if (!chatData) return;

    const { chat, messages } = chatData;

    // Display contact info
    document.getElementById('contact-name').textContent =
        `${chat.first_name} ${chat.last_name}`;
    document.getElementById('contact-number').textContent = chat.number;

    // Display notes
    const notesContainer = document.getElementById('notes');
    notesContainer.innerHTML = chat.notes
        .map(note => `
${note.note}
`) .join(''); // Display messages const messagesContainer = document.getElementById('messages'); messagesContainer.innerHTML = messages.data .reverse() // Show oldest first .map(msg => `

${msg.message}

${new Date(msg.created_at).toLocaleString()} ${msg.media_url ? `Media` : ''}
`) .join(''); }

Important Notes

  • Messages are ordered by created_at DESC (newest first in API response)
  • All unread messages are automatically marked as read when viewing
  • Returns 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

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

Path Parameters

Parameter Type Required Description
id integer Yes Chat ID (conversation ID) to delete

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Chat deleted successfully",
    "data": null,
    "errors": null
}

Example in JavaScript

async function deleteChat(chatId) {
    // Confirm deletion
    if (!confirm('Delete this conversation? All messages will be permanently removed.')) {
        return;
    }

    const response = await fetch(`https://api.texttorrent.com/api/v1/inbox/${chatId}`, {
        method: 'DELETE',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Chat deleted successfully');
        alert('Conversation deleted');
        // Redirect to inbox
        window.location.href = '/inbox';
    } else {
        console.error('Error:', data.message);
        alert('Failed to delete conversation');
    }
}

// Usage
deleteChat(1234);

Important Notes

  • Deletion is permanent and cannot be undone
  • Deletes all messages in the conversation
  • Deletes the chat record itself
  • Does NOT delete the contact - they remain in your contacts list
  • If the contact messages you again, a new conversation will be created

Inbox & Messaging Best Practices

Conversation Management

  • Use Filters: Leverage folder and time filters to organize conversations
  • Monitor Unread: Use unread=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:

// Polling example - check for new messages every 10 seconds
let lastCheckTime = new Date();

async function pollNewMessages() {
    const chats = await getInboxMessages({
        time: 'today',
        limit: 50
    });

    // Find conversations updated since last check
    const newMessages = chats.data.filter(chat =>
        new Date(chat.last_chat_time) > lastCheckTime
    );

    if (newMessages.length > 0) {
        console.log(`${newMessages.length} new message(s)`);
        // Update UI
        refreshInbox();
        // Play notification sound
        playNotificationSound();
    }

    lastCheckTime = new Date();
}

// Poll every 10 seconds
setInterval(pollNewMessages, 10000);

Complete Inbox Management Example

class InboxManager {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1';
    }

    async getConversations(filters = {}) {
        const params = new URLSearchParams(filters);
        return await this.apiCall('GET', `/inbox?${params}`);
    }

    async getLastChat() {
        return await this.apiCall('GET', '/inbox/last-chat');
    }

    async getTemplates(search = null) {
        const params = search ? `?search=${search}` : '';
        return await this.apiCall('GET', `/inbox/templates${params}`);
    }

    async getActiveNumbers() {
        return await this.apiCall('GET', '/inbox/numbers/active');
    }

    async getReceiverNumbers() {
        return await this.apiCall('GET', '/inbox/numbers/receiver');
    }

    async getChatDetails(chatId, page = 1, limit = 10) {
        return await this.apiCall('GET', `/inbox/${chatId}?page=${page}&limit=${limit}`);
    }

    async deleteChat(chatId) {
        return await this.apiCall('DELETE', `/inbox/${chatId}`);
    }

    async apiCall(method, endpoint, data = null) {
        const headers = {
            'X-API-SID': this.apiSid,
            'X-API-PUBLIC-KEY': this.apiKey,
            'Accept': 'application/json'
        };

        if (data && method !== 'GET') {
            headers['Content-Type'] = 'application/json';
        }

        const options = {
            method,
            headers,
            body: data && method !== 'GET' ? JSON.stringify(data) : null
        };

        const response = await fetch(this.baseUrl + endpoint, options);
        const result = await response.json();

        if (!result.success) {
            throw new Error(result.message);
        }

        return result;
    }
}

// Usage
const inbox = new InboxManager('SID....................................', 'PK.....................................');

// Get unread conversations
const unreadChats = await inbox.getConversations({ unread: true });

// Resume last conversation
const lastChat = await inbox.getLastChat();
if (lastChat.data) {
    const chatDetails = await inbox.getChatDetails(lastChat.data.chat_id);
}

// Get templates for quick replies
const templates = await inbox.getTemplates();

// Delete old conversations
await inbox.deleteChat(1234);

UI/UX Recommendations

  • Show unread count badge on conversation list items
  • Highlight conversations with unread messages
  • Auto-scroll to newest message when opening conversation
  • Show typing indicator for better user experience
  • Display timestamp in relative format ("2 minutes ago")
  • Group messages by date for easier navigation
  • Show delivery/read status for sent messages

Common Mistakes to Avoid

  • ❌ Loading all messages at once without pagination
  • ❌ Not confirming before deleting conversations
  • ❌ Ignoring unread message counts
  • ❌ Not implementing search debouncing
  • ❌ Forgetting to refresh conversation list after actions
  • ✅ Use pagination for messages and conversations
  • ✅ Implement confirmation dialogs for destructive actions
  • ✅ Show clear visual indicators for unread messages
  • ✅ Implement efficient search with debouncing
  • ✅ Refresh data after send/delete operations

4.9 Send Message

Send an SMS or MMS message to a contact in an existing conversation. Supports both text messages and media attachments (MMS). Messages are automatically cleaned using AI to fix encoding issues, and credits are deducted based on message type and gateway.

POST /api/v1/inbox/chat

Request Headers

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

Request Body Parameters

Parameter Type Required Description
message string Yes Message content (max 5000 characters)
chat_id integer Yes ID of the conversation thread
from_number string Yes Sender phone number (must exist in your active numbers)
to_number string Yes Recipient phone number
chatFile file No Media file attachment for MMS (image, video, audio)

Example Request - Send SMS

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

Example Request - Send MMS with Media

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

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Message send successfully",
    "data": {
        "id": 5002,
        "chat_id": 1234,
        "direction": "outbound",
        "message": "Hello! How can I help you today?",
        "msg_type": "sms",
        "file": null,
        "api_send_status": "sent",
        "msg_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "created_at": "2025-10-17T15:30:00.000000Z",
        "updated_at": "2025-10-17T15:30:05.000000Z"
    },
    "errors": null
}

Error Response - Invalid Sender Number (400 Bad Request)

{
    "code": 400,
    "success": false,
    "message": "Invalid sender number",
    "data": null,
    "errors": null
}

Error Response - Insufficient Credits (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "You do not have enough credits to perform this action.",
    "data": null,
    "errors": null
}

Error Response - Daily Limit Reached (403 Forbidden)

{
    "code": 403,
    "success": false,
    "message": "You have reached your daily limit for sending messages.",
    "data": null,
    "errors": null
}

Error Response - Inactive Number (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Unable to send message. Sender number is not active!",
    "data": null,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "message": ["The message field is required."],
        "from_number": ["The selected from number is invalid."],
        "chat_id": ["The chat id field is required."],
        "to_number": ["The to number field is required."]
    }
}

Example in JavaScript

async function sendMessage(chatId, message, fromNumber, toNumber, mediaFile = null) {
    const formData = new FormData();
    formData.append('chat_id', chatId);
    formData.append('message', message);
    formData.append('from_number', fromNumber);
    formData.append('to_number', toNumber);

    if (mediaFile) {
        formData.append('chatFile', mediaFile);
    }

    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/chat', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        },
        body: formData
    });

    const data = await response.json();

    if (data.success) {
        console.log('Message sent:', data.data);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Send SMS
await sendMessage(1234, 'Hello!', '+12025559999', '+12025551234');

// Usage - Send MMS with image
const fileInput = document.getElementById('file-input');
const file = fileInput.files[0];
await sendMessage(1234, 'Check this out!', '+12025559999', '+12025551234', file);

Credit Costs

Gateway Type SMS (per segment) MMS
Own Gateway 1 credit 3 credits + SMS credits for text
Text Torrent Gateway 3 credits 7 credits + SMS credits for text

Important Notes

  • Messages are automatically cleaned using AI to fix encoding issues (like corrupted characters)
  • SMS messages are segmented: 160 chars for GSM-7, 70 chars for UCS-2 (Unicode)
  • Multi-segment messages cost credits per segment
  • MMS costs more credits and requires media file upload
  • Sub-accounts have daily sending limits
  • Trial accounts append "- Powered by Text Torrent" to messages
  • Auto-recharge triggers if enabled when credits are low
  • Sender number must be active and belong to your account

4.10 Start New Chat

Create a new conversation thread with a contact. If the contact doesn't exist in your contact list, they will be automatically created. This is useful for initiating conversations with new or existing contacts.

POST /api/v1/inbox/chat/create

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Request Body Parameters

Parameter Type Required Description
receiver_number string Yes Receiver's phone number (10 digits, without +1)
sender_id string Yes Sender phone number (your active number)

Example Request

curl -X POST "https://api.texttorrent.com/api/v1/inbox/chat/create" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "receiver_number": "2025551234",
    "sender_id": "+12025559999"
  }'

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Chat started successfully.",
    "data": {
        "id": 1235,
        "user_id": 1,
        "contact_id": 568,
        "from_number": "+12025559999",
        "last_message": null,
        "created_at": "2025-10-17T15:45:00.000000Z",
        "updated_at": "2025-10-17T15:45:00.000000Z"
    },
    "errors": null
}

Error Response - Contact Blacklisted (404 Not Found)

{
    "code": 404,
    "success": false,
    "message": "This contact is blacklisted.",
    "data": null,
    "errors": null
}

Error Response - Chat Already Exists (404 Not Found)

{
    "code": 404,
    "success": false,
    "message": "You have already started a chat with this contact.",
    "data": null,
    "errors": null
}

Example in JavaScript

async function startNewChat(receiverNumber, senderNumber) {
    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/chat/create', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            receiver_number: receiverNumber,
            sender_id: senderNumber
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Chat created:', data.data);
        // Navigate to the new chat
        window.location.href = `/inbox/${data.data.id}`;
        return data.data;
    } else {
        console.error('Error:', data.message);
        alert(data.message);
    }
}

// Usage - Start chat with new number
await startNewChat('2025551234', '+12025559999');

Important Notes

  • Phone number is automatically prefixed with +1 (US numbers)
  • If contact doesn't exist, a new contact record is created
  • Cannot create chat with blacklisted contacts
  • Prevents duplicate chats - returns error if chat already exists
  • Chat is created empty - use "Send Message" endpoint to send first message

4.11 Generate AI Replies

Generate 5-7 AI-powered reply suggestions based on the conversation history. The AI analyzes the tone, context, and recent messages to provide natural, contextually appropriate responses. Useful for quick replies and maintaining conversation flow.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Request Body Parameters

Parameter Type Required Description
chat_id integer Yes ID of the conversation (must exist in chats table)
direction string Yes Message direction: inbound or outbound
message string Yes The last message content for context

Example Request

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

Success Response (200 OK)

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

Error Response - No Chat History (404 Not Found)

{
    "code": 404,
    "success": false,
    "message": "No chat history found for this chat.",
    "data": null,
    "errors": null
}

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

{
    "code": 422,
    "success": false,
    "message": "No suitable replies generated. The last message may be too short or passive",
    "data": null,
    "errors": null
}

Error Response - Insufficient Credits (404 Not Found)

{
    "code": 404,
    "success": false,
    "message": "You do not have enough credits to perform this action.",
    "data": null,
    "errors": null
}

Example in JavaScript

async function generateAiReplies(chatId, direction, message) {
    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/generate/ai/response', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            chat_id: chatId,
            direction: direction,
            message: message
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('AI suggestions:', data.data);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get AI reply suggestions
const suggestions = await generateAiReplies(1234, 'inbound', 'When will my order arrive?');

// Display suggestions in UI
function displaySuggestions(suggestions) {
    const container = document.getElementById('suggestions');
    container.innerHTML = '';

    suggestions.forEach((suggestion, index) => {
        const button = document.createElement('button');
        button.className = 'suggestion-btn';
        button.textContent = suggestion;
        button.onclick = () => {
            document.getElementById('message-input').value = suggestion;
        };
        container.appendChild(button);
    });
}

displaySuggestions(suggestions);

Credit Costs

Gateway Type Cost per Request
Own Gateway 3 credits
Text Torrent Gateway 6 credits

Important Notes

  • AI analyzes full conversation history for context
  • Returns 5-7 unique reply suggestions
  • Suggestions match the tone and style of the conversation
  • Filters out passive/short messages (e.g., "ok", "thanks") that don't need replies
  • Uses GPT-4o for high-quality, contextual responses
  • Credits are deducted per API call, not per suggestion
  • Auto-recharge triggers if enabled when credits are low
  • Requires active subscription

4.12 Block Contact

Add a contact to your blocked list. Blocked contacts cannot send you messages, and their conversations are hidden from your inbox. Useful for preventing unwanted communications.

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

Request Headers

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

Path Parameters

Parameter Type Required Description
contactId integer Yes ID of the contact to block

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact blacklisted successfully",
    "data": null,
    "errors": null
}

Example in JavaScript

async function blockContact(contactId) {
    if (!confirm('Block this contact? They will not be able to message you.')) {
        return;
    }

    const response = await fetch(`https://api.texttorrent.com/api/v1/inbox/blacklist/${contactId}`, {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Contact blocked successfully');
        alert('Contact has been blocked');
        // Refresh inbox list
        window.location.reload();
    } else {
        console.error('Error:', data.message);
        alert('Failed to block contact');
    }
}

// Usage
blockContact(567);

Important Notes

  • Blocked contacts are excluded from inbox listings
  • Existing conversation remains but is hidden
  • Records who blocked the contact and when
  • Blocked contacts can be unblocked later
  • No credits are deducted for blocking

4.13 Unblock Contact

Remove one or more contacts from your blocked list. Unblocked contacts can send you messages again, and their conversations will reappear in your inbox. Supports bulk unblocking.

POST /api/v1/inbox/unblock

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Request Body Parameters

Parameter Type Required Description
contact_ids array Yes Array of contact IDs to unblock (minimum 1)

Example Request - Unblock Single Contact

curl -X POST "https://api.texttorrent.com/api/v1/inbox/unblock" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_ids": [567]
  }'

Example Request - Unblock Multiple Contacts

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Contact unblocked successfully",
    "data": null,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "contact_ids": ["The contact ids field is required."],
        "contact_ids.0": ["The selected contact ids.0 is invalid."]
    }
}

Example in JavaScript

async function unblockContacts(contactIds) {
    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/unblock', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            contact_ids: contactIds
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Contacts unblocked successfully');
        alert(`${contactIds.length} contact(s) have been unblocked`);
        return true;
    } else {
        console.error('Error:', data.message);
        alert('Failed to unblock contacts');
        return false;
    }
}

// Usage - Unblock single contact
await unblockContacts([567]);

// Usage - Unblock multiple contacts
await unblockContacts([567, 568, 569]);

// Usage - Bulk unblock from checkbox selection
function bulkUnblock() {
    const checkboxes = document.querySelectorAll('.contact-checkbox:checked');
    const contactIds = Array.from(checkboxes).map(cb => parseInt(cb.value));

    if (contactIds.length === 0) {
        alert('Please select contacts to unblock');
        return;
    }

    if (confirm(`Unblock ${contactIds.length} contact(s)?`)) {
        unblockContacts(contactIds);
    }
}

Important Notes

  • Supports bulk unblocking (multiple contacts at once)
  • Resets all blacklist-related fields (blacklisted_by, blacklisted_by_word, blacklisted_at)
  • Conversations with unblocked contacts reappear in inbox
  • Contact IDs must exist in your contacts
  • No credits are deducted for unblocking

4.14 Add Event

Create a scheduled event or reminder related to a conversation. Events can trigger alerts at a specified time before the event, helping you stay on top of follow-ups, appointments, and important dates.

POST /api/v1/inbox/event/add

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Request Body Parameters

Parameter Type Required Description
name string Yes Event name/title
subject string Yes Event subject/description
date string Yes Event date (YYYY-MM-DD format)
time string Yes Event time (HH:MM format, 24-hour)
sender_number string Yes Phone number to send reminder from
alert_before integer Yes Minutes before event to send alert
participant_number string No Participant phone number
participant_email string No Participant email address (must be valid email format)

Example Request

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

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Event created successfully",
    "data": {
        "id": 123,
        "name": "Follow-up Call",
        "subject": "Discuss product demo with client",
        "date": "2025-10-20",
        "time": "14:00",
        "sender_number": "+12025559999",
        "alert_before": 30,
        "alert_at": "2025-10-20 13:30:00",
        "receiver_number": "+12025551111",
        "participant_number": "+12025551234",
        "participant_email": "client@example.com",
        "user_id": 1,
        "created_at": "2025-10-17T15:30:00.000000Z",
        "updated_at": "2025-10-17T15:30:00.000000Z"
    },
    "errors": null
}

Example in JavaScript

async function addEvent(eventData) {
    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/event/add', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(eventData)
    });

    const data = await response.json();

    if (data.success) {
        console.log('Event created:', data.data);
        alert('Event scheduled successfully');
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Create follow-up event
await addEvent({
    name: 'Follow-up Call',
    subject: 'Discuss product demo with client',
    date: '2025-10-20',
    time: '14:00',
    sender_number: '+12025559999',
    alert_before: 30,
    participant_number: '+12025551234',
    participant_email: 'client@example.com'
});

// Usage - Create event from form
function scheduleEventFromForm() {
    const form = document.getElementById('event-form');
    const formData = new FormData(form);

    const eventData = {
        name: formData.get('name'),
        subject: formData.get('subject'),
        date: formData.get('date'),
        time: formData.get('time'),
        sender_number: formData.get('sender_number'),
        alert_before: parseInt(formData.get('alert_before')),
        participant_number: formData.get('participant_number'),
        participant_email: formData.get('participant_email')
    };

    addEvent(eventData);
}

Important Notes

  • Alert time is automatically calculated: event_time - alert_before
  • Receiver number defaults to your account phone number
  • Events can be used for follow-ups, appointments, reminders
  • Alert is sent via SMS at the calculated alert time
  • Date must be in YYYY-MM-DD format (e.g., 2025-10-20)
  • Time must be in 24-hour format (e.g., 14:00 for 2:00 PM)
  • Participant fields are optional but useful for tracking

4.15 Export Inbox

Export all your inbox conversations to a CSV file. The export includes all contacts you've had conversations with (excluding blacklisted contacts). Useful for backups, data analysis, or importing into other systems.

POST /api/v1/inbox/export

Request Headers

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

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Exported successfully",
    "data": {
        "path": "inbox/chats_2025_10_17_15_30_45.csv",
        "url": "https://your-cdn.com/inbox/chats_2025_10_17_15_30_45.csv"
    },
    "errors": null
}

Example in JavaScript

async function exportInbox() {
    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/export', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Export successful:', data.data);

        // Download the file
        const link = document.createElement('a');
        link.href = data.data.url;
        link.download = 'inbox_export.csv';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);

        alert('Inbox exported successfully');
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
exportInbox();

Important Notes

  • Exports all contacts with conversation history
  • Excludes blacklisted contacts
  • File is stored on cloud storage (DigitalOcean Spaces)
  • Filename includes timestamp for uniqueness
  • CSV includes contact IDs for reference
  • No credits are deducted for exporting
  • File remains accessible via the provided URL

4.16 Move Contact to Folder

Organize your inbox by moving a contact to a specific folder. This helps categorize conversations for better organization and filtering.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Request Body Parameters

Parameter Type Required Description
contact_id integer Yes ID of the contact to move (must exist in contacts table)
folder_id integer Yes ID of the destination folder

Example Request

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

Success Response (200 OK)

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

Example in JavaScript

async function moveContactToFolder(contactId, folderId) {
    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/to-folder/create', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            contact_id: contactId,
            folder_id: folderId
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Contact moved:', data.data);
        alert('Contact moved to folder successfully');
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
await moveContactToFolder(567, 5);

// Usage - Create folder selector dropdown
function createFolderMoveAction(folders, contactId) {
    const select = document.createElement('select');
    select.innerHTML = '';

    folders.forEach(folder => {
        const option = document.createElement('option');
        option.value = folder.id;
        option.textContent = folder.name;
        select.appendChild(option);
    });

    select.addEventListener('change', async (e) => {
        if (e.target.value) {
            await moveContactToFolder(contactId, parseInt(e.target.value));
        }
    });

    return select;
}

Important Notes

  • Contact must exist in your contacts
  • Updates the contact's folder_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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Request Body Parameters

Parameter Type Required Description
contact_id integer Yes ID of the contact to add note to
note string Yes Note content

Example Request

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

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Note added successfully.",
    "data": {
        "id": 123,
        "contact_id": 567,
        "note": "VIP customer - priority support. Interested in enterprise plan.",
        "created_at": "2025-10-17T15:50:00.000000Z",
        "updated_at": "2025-10-17T15:50:00.000000Z"
    },
    "errors": null
}

Example in JavaScript

async function addInboxNote(contactId, noteText) {
    const response = await fetch('https://api.texttorrent.com/api/v1/inbox/note/add', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            contact_id: contactId,
            note: noteText
        })
    });

    const data = await response.json();

    if (data.success) {
        console.log('Note added:', data.data);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
await addInboxNote(567, 'VIP customer - priority support');

Important Notes

  • Same as Contact Notes API, accessible from inbox context
  • Notes are displayed in chat details
  • Useful for tracking customer preferences, special instructions, or follow-up items
  • Multiple notes can be added to a single contact
  • No credits are deducted for adding notes

4.18 Delete Inbox Note

Remove a note from a contact. This permanently deletes the note from the contact record.

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

Request Headers

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

Path Parameters

Parameter Type Required Description
id integer Yes ID of the note to delete

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Note deleted successfully.",
    "data": {
        "id": 123,
        "contact_id": 567,
        "note": "VIP customer - priority support. Interested in enterprise plan.",
        "created_at": "2025-10-17T15:50:00.000000Z",
        "updated_at": "2025-10-17T15:50:00.000000Z"
    },
    "errors": null
}

Example in JavaScript

async function deleteInboxNote(noteId) {
    if (!confirm('Delete this note?')) {
        return;
    }

    const response = await fetch(`https://api.texttorrent.com/api/v1/inbox/note/delete/${noteId}`, {
        method: 'DELETE',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        }
    });

    const data = await response.json();

    if (data.success) {
        console.log('Note deleted successfully');
        return true;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
await deleteInboxNote(123);

Important Notes

  • Deletion is permanent and cannot be undone
  • Returns the deleted note data in the response
  • No credits are deducted for deleting notes

5. Sub-Account Management

The Sub-Account Management API allows you to create and manage sub-users under your main account. Sub-accounts can have limited permissions, daily SMS limits, and role-based access control. This is perfect for teams, agencies, or organizations that need multiple users with controlled access.

Key Features:

  • Create and manage unlimited sub-accounts
  • Set daily SMS sending limits per sub-account
  • Assign roles and permissions
  • Enable/disable sub-account access
  • Login as sub-user for support purposes
  • Search and filter sub-accounts
  • Paginated sub-account listings
ℹ️ 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

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

Query Parameters

Parameter Type Required Description
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)
search string No Search by first name or last name
sortBy string No Field to sort by (default: id)
sortType string No Sort direction: asc or desc (default: desc)

Example Request - Get All Sub-Accounts

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

Example Request - Search and Sort

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Retrieved successfully",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 101,
                "first_name": "John",
                "last_name": "Smith",
                "username": "johnsmith",
                "email": "john.smith@example.com",
                "type": "sub",
                "parent_id": 1,
                "status": 1,
                "role": 2,
                "permissions": ["view_contacts", "send_messages"],
                "daily_sms_limit": 500,
                "admin_approval": 1,
                "created_at": "2025-09-15T10:30:00.000000Z",
                "updated_at": "2025-10-17T14:20:00.000000Z",
                "role": {
                    "id": 2,
                    "name": "Sales Agent"
                }
            },
            {
                "id": 102,
                "first_name": "Sarah",
                "last_name": "Johnson",
                "username": "sarahjohnson",
                "email": "sarah.johnson@example.com",
                "type": "sub",
                "parent_id": 1,
                "status": 1,
                "role": 3,
                "permissions": ["view_contacts", "send_messages", "view_analytics"],
                "daily_sms_limit": 1000,
                "admin_approval": 1,
                "created_at": "2025-09-20T11:15:00.000000Z",
                "updated_at": "2025-10-16T09:30:00.000000Z",
                "role": {
                    "id": 3,
                    "name": "Marketing Manager"
                }
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/user/sub-account/list?page=1",
        "from": 1,
        "last_page": 3,
        "last_page_url": "https://api.texttorrent.com/api/v1/user/sub-account/list?page=3",
        "next_page_url": "https://api.texttorrent.com/api/v1/user/sub-account/list?page=2",
        "path": "https://api.texttorrent.com/api/v1/user/sub-account/list",
        "per_page": 10,
        "prev_page_url": null,
        "to": 10,
        "total": 25
    },
    "errors": null
}

Sub-Account Fields

Field Type Description
type string Always "sub" for sub-accounts
parent_id integer ID of the parent account (your account)
status integer Account status: 1 = active, 0 = inactive
permissions array List of permission strings assigned to this sub-account
daily_sms_limit integer Maximum SMS messages this sub-account can send per day
admin_approval integer Admin approval status: 1 = approved, 0 = pending

Example in JavaScript

async function getSubAccounts(options = {}) {
    const params = new URLSearchParams({
        page: options.page || 1,
        limit: options.limit || 10
    });

    if (options.search) params.append('search', options.search);
    if (options.sortBy) params.append('sortBy', options.sortBy);
    if (options.sortType) params.append('sortType', options.sortType);

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/user/sub-account/list?${params}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Sub-accounts:', data.data.data);
        console.log(`Total sub-accounts: ${data.data.total}`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get all sub-accounts
const subAccounts = await getSubAccounts();

// Usage - Search sub-accounts
const searchResults = await getSubAccounts({ search: 'john' });

// Usage - Sort by name ascending
const sorted = await getSubAccounts({ sortBy: 'first_name', sortType: 'asc' });

Important Notes

  • Only returns sub-accounts where parent_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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Request Body Parameters

Parameter Type Required Description
first_name string Yes First name (max 20 characters)
last_name string Yes Last name (max 20 characters)
username string Yes Unique username for login
email string Yes Unique email address
password string Yes Password (minimum 6 characters)
confirm_password string Yes Must match password
role integer Yes Role ID (must exist in user_roles table)
permissions array No Array of permission strings
sms_limit integer No Daily SMS limit (default: 0 = unlimited)

Example Request

curl -X POST "https://api.texttorrent.com/api/v1/user/sub-account/store" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "John",
    "last_name": "Smith",
    "username": "johnsmith",
    "email": "john.smith@example.com",
    "password": "SecurePass123",
    "confirm_password": "SecurePass123",
    "role": 2,
    "permissions": ["view_contacts", "send_messages", "view_inbox"],
    "sms_limit": 500
  }'

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Sub Account Created Successfully",
    "data": {
        "id": 103,
        "first_name": "John",
        "last_name": "Smith",
        "username": "johnsmith",
        "email": "john.smith@example.com",
        "type": "sub",
        "parent_id": 1,
        "status": 1,
        "role": 2,
        "permissions": ["view_contacts", "send_messages", "view_inbox"],
        "daily_sms_limit": 500,
        "admin_approval": 1,
        "created_at": "2025-10-17T16:00:00.000000Z",
        "updated_at": "2025-10-17T16:00:00.000000Z"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "username": ["The username has already been taken."],
        "email": ["The email has already been taken."],
        "password": ["The password must be at least 6 characters."],
        "confirm_password": ["The confirm password and password must match."],
        "role": ["The selected role is invalid."]
    }
}

Example in JavaScript

async function createSubAccount(accountData) {
    const response = await fetch('https://api.texttorrent.com/api/v1/user/sub-account/store', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(accountData)
    });

    const data = await response.json();

    if (data.success) {
        console.log('Sub-account created:', data.data);
        alert('Sub-account created successfully! Welcome email sent.');
        return data.data;
    } else {
        console.error('Error:', data.message, data.errors);
        throw new Error(data.message);
    }
}

// Usage
await createSubAccount({
    first_name: 'John',
    last_name: 'Smith',
    username: 'johnsmith',
    email: 'john.smith@example.com',
    password: 'SecurePass123',
    confirm_password: 'SecurePass123',
    role: 2,
    permissions: ['view_contacts', 'send_messages', 'view_inbox'],
    sms_limit: 500
});

Important Notes

  • Username and email must be unique across all users
  • Welcome email is automatically sent to the sub-account
  • Sub-account is automatically approved (admin_approval=1)
  • Sub-account is created with active status (status=1)
  • Password is securely hashed before storage
  • Daily SMS limit of 0 means unlimited sending
  • Sub-accounts share your main account's credits

5.3 Get Sub-Account Details

Retrieve detailed information about a specific sub-account. Useful for viewing full sub-account profile before editing or for displaying sub-account information in your UI.

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

Request Headers

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

Path Parameters

Parameter Type Required Description
id integer Yes Sub-account user ID

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Sub Account Retrieved Successfully",
    "data": {
        "id": 101,
        "first_name": "John",
        "last_name": "Smith",
        "username": "johnsmith",
        "email": "john.smith@example.com",
        "type": "sub",
        "parent_id": 1,
        "status": 1,
        "role": 2,
        "permissions": ["view_contacts", "send_messages", "view_inbox"],
        "daily_sms_limit": 500,
        "admin_approval": 1,
        "created_at": "2025-09-15T10:30:00.000000Z",
        "updated_at": "2025-10-17T14:20:00.000000Z"
    },
    "errors": null
}

Error Response - Not Found (404 Not Found)

{
    "code": 404,
    "success": false,
    "message": "Sub Account Not Found",
    "data": [],
    "errors": null
}

Example in JavaScript

async function getSubAccountDetails(subAccountId) {
    const response = await fetch(
        `https://api.texttorrent.com/api/v1/user/sub-account/show/${subAccountId}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Sub-account details:', data.data);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
const subAccount = await getSubAccountDetails(101);

Important Notes

  • Only returns sub-accounts that belong to your account (parent_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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Path Parameters

Parameter Type Required Description
id integer Yes Sub-account user ID to update

Request Body Parameters

Parameter Type Required Description
first_name string Yes First name (max 20 characters)
last_name string Yes Last name (max 20 characters)
username string Yes Username (unique, except current user)
email string Yes Email address (unique, except current user)
password string No New password (minimum 6 characters)
confirm_password string No* Required if password is provided, must match password
role integer Yes Role ID (must exist in user_roles table)
permissions array No Array of permission strings
sms_limit integer No Daily SMS limit (default: 0 = unlimited)

Example Request - Update Without Password

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

Example Request - Update With Password

curl -X PUT "https://api.texttorrent.com/api/v1/user/sub-account/update/101" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "John",
    "last_name": "Smith",
    "username": "johnsmith",
    "email": "john.smith@example.com",
    "password": "NewSecurePass456",
    "confirm_password": "NewSecurePass456",
    "role": 2,
    "permissions": ["view_contacts", "send_messages", "view_inbox"],
    "sms_limit": 1000
  }'

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Sub Account Updated Successfully",
    "data": {
        "id": 101,
        "first_name": "John",
        "last_name": "Smith",
        "username": "johnsmith",
        "email": "john.smith@example.com",
        "type": "sub",
        "parent_id": 1,
        "status": 1,
        "role": 2,
        "permissions": ["view_contacts", "send_messages", "view_inbox", "view_analytics"],
        "daily_sms_limit": 1000,
        "admin_approval": 1,
        "created_at": "2025-09-15T10:30:00.000000Z",
        "updated_at": "2025-10-17T16:15:00.000000Z"
    },
    "errors": null
}

Error Response - Not Found (404 Not Found)

{
    "code": 404,
    "success": false,
    "message": "Sub Account Not Found",
    "data": [],
    "errors": null
}

Example in JavaScript

async function updateSubAccount(subAccountId, accountData) {
    const response = await fetch(
        `https://api.texttorrent.com/api/v1/user/sub-account/update/${subAccountId}`,
        {
            method: 'PUT',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(accountData)
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Sub-account updated:', data.data);
        alert('Sub-account updated successfully!');
        return data.data;
    } else {
        console.error('Error:', data.message, data.errors);
        throw new Error(data.message);
    }
}

// Usage - Update without password
await updateSubAccount(101, {
    first_name: 'John',
    last_name: 'Smith',
    username: 'johnsmith',
    email: 'john.smith@example.com',
    role: 2,
    permissions: ['view_contacts', 'send_messages', 'view_inbox', 'view_analytics'],
    sms_limit: 1000
});

// Usage - Update with password
await updateSubAccount(101, {
    first_name: 'John',
    last_name: 'Smith',
    username: 'johnsmith',
    email: 'john.smith@example.com',
    password: 'NewSecurePass456',
    confirm_password: 'NewSecurePass456',
    role: 2,
    permissions: ['view_contacts', 'send_messages'],
    sms_limit: 1000
});

Important Notes

  • Password field is optional - omit to keep existing password
  • Username and email must be unique (excluding current user)
  • Password is securely hashed if provided
  • All fields except password are required
  • Returns 404 if sub-account doesn't exist or doesn't belong to you

5.5 Delete Sub-Account

Permanently delete a sub-account from your account. This action cannot be undone. All data associated with the sub-account will be removed.

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

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

Path Parameters

Parameter Type Required Description
id integer Yes Sub-account user ID to delete

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Sub Account Deleted Successfully",
    "data": [],
    "errors": null
}

Error Response - Not Found (404 Not Found)

{
    "code": 404,
    "success": false,
    "message": "Sub Account Not Found",
    "data": [],
    "errors": null
}

Example in JavaScript

async function deleteSubAccount(subAccountId) {
    if (!confirm('Delete this sub-account? This action cannot be undone.')) {
        return;
    }

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/user/sub-account/delete/${subAccountId}`,
        {
            method: 'DELETE',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Sub-account deleted successfully');
        alert('Sub-account has been deleted');
        return true;
    } else {
        console.error('Error:', data.message);
        alert('Failed to delete sub-account');
        return false;
    }
}

// Usage
await deleteSubAccount(101);

Important Notes

  • Deletion is permanent and cannot be undone
  • Only sub-accounts belonging to your account can be deleted
  • Returns 404 if sub-account doesn't exist or doesn't belong to you
  • Consider disabling instead of deleting if you might need the account later

5.6 Change Sub-Account Status

Enable or disable a sub-account. Disabled sub-accounts cannot log in or access the system. This is useful for temporarily suspending access without deleting the account.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Path Parameters

Parameter Type Required Description
id integer Yes Sub-account user ID

Request Body Parameters

Parameter Type Required Description
status integer Yes New status: 1 = active, 0 = inactive

Example Request - Disable Sub-Account

curl -X PATCH "https://api.texttorrent.com/api/v1/user/sub-account/status/101" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "status": 0
  }'

Example Request - Enable Sub-Account

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Sub Account Status Updated Successfully",
    "data": {
        "id": 101,
        "first_name": "John",
        "last_name": "Smith",
        "username": "johnsmith",
        "email": "john.smith@example.com",
        "type": "sub",
        "parent_id": 1,
        "status": 0,
        "role": 2,
        "permissions": ["view_contacts", "send_messages"],
        "daily_sms_limit": 500,
        "admin_approval": 1,
        "created_at": "2025-09-15T10:30:00.000000Z",
        "updated_at": "2025-10-17T16:30:00.000000Z"
    },
    "errors": null
}

Error Response - Not Found (404 Not Found)

{
    "code": 404,
    "success": false,
    "message": "Sub Account Not Found",
    "data": [],
    "errors": null
}

Example in JavaScript

async function changeSubAccountStatus(subAccountId, status) {
    const statusText = status === 1 ? 'enable' : 'disable';

    if (!confirm(`Are you sure you want to ${statusText} this sub-account?`)) {
        return;
    }

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/user/sub-account/status/${subAccountId}`,
        {
            method: 'PATCH',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ status })
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Status updated:', data.data);
        alert(`Sub-account ${statusText}d successfully`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Disable sub-account
await changeSubAccountStatus(101, 0);

// Usage - Enable sub-account
await changeSubAccountStatus(101, 1);

// Usage - Toggle status
async function toggleSubAccountStatus(subAccount) {
    const newStatus = subAccount.status === 1 ? 0 : 1;
    return await changeSubAccountStatus(subAccount.id, newStatus);
}

Important Notes

  • Status 1 = active (can log in), Status 0 = inactive (cannot log in)
  • Inactive sub-accounts cannot access the system
  • Use this instead of deleting if you want to temporarily suspend access
  • Sub-account data is preserved when disabled
  • Returns 404 if sub-account doesn't exist

5.7 Login as Sub-User

Generate an authentication token to log in as a sub-user. This is useful for support purposes, allowing you to view the system from the sub-account's perspective without knowing their password.

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

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

Path Parameters

Parameter Type Required Description
id integer Yes Sub-account user ID to log in as

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Logged in as Sub Account Successfully",
    "data": {
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
    },
    "errors": null
}

Error Response - Not Found (404 Not Found)

{
    "code": 404,
    "success": false,
    "message": "Sub Account Not Found",
    "data": [],
    "errors": null
}

Example in JavaScript

async function loginAsSubUser(subAccountId) {
    const response = await fetch(
        `https://api.texttorrent.com/api/v1/user/sub-account/login-as-sub-user/${subAccountId}`,
        {
            method: 'POST',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Sub-user token generated:', data.data.token);

        // Store the token
        localStorage.setItem('sub_user_token', data.data.token);

        // Redirect to dashboard as sub-user
        window.location.href = '/dashboard?as_sub_user=true';

        return data.data.token;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
await loginAsSubUser(101);

// Usage - With confirmation
async function impersonateSubUser(subAccountId, subAccountName) {
    if (confirm(`Log in as ${subAccountName}? You'll be viewing the system from their perspective.`)) {
        const token = await loginAsSubUser(subAccountId);
        console.log('Now viewing as sub-user');
    }
}

Example in PHP

<?php
$subAccountId = 101;

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.texttorrent.com/api/v1/user/sub-account/login-as-sub-user/{$subAccountId}");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-SID: SID....................................',
    'X-API-PUBLIC-KEY: PK.....................................',
    'Accept: application/json'
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

if ($data['success']) {
    $token = $data['data']['token'];

    // Store token in session for sub-user access
    $_SESSION['sub_user_token'] = $token;
    $_SESSION['is_impersonating'] = true;

    // Redirect to dashboard
    header('Location: /dashboard');
    exit;
} else {
    echo "Error: " . $data['message'];
}
?>

Important Notes

  • Generates a valid OAuth access token for the sub-account
  • Token can be used for all API requests as that sub-user
  • Only works for sub-accounts that belong to your account
  • Use for support purposes only (troubleshooting, training, etc.)
  • Consider logging impersonation events for security audit trails
  • Token follows standard OAuth token expiration rules

Sub-Account Management Best Practices

Security

  • Strong Passwords: Enforce minimum 6 characters (consider increasing for production)
  • Unique Credentials: Each sub-account must have unique username and email
  • Audit Trail: Log all sub-account creation, updates, and impersonation events
  • Regular Review: Periodically review and disable unused sub-accounts

Permission Management

  • Principle of Least Privilege: Grant only necessary permissions
  • Role-Based Access: Use roles to group common permission sets
  • Document Permissions: Clearly document what each permission allows
  • Regular Audits: Review permissions quarterly

Daily Limits

  • Set Appropriate Limits: Based on sub-account role and responsibility
  • Monitor Usage: Track daily SMS usage per sub-account
  • Adjust as Needed: Increase limits for productive users
  • Zero for Unlimited: Use 0 for sub-accounts that need unlimited sending

Complete Sub-Account Management Example

class SubAccountManager {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1/user/sub-account';
    }

    async list(options = {}) {
        const params = new URLSearchParams(options);
        return await this.apiCall('GET', `/list?${params}`);
    }

    async create(accountData) {
        return await this.apiCall('POST', '/store', accountData);
    }

    async get(id) {
        return await this.apiCall('GET', `/show/${id}`);
    }

    async update(id, accountData) {
        return await this.apiCall('PUT', `/update/${id}`, accountData);
    }

    async delete(id) {
        return await this.apiCall('DELETE', `/delete/${id}`);
    }

    async changeStatus(id, status) {
        return await this.apiCall('PATCH', `/status/${id}`, { status });
    }

    async loginAs(id) {
        return await this.apiCall('POST', `/login-as-sub-user/${id}`);
    }

    async apiCall(method, endpoint, data = null) {
        const headers = {
            'X-API-SID': this.apiSid,
            'X-API-PUBLIC-KEY': this.apiKey,
            'Accept': 'application/json'
        };

        if (data && method !== 'GET') {
            headers['Content-Type'] = 'application/json';
        }

        const options = {
            method,
            headers,
            body: data && method !== 'GET' ? JSON.stringify(data) : null
        };

        const response = await fetch(this.baseUrl + endpoint, options);
        const result = await response.json();

        if (!result.success) {
            throw new Error(result.message);
        }

        return result;
    }
}

// Usage
const subAccountMgr = new SubAccountManager('SID....................................', 'PK.....................................');

// List sub-accounts
const accounts = await subAccountMgr.list({ search: 'john' });

// Create new sub-account
const newAccount = await subAccountMgr.create({
    first_name: 'Jane',
    last_name: 'Doe',
    username: 'janedoe',
    email: 'jane.doe@example.com',
    password: 'SecurePass123',
    confirm_password: 'SecurePass123',
    role: 2,
    permissions: ['view_contacts', 'send_messages'],
    sms_limit: 500
});

// Update sub-account
await subAccountMgr.update(101, {
    first_name: 'John',
    last_name: 'Smith',
    username: 'johnsmith',
    email: 'john.smith@example.com',
    role: 2,
    permissions: ['view_contacts', 'send_messages', 'view_analytics'],
    sms_limit: 1000
});

// Disable sub-account
await subAccountMgr.changeStatus(101, 0);

// Login as sub-user
const token = await subAccountMgr.loginAs(101);

Common Mistakes to Avoid

  • ❌ Sharing account credentials between multiple people
  • ❌ Granting excessive permissions "just in case"
  • ❌ Not setting daily limits for untested sub-accounts
  • ❌ Forgetting to disable sub-accounts when employees leave
  • ❌ Not logging impersonation events
  • ✅ Create individual sub-accounts for each team member
  • ✅ Use role-based permissions
  • ✅ Set appropriate daily limits based on role
  • ✅ Regularly audit and clean up unused accounts
  • ✅ Log all administrative actions

Typical Roles and Permissions

Role Permissions Daily Limit
Sales Agent view_contacts, send_messages, view_inbox 500
Marketing Manager view_contacts, send_messages, view_inbox, view_analytics, create_campaigns 1000
Support Rep view_contacts, send_messages, view_inbox, manage_notes 300
Admin All permissions 0 (unlimited)

6. Bulk Messaging & Campaigns

The Bulk Messaging API allows you to create and manage SMS/MMS campaigns to send messages to multiple contacts at once. You can schedule campaigns, use message templates, implement batch processing, and track campaign performance. Perfect for marketing campaigns, announcements, and mass notifications.

Key Features:

  • Send SMS and MMS campaigns to contact lists
  • Schedule campaigns for future delivery
  • Batch processing with customizable size and frequency
  • Round-robin number pool distribution
  • Message template support
  • Spin text variations for personalization
  • Automatic opt-out link inclusion
  • Credit calculation and validation
  • Real-time segment counting
  • Campaign status tracking
ℹ️ 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

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

Query Parameters

Parameter Type Required Description
search_number string No Search/filter contact lists by name
search_contact string No Search/filter active numbers

Example Request

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

Example Request - With Search

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Data fetched successfully",
    "data": {
        "contactLists": [
            {
                "id": 45,
                "name": "VIP Customers",
                "bookmarked": 1,
                "total_contacts": 1250
            },
            {
                "id": 42,
                "name": "Newsletter Subscribers",
                "bookmarked": 0,
                "total_contacts": 3420
            }
        ],
        "activeNumbers": [
            {
                "id": 12,
                "number": "+15551234567",
                "friendly_name": "Main Business Line",
                "capabilities": {"sms": true, "mms": true, "voice": true},
                "status": 1
            },
            {
                "id": 15,
                "number": "+15559876543",
                "friendly_name": "Marketing Line",
                "capabilities": {"sms": true, "mms": true, "voice": false},
                "status": 1
            }
        ],
        "subUsers": [
            {
                "id": 1,
                "name": "John Smith (2)",
                "numbers": ["+15551234567", "+15559876543"]
            },
            {
                "id": 101,
                "name": "Sarah Johnson (1)",
                "numbers": ["+15556543210"]
            }
        ]
    },
    "errors": null
}

Response Fields

Field Type Description
contactLists array Available contact lists with total contact counts
activeNumbers array Active phone numbers available for sending (status=1)
subUsers array Sub-accounts and their associated active phone numbers
total_contacts integer Number of contacts in each list
capabilities object Number capabilities (sms, mms, voice)

Example in JavaScript

async function getCampaignData(searchOptions = {}) {
    const params = new URLSearchParams();

    if (searchOptions.contactList) {
        params.append('search_number', searchOptions.contactList);
    }
    if (searchOptions.phoneNumber) {
        params.append('search_contact', searchOptions.phoneNumber);
    }

    const queryString = params.toString() ? `?${params}` : '';

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/campaigning/bulk${queryString}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Contact Lists:', data.data.contactLists);
        console.log('Active Numbers:', data.data.activeNumbers);
        console.log('Sub Users:', data.data.subUsers);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get all data
const campaignData = await getCampaignData();

// Usage - Search contact lists
const filtered = await getCampaignData({
    contactList: 'customers',
    phoneNumber: '+1'
});

Important Notes

  • Only returns contact lists where user_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

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

Request Body Parameters

Parameter Type Required Description
campaign_name string Yes Campaign name (max 255 characters)
contact_list_id integer Yes ID of contact list to send to (must exist)
template_id integer Yes ID of message template (must exist)
sms_type string Yes Message type: sms or mms
sms_body string Yes Message content (max 1600 characters)
numbers array Yes Array of phone numbers to send from (max 15 chars each)
file file No* Media file for MMS (required if sms_type=mms)
appended_message string No Additional text appended to message (max 1600 chars)
number_pool boolean No Enable number pool distribution
batch_process boolean No Enable batch processing
opt_out_link boolean No Include opt-out link in message
round_robin_campaign boolean No Enable round-robin number rotation
batch_size integer No Number of messages per batch (if batch_process enabled)
batch_frequency integer No Minutes between batches (if batch_process enabled)
selected_date date No Schedule date (YYYY-MM-DD format)
selected_time string No Schedule time (HH:MM format)
phone_numbers string No Comma-separated phone numbers
selected_users array No Array of sub-user IDs to use their numbers

Example Request - Simple SMS Campaign

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

Example Request - MMS Campaign with File

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

Example Request - Scheduled Campaign with Batch Processing

curl -X POST "https://api.texttorrent.com/api/v1/campaigning/bulk" \
  -H "X-API-SID: SID...................................." \
  -H "X-API-PUBLIC-KEY: PK....................................." \
  -H "Accept: application/json" \
  -F "campaign_name=Holiday Greetings" \
  -F "contact_list_id=45" \
  -F "template_id=12" \
  -F "sms_type=sms" \
  -F "sms_body=Happy Holidays from our team!" \
  -F "numbers[]=+15551234567" \
  -F "batch_process=true" \
  -F "batch_size=100" \
  -F "batch_frequency=30" \
  -F "selected_date=2025-12-25" \
  -F "selected_time=09:00"

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Campaign created successfully",
    "data": {
        "campaign_id": 567,
        "total_credit": 1875,
        "total_contacts": 1250,
        "total_segments": 1
    },
    "errors": null
}

Error Response - Insufficient Credits (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "You do not have enough credits to perform this action.",
    "data": null,
    "errors": null
}

Error Response - Trial Account (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "You cannot use this feature during the trial period.",
    "data": null,
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "campaign_name": ["The campaign name field is required."],
        "contact_list_id": ["The selected contact list id is invalid."],
        "template_id": ["The selected template id is invalid."],
        "sms_type": ["The sms type must be either sms or mms."],
        "file": ["The file field is required when sms type is mms."],
        "numbers": ["The numbers field is required."]
    }
}

Example in JavaScript

async function createCampaign(campaignData, mediaFile = null) {
    const formData = new FormData();

    // Required fields
    formData.append('campaign_name', campaignData.campaign_name);
    formData.append('contact_list_id', campaignData.contact_list_id);
    formData.append('template_id', campaignData.template_id);
    formData.append('sms_type', campaignData.sms_type);
    formData.append('sms_body', campaignData.sms_body);

    // Numbers array
    campaignData.numbers.forEach(number => {
        formData.append('numbers[]', number);
    });

    // Optional fields
    if (campaignData.appended_message) {
        formData.append('appended_message', campaignData.appended_message);
    }
    if (campaignData.number_pool) {
        formData.append('number_pool', campaignData.number_pool);
    }
    if (campaignData.batch_process) {
        formData.append('batch_process', campaignData.batch_process);
        formData.append('batch_size', campaignData.batch_size);
        formData.append('batch_frequency', campaignData.batch_frequency);
    }
    if (campaignData.opt_out_link) {
        formData.append('opt_out_link', campaignData.opt_out_link);
    }
    if (campaignData.round_robin_campaign) {
        formData.append('round_robin_campaign', campaignData.round_robin_campaign);
    }
    if (campaignData.selected_date) {
        formData.append('selected_date', campaignData.selected_date);
        formData.append('selected_time', campaignData.selected_time);
    }

    // MMS file
    if (mediaFile && campaignData.sms_type === 'mms') {
        formData.append('file', mediaFile);
    }

    const response = await fetch('https://api.texttorrent.com/api/v1/campaigning/bulk', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json'
        },
        body: formData
    });

    const data = await response.json();

    if (data.success) {
        console.log('Campaign created:', data.data);
        alert(`Campaign created! ID: ${data.data.campaign_id}, Credits: ${data.data.total_credit}`);
        return data.data;
    } else {
        console.error('Error:', data.message, data.errors);
        throw new Error(data.message);
    }
}

// Usage - Simple SMS campaign
await createCampaign({
    campaign_name: 'Spring Sale Announcement',
    contact_list_id: 45,
    template_id: 12,
    sms_type: 'sms',
    sms_body: 'Hi {first_name}, don\'t miss our Spring Sale! Get 20% off all items.',
    numbers: ['+15551234567', '+15559876543'],
    opt_out_link: true
});

// Usage - Scheduled campaign
await createCampaign({
    campaign_name: 'Holiday Greetings',
    contact_list_id: 45,
    template_id: 12,
    sms_type: 'sms',
    sms_body: 'Happy Holidays from our team!',
    numbers: ['+15551234567'],
    batch_process: true,
    batch_size: 100,
    batch_frequency: 30,
    selected_date: '2025-12-25',
    selected_time: '09:00'
});

// Usage - MMS campaign with file
const fileInput = document.getElementById('mms-file');
await createCampaign({
    campaign_name: 'Product Launch',
    contact_list_id: 45,
    template_id: 12,
    sms_type: 'mms',
    sms_body: 'Check out our new product!',
    numbers: ['+15551234567']
}, fileInput.files[0]);

Example in PHP

<?php
$ch = curl_init();

$campaignData = [
    'campaign_name' => 'Spring Sale Announcement',
    'contact_list_id' => 45,
    'template_id' => 12,
    'sms_type' => 'sms',
    'sms_body' => 'Hi {first_name}, don\'t miss our Spring Sale! Get 20% off all items.',
    'numbers' => ['+15551234567', '+15559876543'],
    'opt_out_link' => true
];

// Convert numbers array to proper format
$postFields = $campaignData;
$postFields['numbers'] = $campaignData['numbers'];

curl_setopt($ch, CURLOPT_URL, 'https://api.texttorrent.com/api/v1/campaigning/bulk');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postFields));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-SID: SID....................................',
    'X-API-PUBLIC-KEY: PK.....................................',
    'Accept: application/json'
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

if ($data['success']) {
    echo "Campaign created! ID: {$data['data']['campaign_id']}\n";
    echo "Credits consumed: {$data['data']['total_credit']}\n";
    echo "Total contacts: {$data['data']['total_contacts']}\n";
} else {
    echo "Error: {$data['message']}\n";
}
?>

Understanding Message Segments

Encoding Single Segment Multi-Segment Credit per Segment
GSM-7 (Standard) Up to 160 characters 153 characters each 1 credit
UCS-2 (Unicode) Up to 70 characters 67 characters each 1 credit
MMS N/A N/A Fixed MMS rate

Important Notes

  • Credit Calculation: Automatically calculates required credits based on segments and contacts
  • Auto-Recharge: Triggers when credits fall below 1000 (if enabled)
  • Trial Accounts: Cannot create campaigns during trial period
  • Blacklisted Contacts: Automatically excluded from campaigns
  • Scheduling: Set both date and time for scheduled campaigns
  • Batch Processing: Spreads campaign over time with batch_size and batch_frequency
  • Round-Robin: Distributes messages evenly across provided numbers
  • Number Pool: Uses multiple numbers to send messages
  • Opt-Out Link: Automatically appends opt-out link to message
  • Spin Text: Supports {option1|option2} syntax for message variations
  • MMS Files: Stored in DigitalOcean Spaces with permanent URLs
  • Background Processing: Campaigns processed via job queue
  • Activity Logging: Creates activity log entry for each campaign

6.3 Bulk Messaging Best Practices

Campaign Planning

  • Test First: Send test messages to yourself before launching campaign
  • Check Credits: Ensure sufficient credits before creating large campaigns
  • Segment Your Audience: Use contact lists to target specific groups
  • Schedule Wisely: Avoid sending at late night or early morning hours
  • Use Templates: Create reusable templates for common messages

Message Optimization

  • Keep It Short: Aim for single segment messages (160 chars for GSM-7)
  • Clear Call-to-Action: Include clear next steps or links
  • Personalization: Use {first_name}, {last_name} placeholders
  • Spin Text: Use variations like {Hi|Hello|Hey} for uniqueness
  • Avoid Spam Words: Minimize use of spam trigger words

Batch Processing Strategy

  • Large Lists: Use batch processing for 1000+ contacts
  • Recommended Batch Size: 50-200 messages per batch
  • Frequency: 15-30 minutes between batches recommended
  • Time Zones: Consider recipient time zones when scheduling

Compliance & Legal

  • Always Include Opt-Out: Enable opt_out_link for all campaigns
  • Honor Opt-Outs: Blacklisted contacts are automatically excluded
  • TCPA Compliance: Ensure you have consent to message contacts
  • Business Hours: Send during reasonable hours (9 AM - 8 PM)
  • Frequency Limits: Don't overwhelm recipients with too many messages

Number Pool & Round-Robin

  • Multiple Numbers: Use 3-5 numbers for large campaigns
  • Round-Robin: Evenly distributes messages across numbers
  • Avoid Spam Filters: Multiple numbers reduce spam detection
  • Number Reputation: Rotate numbers to maintain sender reputation

Complete Campaign Example

class CampaignManager {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1/campaigning';
    }

    async getCampaignData(search = {}) {
        const params = new URLSearchParams(search);
        const response = await fetch(
            `${this.baseUrl}/bulk?${params}`,
            {
                method: 'GET',
                headers: this.getHeaders()
            }
        );
        return await this.handleResponse(response);
    }

    async createCampaign(campaignData, file = null) {
        const formData = new FormData();

        // Add all campaign data
        Object.keys(campaignData).forEach(key => {
            if (Array.isArray(campaignData[key])) {
                campaignData[key].forEach(value => {
                    formData.append(`${key}[]`, value);
                });
            } else if (campaignData[key] !== null && campaignData[key] !== undefined) {
                formData.append(key, campaignData[key]);
            }
        });

        // Add file for MMS
        if (file) {
            formData.append('file', file);
        }

        const response = await fetch(`${this.baseUrl}/bulk`, {
            method: 'POST',
            headers: {
                'X-API-SID': this.apiSid,
                'X-API-PUBLIC-KEY': this.apiKey,
                'Accept': 'application/json'
            },
            body: formData
        });

        return await this.handleResponse(response);
    }

    getHeaders() {
        return {
            'X-API-SID': this.apiSid,
            'X-API-PUBLIC-KEY': this.apiKey,
            'Accept': 'application/json'
        };
    }

    async handleResponse(response) {
        const data = await response.json();
        if (!data.success) {
            throw new Error(data.message);
        }
        return data.data;
    }
}

// Usage
const manager = new CampaignManager('SID....................................', 'PK.....................................');

// Get campaign data
const data = await manager.getCampaignData({ search_number: 'customers' });
console.log('Available contact lists:', data.contactLists);

// Create immediate SMS campaign
const campaign = await manager.createCampaign({
    campaign_name: 'Flash Sale Alert',
    contact_list_id: 45,
    template_id: 12,
    sms_type: 'sms',
    sms_body: 'Hi {first_name}! Flash sale ends in 2 hours. Shop now!',
    numbers: ['+15551234567', '+15559876543'],
    opt_out_link: true,
    round_robin_campaign: true
});

console.log(`Campaign ${campaign.campaign_id} created!`);
console.log(`Credits consumed: ${campaign.total_credit}`);
console.log(`Messages queued: ${campaign.total_contacts}`);

// Create scheduled campaign with batches
const scheduled = await manager.createCampaign({
    campaign_name: 'Weekly Newsletter',
    contact_list_id: 45,
    template_id: 12,
    sms_type: 'sms',
    sms_body: 'This week\'s top stories: {spin_text}',
    numbers: ['+15551234567'],
    opt_out_link: true,
    batch_process: true,
    batch_size: 100,
    batch_frequency: 30,
    selected_date: '2025-10-20',
    selected_time: '09:00'
});

console.log('Scheduled campaign created:', scheduled);

Credit Estimation Formula

function estimateCampaignCredits(messageBody, totalContacts, smsType = 'sms') {
    if (smsType === 'mms') {
        // MMS has fixed cost + SMS cost if body included
        const mmsCredit = 3; // Example MMS cost
        const smsSegments = messageBody ? calculateSegments(messageBody) : 0;
        return (mmsCredit + smsSegments) * totalContacts;
    }

    const segments = calculateSegments(messageBody);
    return segments * totalContacts;
}

function calculateSegments(message) {
    const isGSM7 = /^[\x00-\x7F]*$/u.test(message);
    const length = message.length;

    if (isGSM7) {
        return length <= 160 ? 1 : Math.ceil(length / 153);
    } else {
        return length <= 70 ? 1 : Math.ceil(length / 67);
    }
}

// Usage
const message = 'Hi {first_name}, don\'t miss our Spring Sale!';
const contacts = 1250;
const credits = estimateCampaignCredits(message, contacts);
console.log(`Estimated credits: ${credits}`);

Common Mistakes to Avoid

  • ❌ Creating campaigns without checking credit balance
  • ❌ Not testing messages before sending to entire list
  • ❌ Forgetting to include opt-out link
  • ❌ Sending at inappropriate times (late night, early morning)
  • ❌ Using single number for very large campaigns
  • ❌ Not using batch processing for 1000+ contacts
  • ❌ Exceeding character limits causing unexpected segments
  • ✅ Always test with small group first
  • ✅ Monitor credit usage and set up auto-recharge
  • ✅ Use batch processing for large campaigns
  • ✅ Include opt-out links in all campaigns
  • ✅ Use multiple numbers with round-robin
  • ✅ Schedule campaigns for optimal times
  • ✅ Keep messages concise and under segment limits

Recommended Campaign Settings

Campaign Size Batch Processing Batch Size Frequency Number Pool
< 100 contacts Not needed N/A N/A 1-2 numbers
100 - 500 contacts Optional 50-100 15-30 min 2-3 numbers
500 - 2000 contacts Recommended 100-200 20-30 min 3-5 numbers
2000+ contacts Required 200-500 30-60 min 5+ numbers

7. Campaign Analytics

The Campaign Analytics API provides comprehensive insights into your bulk messaging campaigns. Track campaign performance, monitor delivery status, view detailed message logs, and analyze success rates. Perfect for optimizing your messaging strategy and troubleshooting delivery issues.

Key Features:

  • List all campaigns with pagination and filtering
  • Get campaign counts by status (scheduled, processing, completed, failed, paused)
  • View detailed campaign information
  • Access individual message delivery logs
  • Filter messages by status and search criteria
  • Track credits consumed per campaign
  • Monitor total participants and delivery rates
  • View sender and recipient information
ℹ️ About Campaign Status: Campaigns can have different statuses: Scheduled (waiting for scheduled time), Processing (currently sending), Completed (all messages sent), Paused (temporarily stopped), and Failed (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

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

Query Parameters

Parameter Type Required Description
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)
search string No Search campaigns by name
status string No Filter by status: Scheduled, Processing, Completed, Paused, Failed

Example Request - Get All Campaigns

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

Example Request - Filter by Status

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

Example Request - Search Campaigns

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Campaigns fetched successfully",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 567,
                "campaign_name": "Spring Sale Announcement",
                "status": "Completed",
                "contact_list_id": 45,
                "contact_list_name": "VIP Customers",
                "first_name": "John",
                "last_name": "Smith",
                "email": "john.smith@example.com",
                "phone": "+15551234567",
                "created_at": "2025-10-15T14:30:00.000000Z"
            },
            {
                "id": 568,
                "campaign_name": "Holiday Greetings",
                "status": "Scheduled",
                "contact_list_id": 42,
                "contact_list_name": "Newsletter Subscribers",
                "first_name": "John",
                "last_name": "Smith",
                "email": "john.smith@example.com",
                "phone": "+15551234567",
                "created_at": "2025-10-16T09:15:00.000000Z"
            },
            {
                "id": 569,
                "campaign_name": "Product Launch",
                "status": "Processing",
                "contact_list_id": 45,
                "contact_list_name": "VIP Customers",
                "first_name": "John",
                "last_name": "Smith",
                "email": "john.smith@example.com",
                "phone": "+15551234567",
                "created_at": "2025-10-17T10:00:00.000000Z"
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic?page=1",
        "from": 1,
        "last_page": 5,
        "last_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic?page=5",
        "next_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic?page=2",
        "path": "https://api.texttorrent.com/api/v1/campaigning/analytic",
        "per_page": 10,
        "prev_page_url": null,
        "to": 10,
        "total": 47
    },
    "errors": null
}

Campaign Status Values

Status Description
Scheduled Campaign is waiting for scheduled delivery time
Processing Campaign is currently sending messages
Completed All messages have been sent
Paused Campaign has been temporarily paused
Failed Campaign encountered errors during sending

Example in JavaScript

async function listCampaigns(options = {}) {
    const params = new URLSearchParams({
        page: options.page || 1,
        limit: options.limit || 10
    });

    if (options.search) params.append('search', options.search);
    if (options.status) params.append('status', options.status);

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/campaigning/analytic?${params}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Campaigns:', data.data.data);
        console.log(`Total campaigns: ${data.data.total}`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get all campaigns
const campaigns = await listCampaigns();

// Usage - Filter by status
const completed = await listCampaigns({ status: 'Completed' });

// Usage - Search campaigns
const searchResults = await listCampaigns({ search: 'Spring Sale' });

// Usage - Pagination
const page2 = await listCampaigns({ page: 2, limit: 20 });

Important Notes

  • Only returns campaigns where user_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

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

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Campaigns count fetched successfully",
    "data": {
        "total": 47,
        "scheduled": 5,
        "processing": 2,
        "completed": 38,
        "paused": 1,
        "failed": 1
    },
    "errors": null
}

Response Fields

Field Type Description
total integer Total number of campaigns
scheduled integer Campaigns waiting for scheduled time
processing integer Campaigns currently sending messages
completed integer Campaigns that finished sending
paused integer Campaigns that are paused
failed integer Campaigns that encountered errors

Example in JavaScript

async function getCampaignCounts() {
    const response = await fetch(
        'https://api.texttorrent.com/api/v1/campaigning/analytic/count',
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Campaign Statistics:');
        console.log(`Total: ${data.data.total}`);
        console.log(`Scheduled: ${data.data.scheduled}`);
        console.log(`Processing: ${data.data.processing}`);
        console.log(`Completed: ${data.data.completed}`);
        console.log(`Paused: ${data.data.paused}`);
        console.log(`Failed: ${data.data.failed}`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
const stats = await getCampaignCounts();

// Display as percentages
const successRate = ((stats.completed / stats.total) * 100).toFixed(2);
console.log(`Success rate: ${successRate}%`);

Important Notes

  • Counts are based on campaigns owned by your account
  • Perfect for dashboard widgets and overview displays
  • Lightweight endpoint with minimal data transfer
  • Use to quickly assess campaign health

7.3 Get Campaign Details

Retrieve detailed information about a specific campaign including credits consumed, total participants, and current status. Use this to view campaign overview before diving into individual message logs.

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

Request Headers

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

Path Parameters

Parameter Type Required Description
id integer Yes Campaign ID

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Campaigns fetched successfully",
    "data": {
        "id": 567,
        "campaign_name": "Spring Sale Announcement",
        "contact_list_id": 45,
        "credits_consumed": 1875,
        "total_participants": 1250,
        "status": "Completed",
        "created_at": "2025-10-15T14:30:00.000000Z"
    },
    "errors": null
}

Response Fields

Field Type Description
credits_consumed integer Total credits used for this campaign
total_participants integer Number of contacts in the campaign
contact_list_id integer ID of the contact list used

Example in JavaScript

async function getCampaignDetails(campaignId) {
    const response = await fetch(
        `https://api.texttorrent.com/api/v1/campaigning/analytic/details/${campaignId}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Campaign Details:', data.data);
        console.log(`Credits: ${data.data.credits_consumed}`);
        console.log(`Participants: ${data.data.total_participants}`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
const campaign = await getCampaignDetails(567);

Important Notes

  • Returns high-level campaign information
  • Use for campaign summary displays
  • Credits consumed shows total cost of campaign
  • Total participants shows number of recipients

7.4 Get Campaign Message List

Retrieve a detailed list of all individual messages in a campaign. This endpoint provides granular tracking of each message sent, including sender/receiver information, delivery status, and execution time. Essential for troubleshooting and detailed analytics.

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

Request Headers

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

Path Parameters

Parameter Type Required Description
id integer Yes Campaign ID

Query Parameters

Parameter Type Required Description
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)
search string No Search by sender or receiver number
status string No Filter by status: Completed, Processing, Paused, Scheduled, Failed

Example Request - Get All Messages

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

Example Request - Filter by Status

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

Example Request - Search by Number

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Campaigns fetched successfully",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 12345,
                "bulk_message_id": 567,
                "send_from": "+15551234567",
                "send_from_name": "John Smith - Main Business Line",
                "send_to": "+15559876543",
                "send_to_name": "Jane Doe",
                "status": 1,
                "send_status": "delivered",
                "execute_at": "2025-10-15T14:30:00.000000Z",
                "created_at": "2025-10-15T14:29:45.000000Z",
                "updated_at": "2025-10-15T14:30:15.000000Z"
            },
            {
                "id": 12346,
                "bulk_message_id": 567,
                "send_from": "+15551234567",
                "send_from_name": "John Smith - Main Business Line",
                "send_to": "+15551112222",
                "send_to_name": "Bob Wilson",
                "status": 1,
                "send_status": "sent",
                "execute_at": "2025-10-15T14:30:05.000000Z",
                "created_at": "2025-10-15T14:29:45.000000Z",
                "updated_at": "2025-10-15T14:30:20.000000Z"
            },
            {
                "id": 12347,
                "bulk_message_id": 567,
                "send_from": "+15559876543",
                "send_from_name": "John Smith - Marketing Line",
                "send_to": "+15553334444",
                "send_to_name": "Alice Johnson",
                "status": 1,
                "send_status": "failed",
                "execute_at": "2025-10-15T14:30:10.000000Z",
                "created_at": "2025-10-15T14:29:45.000000Z",
                "updated_at": "2025-10-15T14:30:25.000000Z"
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?page=1",
        "from": 1,
        "last_page": 125,
        "last_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?page=125",
        "next_page_url": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list?page=2",
        "path": "https://api.texttorrent.com/api/v1/campaigning/analytic/details/567/list",
        "per_page": 10,
        "prev_page_url": null,
        "to": 10,
        "total": 1250
    },
    "errors": null
}

Message Status Values

Status Code Send Status Filter Status Description
0 N/A Scheduled Message waiting for scheduled time (execute_at > now)
1 pending Processing Message is being processed/sent
1 delivered Completed Message successfully delivered to recipient
1 sent Completed Message sent (delivery confirmation pending)
1 failed Failed Message failed to send
1 undelivered Failed Message sent but not delivered
5 N/A Paused Message paused by user

Response Fields

Field Type Description
send_from string Phone number message was sent from
send_from_name string User and friendly name of sender number
send_to string Recipient phone number
send_to_name string Recipient name from contacts
status integer Processing status code (0, 1, 5)
send_status string Delivery status (pending, sent, delivered, failed, undelivered)
execute_at datetime When message was/will be sent

Example in JavaScript

async function getCampaignMessages(campaignId, options = {}) {
    const params = new URLSearchParams({
        page: options.page || 1,
        limit: options.limit || 10
    });

    if (options.search) params.append('search', options.search);
    if (options.status) params.append('status', options.status);

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/campaigning/analytic/details/${campaignId}/list?${params}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Messages:', data.data.data);
        console.log(`Total messages: ${data.data.total}`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get all messages
const messages = await getCampaignMessages(567);

// Usage - Get failed messages
const failed = await getCampaignMessages(567, { status: 'Failed' });

// Usage - Search by number
const search = await getCampaignMessages(567, { search: '+1555' });

// Usage - Pagination
const page2 = await getCampaignMessages(567, { page: 2, limit: 50 });

Important Notes

  • Results are ordered by ID in descending order
  • Search performs partial matching on both send_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

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

Path Parameters

Parameter Type Required Description
id integer Yes Campaign ID

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Campaigns count fetched successfully",
    "data": {
        "total": 1250,
        "scheduled": 0,
        "processing": 5,
        "completed": 1230,
        "paused": 0,
        "failed": 15
    },
    "errors": null
}

Response Fields

Field Type Description
total integer Total number of messages in campaign
scheduled integer Messages waiting for scheduled time (status=0, execute_at > now)
processing integer Messages being processed (status=1, send_status=pending)
completed integer Messages delivered or sent (send_status=delivered or sent)
paused integer Messages paused by user (status=5)
failed integer Messages that failed (send_status=failed or undelivered)

Example in JavaScript

async function getCampaignMessageCounts(campaignId) {
    const response = await fetch(
        `https://api.texttorrent.com/api/v1/campaigning/analytic/details/${campaignId}/count`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Message Statistics:');
        console.log(`Total: ${data.data.total}`);
        console.log(`Completed: ${data.data.completed}`);
        console.log(`Failed: ${data.data.failed}`);

        // Calculate delivery rate
        const deliveryRate = ((data.data.completed / data.data.total) * 100).toFixed(2);
        console.log(`Delivery rate: ${deliveryRate}%`);

        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage
const stats = await getCampaignMessageCounts(567);

// Display metrics
function displayCampaignMetrics(stats) {
    const deliveryRate = ((stats.completed / stats.total) * 100).toFixed(2);
    const failureRate = ((stats.failed / stats.total) * 100).toFixed(2);

    console.log('Campaign Performance:');
    console.log(`  Total Messages: ${stats.total}`);
    console.log(`  Delivery Rate: ${deliveryRate}%`);
    console.log(`  Failure Rate: ${failureRate}%`);
    console.log(`  Still Processing: ${stats.processing}`);
}

displayCampaignMetrics(stats);

Important Notes

  • Counts are at the message level, not campaign level
  • Use to calculate delivery rates and failure rates
  • Perfect for campaign performance dashboards
  • Lightweight endpoint for quick statistics

7.6 Campaign Analytics Best Practices

Monitoring Strategy

  • Regular Checks: Monitor campaign status regularly during sending
  • Track Failures: Investigate failed messages to identify patterns
  • Delivery Rates: Aim for 95%+ delivery rate for optimal performance
  • Processing Time: Large campaigns may take time based on batch settings

Performance Analysis

  • Compare Campaigns: Track which campaigns perform best
  • Time Analysis: Note when messages have highest delivery rates
  • Number Performance: Track which sending numbers have best rates
  • Contact Quality: High failure rates may indicate stale contacts

Troubleshooting Failed Messages

  • Invalid Numbers: Check for formatting issues
  • Carrier Blocks: Some carriers may block bulk messages
  • Credit Issues: Verify sufficient credits during campaign
  • Rate Limits: Respect carrier rate limits with batch processing

Complete Analytics Dashboard Example

class CampaignAnalytics {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1/campaigning/analytic';
    }

    async getCampaignOverview() {
        const [campaigns, counts] = await Promise.all([
            this.listCampaigns({ limit: 5 }),
            this.getCampaignCounts()
        ]);

        return {
            recentCampaigns: campaigns.data,
            statistics: counts
        };
    }

    async getCampaignPerformance(campaignId) {
        const [details, messageCounts, messages] = await Promise.all([
            this.getCampaignDetails(campaignId),
            this.getMessageCounts(campaignId),
            this.getMessages(campaignId, { limit: 100 })
        ]);

        const deliveryRate = (messageCounts.completed / messageCounts.total * 100).toFixed(2);
        const failureRate = (messageCounts.failed / messageCounts.total * 100).toFixed(2);

        return {
            campaign: details,
            metrics: {
                total: messageCounts.total,
                completed: messageCounts.completed,
                failed: messageCounts.failed,
                deliveryRate: `${deliveryRate}%`,
                failureRate: `${failureRate}%`,
                creditsPerMessage: (details.credits_consumed / messageCounts.total).toFixed(2)
            },
            recentMessages: messages.data
        };
    }

    async listCampaigns(options = {}) {
        return await this.apiCall('GET', '', options);
    }

    async getCampaignCounts() {
        return await this.apiCall('GET', '/count');
    }

    async getCampaignDetails(id) {
        return await this.apiCall('GET', `/details/${id}`);
    }

    async getMessages(id, options = {}) {
        return await this.apiCall('GET', `/details/${id}/list`, options);
    }

    async getMessageCounts(id) {
        return await this.apiCall('GET', `/details/${id}/count`);
    }

    async apiCall(method, endpoint, params = {}) {
        const queryString = new URLSearchParams(params).toString();
        const url = `${this.baseUrl}${endpoint}${queryString ? `?${queryString}` : ''}`;

        const response = await fetch(url, {
            method,
            headers: {
                'X-API-SID': this.apiSid,
                'X-API-PUBLIC-KEY': this.apiKey,
                'Accept': 'application/json'
            }
        });

        const data = await response.json();
        if (!data.success) throw new Error(data.message);
        return data.data;
    }
}

// Usage - Dashboard Overview
const analytics = new CampaignAnalytics('SID....................................', 'PK.....................................');

const overview = await analytics.getCampaignOverview();
console.log('Recent Campaigns:', overview.recentCampaigns);
console.log('Campaign Stats:', overview.statistics);

// Usage - Detailed Campaign Analysis
const performance = await analytics.getCampaignPerformance(567);
console.log('Campaign:', performance.campaign.campaign_name);
console.log('Delivery Rate:', performance.metrics.deliveryRate);
console.log('Failure Rate:', performance.metrics.failureRate);
console.log('Cost per Message:', performance.metrics.creditsPerMessage);

// Usage - Find Problem Campaigns
const campaigns = await analytics.listCampaigns({ status: 'Failed' });
for (const campaign of campaigns.data) {
    const counts = await analytics.getMessageCounts(campaign.id);
    const failureRate = (counts.failed / counts.total * 100).toFixed(2);
    console.log(`${campaign.campaign_name}: ${failureRate}% failure rate`);
}

Key Performance Indicators (KPIs)

KPI Calculation Good Target
Delivery Rate (Completed / Total) × 100 > 95%
Failure Rate (Failed / Total) × 100 < 5%
Cost per Message Credits Consumed / Total Messages 1-3 credits
Campaign Completion Time Last Message - First Message Depends on batch settings

Common Issues & Solutions

  • High Failure Rate:
    • Clean contact lists regularly
    • Validate numbers before campaigns
    • Remove invalid/disconnected numbers
  • Stuck in Processing:
    • Check batch settings aren't too restrictive
    • Verify sufficient credits remain
    • Contact support if persists > 1 hour
  • Scheduled Not Starting:
    • Verify scheduled time is in future
    • Check server timezone settings
    • Ensure job queue is running

Reporting Best Practices

  • ✅ Export failed messages for contact list cleanup
  • ✅ Track delivery rates over time for trends
  • ✅ Compare performance across different contact lists
  • ✅ Monitor processing times for batch optimization
  • ✅ Keep historical data for compliance
  • ✅ Document any unusual failure patterns
  • ❌ Don't ignore failed messages
  • ❌ Don't skip regular performance reviews
  • ❌ Don't send to lists with high failure rates

8. Message Templates

The Message Templates API allows you to create, manage, and organize reusable message templates for your campaigns and conversations. Templates save time by storing frequently used messages and ensure consistency across your communications. Perfect for standard responses, campaign messages, and automated workflows.

Key Features:

  • Create and manage reusable message templates
  • Search and filter templates by name
  • Sort templates by various fields
  • Assign templates to specific users/sub-accounts
  • Enable/disable templates with status control
  • Preview message content
  • Export templates to Excel for backup
  • Paginated template listings
ℹ️ About Templates: Templates can contain placeholder variables like {first_name}, {last_name}, and spin text variations {option1|option2} 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

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

Query Parameters

Parameter Type Required Description
limit integer No Number of results per page (default: 10)
page integer No Page number for pagination (default: 1)
search string No Search templates by name
sort_by string No Field to sort by (default: created_at)
sort_direction string No Sort direction: ASC or DESC (default: DESC)

Example Request - Get All Templates

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

Example Request - Search and Sort

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Templates retrieved successfully",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 12,
                "template_name": "Welcome Message",
                "status": 1,
                "assigned_users": [101, 102, 103],
                "preview_message": "Hi {first_name}, welcome to our service! We're excited to have you.",
                "created_at": "2025-09-20T10:30:00.000000Z",
                "user_name": "John Smith",
                "assignees": [
                    {
                        "id": 101,
                        "name": "Sarah Johnson",
                        "email": "sarah.johnson@example.com"
                    },
                    {
                        "id": 102,
                        "name": "Mike Davis",
                        "email": "mike.davis@example.com"
                    },
                    {
                        "id": 103,
                        "name": "Emily Brown",
                        "email": "emily.brown@example.com"
                    }
                ]
            },
            {
                "id": 15,
                "template_name": "Flash Sale Alert",
                "status": 1,
                "assigned_users": [],
                "preview_message": "{Hi|Hello|Hey} {first_name}! Flash sale ends in 2 hours. Shop now!",
                "created_at": "2025-10-10T14:15:00.000000Z",
                "user_name": "John Smith",
                "assignees": []
            },
            {
                "id": 18,
                "template_name": "Order Confirmation",
                "status": 0,
                "assigned_users": [101],
                "preview_message": "Thank you for your order! Your order number is {order_id}. We'll notify you when it ships.",
                "created_at": "2025-10-15T09:45:00.000000Z",
                "user_name": "John Smith",
                "assignees": [
                    {
                        "id": 101,
                        "name": "Sarah Johnson",
                        "email": "sarah.johnson@example.com"
                    }
                ]
            }
        ],
        "first_page_url": "https://api.texttorrent.com/api/v1/campaigning/template?page=1",
        "from": 1,
        "last_page": 3,
        "last_page_url": "https://api.texttorrent.com/api/v1/campaigning/template?page=3",
        "next_page_url": "https://api.texttorrent.com/api/v1/campaigning/template?page=2",
        "path": "https://api.texttorrent.com/api/v1/campaigning/template",
        "per_page": 10,
        "prev_page_url": null,
        "to": 10,
        "total": 28
    },
    "errors": null
}

Response Fields

Field Type Description
status integer Template status: 1 = active, 0 = inactive
assigned_users array Array of user IDs who can access this template
preview_message string Template content with variables and spin text
user_name string Name of user who created the template
assignees array Full details of assigned users (id, name, email)

Example in JavaScript

async function listTemplates(options = {}) {
    const params = new URLSearchParams({
        page: options.page || 1,
        limit: options.limit || 10
    });

    if (options.search) params.append('search', options.search);
    if (options.sort_by) params.append('sort_by', options.sort_by);
    if (options.sort_direction) params.append('sort_direction', options.sort_direction);

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/campaigning/template?${params}`,
        {
            method: 'GET',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Templates:', data.data.data);
        console.log(`Total templates: ${data.data.total}`);
        return data.data;
    } else {
        console.error('Error:', data.message);
        throw new Error(data.message);
    }
}

// Usage - Get all templates
const templates = await listTemplates();

// Usage - Search templates
const searchResults = await listTemplates({ search: 'welcome' });

// Usage - Sort by name
const sorted = await listTemplates({
    sort_by: 'template_name',
    sort_direction: 'ASC'
});

Important Notes

  • Only returns templates where user_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

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

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Request Body Parameters

Parameter Type Required Description
template_name string Yes Template name (max 255 characters)
preview_message string Yes Template message content
status boolean Yes Template status: true = active, false = inactive
assignees array No Array of user IDs to assign template to

Example Request - Simple Template

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

Example Request - With Spin Text and Assignees

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

Success Response (201 Created)

{
    "code": 201,
    "success": true,
    "message": "Template created successfully",
    "data": {
        "id": 25,
        "user_id": 1,
        "template_name": "Welcome Message",
        "preview_message": "Hi {first_name}, welcome to our service! We're excited to have you.",
        "status": 1,
        "assigned_users": [],
        "created_at": "2025-10-17T16:45:00.000000Z",
        "updated_at": "2025-10-17T16:45:00.000000Z"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "template_name": ["The template name field is required."],
        "preview_message": ["The preview message field is required."],
        "status": ["The status field is required."],
        "assignees.0": ["The selected assignees.0 is invalid."]
    }
}

Example in JavaScript

async function createTemplate(templateData) {
    const response = await fetch('https://api.texttorrent.com/api/v1/campaigning/template', {
        method: 'POST',
        headers: {
            'X-API-SID': 'SID....................................',
            'X-API-PUBLIC-KEY': 'PK.....................................',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(templateData)
    });

    const data = await response.json();

    if (data.success) {
        console.log('Template created:', data.data);
        alert(`Template "${data.data.template_name}" created successfully!`);
        return data.data;
    } else {
        console.error('Error:', data.message, data.errors);
        throw new Error(data.message);
    }
}

// Usage - Simple template
await createTemplate({
    template_name: 'Welcome Message',
    preview_message: 'Hi {first_name}, welcome to our service!',
    status: true
});

// Usage - With spin text and assignees
await createTemplate({
    template_name: 'Flash Sale Alert',
    preview_message: '{Hi|Hello|Hey} {first_name}! Flash sale ends in 2 hours!',
    status: true,
    assignees: [101, 102, 103]
});

Supported Variables

Variable Description Example Output
{first_name} Contact's first name John
{last_name} Contact's last name Smith
{option1|option2} Spin text (random selection) option1 or option2

Important Notes

  • Template is automatically assigned to creator's user ID
  • Empty assignees array means template is available to all users
  • Assignees must be valid user IDs in the system
  • Status controls template availability (active/inactive)
  • Spin text format: {option1|option2|option3}
  • Variables are replaced during message sending

8.3 Update Message Template

Update an existing message template's name, content, status, or user assignments. The creation timestamp is preserved during updates.

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

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Path Parameters

Parameter Type Required Description
id integer Yes Template ID to update

Request Body Parameters

Parameter Type Required Description
template_name string Yes Template name (max 255 characters)
preview_message string Yes Template message content
status boolean Yes Template status: true = active, false = inactive
assignees array No Array of user IDs to assign template to

Example Request

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

Success Response (200 OK)

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

Example in JavaScript

async function updateTemplate(templateId, templateData) {
    const response = await fetch(
        `https://api.texttorrent.com/api/v1/campaigning/template/${templateId}`,
        {
            method: 'PUT',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(templateData)
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Template updated:', data.data);
        alert('Template updated successfully!');
        return data.data;
    } else {
        console.error('Error:', data.message, data.errors);
        throw new Error(data.message);
    }
}

// Usage
await updateTemplate(12, {
    template_name: 'Updated Welcome Message',
    preview_message: 'Hi {first_name}, thank you for joining us!',
    status: true,
    assignees: [101, 102]
});

Important Notes

  • All fields are required even if not changing
  • Creation timestamp is preserved during update
  • Updated timestamp reflects the update time
  • Use empty array for assignees to make available to all

8.4 Delete Message Template

Permanently delete a message template. This action cannot be undone. If the template has an associated avatar file, it will also be deleted.

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

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

Path Parameters

Parameter Type Required Description
id integer Yes Template ID to delete

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Template deleted successfully",
    "data": [],
    "errors": null
}

Example in JavaScript

async function deleteTemplate(templateId) {
    if (!confirm('Delete this template? This action cannot be undone.')) {
        return;
    }

    const response = await fetch(
        `https://api.texttorrent.com/api/v1/campaigning/template/${templateId}`,
        {
            method: 'DELETE',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json'
            }
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Template deleted successfully');
        alert('Template has been deleted');
        return true;
    } else {
        console.error('Error:', data.message);
        alert('Failed to delete template');
        return false;
    }
}

// Usage
await deleteTemplate(12);

Important Notes

  • Deletion is permanent and cannot be undone
  • Associated avatar file is automatically deleted
  • Consider disabling instead of deleting if you might need it later

8.5 Export Message Templates

Export selected message templates to an Excel file. The file is stored in DigitalOcean Spaces and a download URL is returned. Perfect for backing up templates or sharing with team members.

POST /api/v1/campaigning/template/export

Request Headers

X-API-SID: SID....................................
X-API-PUBLIC-KEY: PK.....................................
Accept: application/json
Content-Type: application/json

Request Body Parameters

Parameter Type Required Description
template_ids array Yes Array of template IDs to export

Example Request

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

Success Response (200 OK)

{
    "code": 200,
    "success": true,
    "message": "Templates exported successfully",
    "data": {
        "path": "inbox_template/inbox_template_1729180800.xlsx",
        "url": "https://texttorrent.nyc3.digitaloceanspaces.com/inbox_template/inbox_template_1729180800.xlsx"
    },
    "errors": null
}

Validation Errors (422 Unprocessable Entity)

{
    "code": 422,
    "success": false,
    "message": "Validation Error",
    "data": null,
    "errors": {
        "template_ids": ["The template ids field is required."],
        "template_ids.0": ["The selected template ids.0 is invalid."]
    }
}

Example in JavaScript

async function exportTemplates(templateIds) {
    const response = await fetch(
        'https://api.texttorrent.com/api/v1/campaigning/template/export',
        {
            method: 'POST',
            headers: {
                'X-API-SID': 'SID....................................',
                'X-API-PUBLIC-KEY': 'PK.....................................',
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ template_ids: templateIds })
        }
    );

    const data = await response.json();

    if (data.success) {
        console.log('Export successful:', data.data);

        // Download the file
        window.open(data.data.url, '_blank');

        return data.data;
    } else {
        console.error('Error:', data.message, data.errors);
        throw new Error(data.message);
    }
}

// Usage - Export selected templates
await exportTemplates([12, 15, 18, 25]);

// Usage - Export all templates from list
const templates = await listTemplates();
const allIds = templates.data.map(t => t.id);
await exportTemplates(allIds);

Example in PHP

<?php
$templateIds = [12, 15, 18, 25];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.texttorrent.com/api/v1/campaigning/template/export');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['template_ids' => $templateIds]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-SID: SID....................................',
    'X-API-PUBLIC-KEY: PK.....................................',
    'Accept: application/json',
    'Content-Type: application/json'
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

if ($data['success']) {
    $downloadUrl = $data['data']['url'];
    echo "Download URL: {$downloadUrl}\n";

    // Optionally download the file
    file_put_contents('templates.xlsx', file_get_contents($downloadUrl));
} else {
    echo "Error: {$data['message']}\n";
}
?>

Important Notes

  • All template IDs must exist and belong to your account
  • File is stored in DigitalOcean Spaces cloud storage
  • URL is publicly accessible for download
  • File name includes timestamp for uniqueness
  • Excel format (.xlsx) is used for exports

8.6 Message Template Best Practices

Template Organization

  • Descriptive Names: Use clear, descriptive names like "Welcome New Customer" instead of "Template 1"
  • Categorize: Use naming conventions to group related templates (e.g., "Sales - ", "Support - ")
  • Status Management: Disable outdated templates instead of deleting them
  • Regular Review: Audit templates quarterly to remove unused ones

Content Best Practices

  • Personalization: Always use {first_name} when possible for better engagement
  • Spin Text: Use variations {Hi|Hello|Hey} to make messages feel less automated
  • Keep it Concise: Shorter messages have higher read rates
  • Clear CTA: Include a clear call-to-action in every template
  • Test Variables: Verify all variables work before using in campaigns

User Assignment Strategy

  • Role-Based: Assign templates based on team roles (sales, support, etc.)
  • Global Templates: Leave assignees empty for company-wide templates
  • Personal Templates: Create user-specific templates for individual workflows
  • Training: Document which templates to use for different scenarios

Complete Template Manager Example

class TemplateManager {
    constructor(apiSid, apiKey) {
        this.apiSid = apiSid;
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.texttorrent.com/api/v1/campaigning/template';
    }

    async list(options = {}) {
        return await this.apiCall('GET', '', options);
    }

    async create(templateData) {
        return await this.apiCall('POST', '', {}, templateData);
    }

    async update(id, templateData) {
        return await this.apiCall('PUT', `/${id}`, {}, templateData);
    }

    async delete(id) {
        return await this.apiCall('DELETE', `/${id}`);
    }

    async export(templateIds) {
        return await this.apiCall('POST', '/export', {}, { template_ids: templateIds });
    }

    async search(query) {
        return await this.list({ search: query });
    }

    async getActiveTemplates() {
        const templates = await this.list({ limit: 100 });
        return templates.data.filter(t => t.status === 1);
    }

    async apiCall(method, endpoint, params = {}, body = null) {
        const queryString = new URLSearchParams(params).toString();
        const url = `${this.baseUrl}${endpoint}${queryString ? `?${queryString}` : ''}`;

        const options = {
            method,
            headers: {
                'X-API-SID': this.apiSid,
                'X-API-PUBLIC-KEY': this.apiKey,
                'Accept': 'application/json'
            }
        };

        if (body && (method === 'POST' || method === 'PUT')) {
            options.headers['Content-Type'] = 'application/json';
            options.body = JSON.stringify(body);
        }

        const response = await fetch(url, options);
        const data = await response.json();

        if (!data.success) throw new Error(data.message);
        return data.data;
    }
}

// Usage
const manager = new TemplateManager('SID....................................', 'PK.....................................');

// List all templates
const templates = await manager.list();

// Search templates
const searchResults = await manager.search('welcome');

// Create new template
const newTemplate = await manager.create({
    template_name: 'Order Confirmation',
    preview_message: 'Hi {first_name}, your order #{order_id} is confirmed!',
    status: true,
    assignees: [101]
});

// Update template
await manager.update(12, {
    template_name: 'Updated Name',
    preview_message: 'Updated message',
    status: true,
    assignees: []
});

// Export templates
const exportData = await manager.export([12, 15, 18]);
console.log('Download:', exportData.url);

// Get only active templates
const active = await manager.getActiveTemplates();
console.log(`${active.length} active templates`);

Template Variables Reference

Variable Type Format Example Output
First Name {first_name} Hi {first_name}! Hi John!
Last Name {last_name} Mr. {last_name} Mr. Smith
Spin Text (2 options) {opt1|opt2} {Hi|Hello} there! Hi there! or Hello there!
Spin Text (3+ options) {a|b|c} {Great|Awesome|Excellent}! Great! or Awesome! or Excellent!

Common Template Use Cases

Template Type Example Message Assignees
Welcome Message Hi {first_name}, welcome to our service! We're excited to have you. All users
Order Confirmation Thank you {first_name}! Your order #{order_id} is confirmed. Sales team
Appointment Reminder {Hi|Hello} {first_name}, reminder: appointment tomorrow at {time}. Support team
Flash Sale {Hey|Hi} {first_name}! {Limited|Flash} sale ends in 2 hours. Shop now! Marketing team

Common Mistakes to Avoid

  • ❌ Using generic templates without personalization
  • ❌ Creating too many similar templates
  • ❌ Not testing variables before campaign use
  • ❌ Forgetting to update outdated templates
  • ❌ Deleting templates instead of disabling them
  • ❌ Not assigning templates to appropriate teams
  • ✅ Always use {first_name} for personalization
  • ✅ Use spin text for message variations
  • ✅ Test templates with sample data first
  • ✅ Keep template library organized
  • ✅ Disable instead of delete for historical records
  • ✅ Assign templates based on roles