curl -X POST "https://api.tracklysms.com/api/v2/contacts/bulk" \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{
"phone_number": "+14155559876",
"list_number": "+18005551234",
"use_journeys": true,
"custom_fields": {"first_name": "Jane"}
},
{
"phone_number": "+12125551000",
"list_number": "+18005551234",
"custom_fields": {"first_name": "John"}
},
{
"phone_number": "+13105558888",
"list_number": "+18005551234",
"signup_date": "2025-03-10T08:00:00Z",
"ip_address": "198.51.100.17"
}
]
}'
import requests
contacts = [
{
"phone_number": "+14155559876",
"list_number": "+18005551234",
"use_journeys": True,
"custom_fields": {"first_name": "Jane"}
},
{
"phone_number": "+12125551000",
"list_number": "+18005551234",
"custom_fields": {"first_name": "John"}
},
{
"phone_number": "+13105558888",
"list_number": "+18005551234",
"signup_date": "2025-03-10T08:00:00Z",
"ip_address": "198.51.100.17"
}
]
response = requests.post(
"https://api.tracklysms.com/api/v2/contacts/bulk",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json"
},
json={"contacts": contacts}
)
data = response.json()
print(f"Created: {data['success_count']}, Errors: {data['error_count']}")
for error in data.get("errors", []):
print(f" Index {error['index']}: {error['code']} - {error['error']}")
const contacts = [
{
phone_number: "+14155559876",
list_number: "+18005551234",
use_journeys: true,
custom_fields: { first_name: "Jane" },
},
{
phone_number: "+12125551000",
list_number: "+18005551234",
custom_fields: { first_name: "John" },
},
{
phone_number: "+13105558888",
list_number: "+18005551234",
signup_date: "2025-03-10T08:00:00Z",
ip_address: "198.51.100.17",
},
];
const response = await fetch(
"https://api.tracklysms.com/api/v2/contacts/bulk",
{
method: "POST",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({ contacts }),
}
);
const data = await response.json();
console.log(`Created: ${data.success_count}, Errors: ${data.error_count}`);
{
"success_count": 2,
"error_count": 1,
"errors": [
{
"index": 2,
"phone_number": "3105558888",
"code": "invalid_phone",
"error": "Invalid E.164 phone number format"
}
],
"contacts_created": 2,
"list_contacts_created": 2,
"journeys_enrolled": 1
}
{
"success_count": 3,
"error_count": 0,
"errors": [],
"contacts_created": 3,
"list_contacts_created": 3,
"journeys_enrolled": 0
}
{
"error": "contacts array is required",
"code": "missing_contacts"
}
{
"error": "Maximum 1000 contacts per request",
"code": "too_many_contacts"
}
{
"error": "Maximum 500 contacts with use_validation=true per request",
"code": "too_many_contacts_with_validation"
}
{
"error": "Maximum 100 contacts with double_optin=true per request",
"code": "too_many_contacts_with_double_optin"
}
{
"error": "An active payment method is required when use_validation=true",
"code": "no_payment_method"
}
Contacts (v2)
Bulk Create Contacts
Add up to 1,000 contacts to sending lists in a single request, with per-contact error reporting.
POST
/
v2
/
contacts
/
bulk
curl -X POST "https://api.tracklysms.com/api/v2/contacts/bulk" \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{
"phone_number": "+14155559876",
"list_number": "+18005551234",
"use_journeys": true,
"custom_fields": {"first_name": "Jane"}
},
{
"phone_number": "+12125551000",
"list_number": "+18005551234",
"custom_fields": {"first_name": "John"}
},
{
"phone_number": "+13105558888",
"list_number": "+18005551234",
"signup_date": "2025-03-10T08:00:00Z",
"ip_address": "198.51.100.17"
}
]
}'
import requests
contacts = [
{
"phone_number": "+14155559876",
"list_number": "+18005551234",
"use_journeys": True,
"custom_fields": {"first_name": "Jane"}
},
{
"phone_number": "+12125551000",
"list_number": "+18005551234",
"custom_fields": {"first_name": "John"}
},
{
"phone_number": "+13105558888",
"list_number": "+18005551234",
"signup_date": "2025-03-10T08:00:00Z",
"ip_address": "198.51.100.17"
}
]
response = requests.post(
"https://api.tracklysms.com/api/v2/contacts/bulk",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json"
},
json={"contacts": contacts}
)
data = response.json()
print(f"Created: {data['success_count']}, Errors: {data['error_count']}")
for error in data.get("errors", []):
print(f" Index {error['index']}: {error['code']} - {error['error']}")
const contacts = [
{
phone_number: "+14155559876",
list_number: "+18005551234",
use_journeys: true,
custom_fields: { first_name: "Jane" },
},
{
phone_number: "+12125551000",
list_number: "+18005551234",
custom_fields: { first_name: "John" },
},
{
phone_number: "+13105558888",
list_number: "+18005551234",
signup_date: "2025-03-10T08:00:00Z",
ip_address: "198.51.100.17",
},
];
const response = await fetch(
"https://api.tracklysms.com/api/v2/contacts/bulk",
{
method: "POST",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({ contacts }),
}
);
const data = await response.json();
console.log(`Created: ${data.success_count}, Errors: ${data.error_count}`);
{
"success_count": 2,
"error_count": 1,
"errors": [
{
"index": 2,
"phone_number": "3105558888",
"code": "invalid_phone",
"error": "Invalid E.164 phone number format"
}
],
"contacts_created": 2,
"list_contacts_created": 2,
"journeys_enrolled": 1
}
{
"success_count": 3,
"error_count": 0,
"errors": [],
"contacts_created": 3,
"list_contacts_created": 3,
"journeys_enrolled": 0
}
{
"error": "contacts array is required",
"code": "missing_contacts"
}
{
"error": "Maximum 1000 contacts per request",
"code": "too_many_contacts"
}
{
"error": "Maximum 500 contacts with use_validation=true per request",
"code": "too_many_contacts_with_validation"
}
{
"error": "Maximum 100 contacts with double_optin=true per request",
"code": "too_many_contacts_with_double_optin"
}
{
"error": "An active payment method is required when use_validation=true",
"code": "no_payment_method"
}
Creates or updates multiple contacts in a single batch operation. Each contact in the array follows the same schema as the single Create Contact endpoint. The operation processes all contacts and returns a summary with individual error details for any that failed.
Contacts that fail validation do not prevent other contacts in the batch from being processed. Check the
errors array in the response for per-contact failure details.
Body Parameters
array
required
Array of contact objects to create. Maximum of 1,000 contacts per request.
Show Contact object properties
Show Contact object properties
string
required
Contact phone number in E.164 format (e.g.,
+14155559876).string
required
Sending list phone number in E.164 format (e.g.,
+18005551234). Must be a list owned by your account.boolean
default:"false"
When
true, enroll the contact in matching Welcome Journeys.boolean
default:"true"
Skip journey enrollment if the ListContact already exists.
string
Signup date in ISO 8601 UTC format. Defaults to now.
string
IP address for consent tracking.
string
Signup URL for consent tracking.
string
How consent was obtained. One of
web_form, sms_keyword, mobile_app, verbal, written, api_with_proof, double_opt_in. Optional — stored per contact for TCPA compliance when a valid method is provided, otherwise null. Invalid or verbal values are ignored (stored as null), not rejected.string
URL or description of where consent was captured. Defaults to the contact’s
url when omitted.string
Exact disclosure text shown to the contact at consent.
string
Browser/device user-agent captured at consent.
object
Key-value pairs of custom data. Merged with existing fields.
boolean
default:"false"
When
true, replaces existing custom fields entirely instead of merging.string
Ad network source for attribution tracking.
string
Ad network campaign or account ID.
boolean
default:"false"
Run carrier phone validation on each number. Accept-on-fail: numbers that complete validation are still added regardless of disposition —
invalid / unreachable numbers are stored with their validation payload so you can segment them out at send time via the validation_status audience filter. Billed $0.003 per non-cached lookup; once validated for your account, repeat lookups of the same number are free indefinitely (force=true to re-validate). See Phone Validation for disposition rules.Batch behavior. If any contact in the batch has use_validation=true, the billing gate is checked once for the whole request — a failing gate returns 402 before any contacts are processed. Numbers are never rejected by disposition; only a number with an invalid format (invalid_phone) or a validation-provider outage (provider_error) surfaces in the errors array.Cap. At most 500 contacts per request may set use_validation=true. Exceeding this rejects the whole request with HTTP 413 (too_many_contacts_with_validation).boolean
default:"false"
When
true, the contact is held pending confirmation and sent the confirmation message from optin_configuration_id; they activate only after replying Y/YES/CONFIRM. Requires optin_configuration_id to reference an active double-opt-in config on the same list. Cap: at most 100 contacts per request may set double_optin=true — exceeding this rejects the whole request with HTTP 413 (too_many_contacts_with_double_optin).string
ID of the double-opt-in opt-in configuration used to drive confirmation. Required (per contact) when that contact’s
double_optin is true.Response Fields
integer
Number of contacts that were successfully created or updated.
integer
Number of contacts that failed validation or processing.
array
Array of error objects for contacts that failed.
integer
Number of new Contact records created (phone numbers not previously in the system).
integer
Number of new ListContact records created (new contact-on-list memberships).
integer
Total number of journey enrollments across all contacts in the batch.
array
Array of warning strings about missed configuration, such as active Welcome Journeys matching the list when
use_journeys was not set.Examples
curl -X POST "https://api.tracklysms.com/api/v2/contacts/bulk" \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{
"phone_number": "+14155559876",
"list_number": "+18005551234",
"use_journeys": true,
"custom_fields": {"first_name": "Jane"}
},
{
"phone_number": "+12125551000",
"list_number": "+18005551234",
"custom_fields": {"first_name": "John"}
},
{
"phone_number": "+13105558888",
"list_number": "+18005551234",
"signup_date": "2025-03-10T08:00:00Z",
"ip_address": "198.51.100.17"
}
]
}'
import requests
contacts = [
{
"phone_number": "+14155559876",
"list_number": "+18005551234",
"use_journeys": True,
"custom_fields": {"first_name": "Jane"}
},
{
"phone_number": "+12125551000",
"list_number": "+18005551234",
"custom_fields": {"first_name": "John"}
},
{
"phone_number": "+13105558888",
"list_number": "+18005551234",
"signup_date": "2025-03-10T08:00:00Z",
"ip_address": "198.51.100.17"
}
]
response = requests.post(
"https://api.tracklysms.com/api/v2/contacts/bulk",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json"
},
json={"contacts": contacts}
)
data = response.json()
print(f"Created: {data['success_count']}, Errors: {data['error_count']}")
for error in data.get("errors", []):
print(f" Index {error['index']}: {error['code']} - {error['error']}")
const contacts = [
{
phone_number: "+14155559876",
list_number: "+18005551234",
use_journeys: true,
custom_fields: { first_name: "Jane" },
},
{
phone_number: "+12125551000",
list_number: "+18005551234",
custom_fields: { first_name: "John" },
},
{
phone_number: "+13105558888",
list_number: "+18005551234",
signup_date: "2025-03-10T08:00:00Z",
ip_address: "198.51.100.17",
},
];
const response = await fetch(
"https://api.tracklysms.com/api/v2/contacts/bulk",
{
method: "POST",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({ contacts }),
}
);
const data = await response.json();
console.log(`Created: ${data.success_count}, Errors: ${data.error_count}`);
{
"success_count": 2,
"error_count": 1,
"errors": [
{
"index": 2,
"phone_number": "3105558888",
"code": "invalid_phone",
"error": "Invalid E.164 phone number format"
}
],
"contacts_created": 2,
"list_contacts_created": 2,
"journeys_enrolled": 1
}
{
"success_count": 3,
"error_count": 0,
"errors": [],
"contacts_created": 3,
"list_contacts_created": 3,
"journeys_enrolled": 0
}
{
"error": "contacts array is required",
"code": "missing_contacts"
}
{
"error": "Maximum 1000 contacts per request",
"code": "too_many_contacts"
}
{
"error": "Maximum 500 contacts with use_validation=true per request",
"code": "too_many_contacts_with_validation"
}
{
"error": "Maximum 100 contacts with double_optin=true per request",
"code": "too_many_contacts_with_double_optin"
}
{
"error": "An active payment method is required when use_validation=true",
"code": "no_payment_method"
}
Error Codes
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | missing_contacts | The contacts array was not provided in the request body. |
| 401 | invalid_credentials | Missing or invalid X-Api-Key header. |
| 403 | account_suspended | Your account is suspended. Resolve outstanding billing or contact support. |
| 429 | rate_limited | Request throttled; retry with exponential backoff after the window resets. |
| 402 | no_billing_config / no_payment_method / payment_failed / suspended | At least one contact requested use_validation=true but the account has no active payment method. The whole batch is rejected. |
| 413 | too_many_contacts | The contacts array exceeds the 1,000 item limit. |
| 413 | too_many_contacts_with_validation | More than 500 contacts in the batch set use_validation=true. The whole request is rejected. |
| 413 | too_many_contacts_with_double_optin | More than 100 contacts in the batch set double_optin=true. The whole request is rejected. |
| 500 | internal_error | An unexpected server error occurred. |
Per-Contact Error Codes
These codes appear in theerrors array for individual contacts that failed:
| Code | Description |
|---|---|
missing_phone_number | The contact object is missing the phone_number field. |
missing_list_number | The contact object is missing the list_number field. |
invalid_phone | The phone number is not a valid E.164 number. |
invalid_list_number | The list number is not a valid E.164 number. |
list_not_found | No sending list with the given list_number exists for this account. |
provider_error | use_validation=true could not reach the validation provider for this number. Other contacts in the batch are still processed. |
payload_too_large | The individual contact payload exceeds the 4KB limit. |
double_optin_requires_config | double_optin=true was sent without optin_configuration_id. |
invalid_optin_configuration | optin_configuration_id is not an active double-opt-in config on this list. |
optin_configuration_requires_double_optin | optin_configuration_id was sent without double_optin=true. |
double_optin_not_supported_on_resubscribe | double_optin=true was set for a previously unsubscribed contact. Resubscribe without double_optin, or re-opt-in through the confirmation flow. |
double_optin_take_failed | The double-opt-in hold could not be created for this contact; the pending membership was rolled back. Retry this contact. |
save_error | The contact subscription could not be saved for this contact. Retry this contact. |
rate_limited | use_validation=true was rate-limited by the validation provider for this number. Retry this contact later. |
profile_create_failed | The contact profile could not be created; retry this contact. |
For large imports exceeding 1,000 contacts, split your data into batches and send multiple requests. Consider adding a brief delay between batches to avoid rate limiting.
Next Steps
Importing Contacts
Learn about import options and formats
Create Audience
Segment your new contacts
⌘I