> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracklysms.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bulk Create Contacts

> Add up to 1,000 contacts to sending lists in a single request, with per-contact error reporting.

Creates or updates multiple contacts in a single batch operation. Each contact in the array follows the same schema as the single [Create Contact](/api-reference/v2/contacts/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

<ParamField body="contacts" type="array" required>
  Array of contact objects to create. Maximum of **1,000** contacts per request.

  <Expandable title="Contact object properties">
    <ParamField body="phone_number" type="string" required>
      Contact phone number in E.164 format (e.g., `+14155559876`).
    </ParamField>

    <ParamField body="list_number" type="string" required>
      Sending list phone number in E.164 format (e.g., `+18005551234`). Must be a list owned by your account.
    </ParamField>

    <ParamField body="use_journeys" type="boolean" default="false">
      When `true`, enroll the contact in matching Welcome Journeys.
    </ParamField>

    <ParamField body="skip_journey_if_exists" type="boolean" default="true">
      Skip journey enrollment if the ListContact already exists.
    </ParamField>

    <ParamField body="signup_date" type="string">
      Signup date in ISO 8601 UTC format. Defaults to now.
    </ParamField>

    <ParamField body="ip_address" type="string">
      IP address for consent tracking.
    </ParamField>

    <ParamField body="url" type="string">
      Signup URL for consent tracking.
    </ParamField>

    <ParamField body="consent_method" type="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.
    </ParamField>

    <ParamField body="consent_source" type="string">
      URL or description of where consent was captured. Defaults to the contact's `url` when omitted.
    </ParamField>

    <ParamField body="consent_language" type="string">
      Exact disclosure text shown to the contact at consent.
    </ParamField>

    <ParamField body="user_agent" type="string">
      Browser/device user-agent captured at consent.
    </ParamField>

    <ParamField body="custom_fields" type="object">
      Key-value pairs of custom data. Merged with existing fields.
    </ParamField>

    <ParamField body="override_custom_fields" type="boolean" default="false">
      When `true`, replaces existing custom fields entirely instead of merging.
    </ParamField>

    <ParamField body="ad_network_source" type="string">
      Ad network source for attribution tracking.
    </ParamField>

    <ParamField body="ad_network_id" type="string">
      Ad network campaign or account ID.
    </ParamField>

    <ParamField body="use_validation" type="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](/api-reference/phone/validate) 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`).
    </ParamField>

    <ParamField body="double_optin" type="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`).
    </ParamField>

    <ParamField body="optin_configuration_id" type="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`.
    </ParamField>
  </Expandable>
</ParamField>

## Response Fields

<ResponseField name="success_count" type="integer">
  Number of contacts that were successfully created or updated.
</ResponseField>

<ResponseField name="error_count" type="integer">
  Number of contacts that failed validation or processing.
</ResponseField>

<ResponseField name="errors" type="array">
  Array of error objects for contacts that failed.

  <Expandable title="Error object properties">
    <ResponseField name="index" type="integer">
      Zero-based index of the failed contact in the input array.
    </ResponseField>

    <ResponseField name="phone_number" type="string">
      Phone number of the failed contact (if provided).
    </ResponseField>

    <ResponseField name="code" type="string">
      Machine-readable error code (e.g., `invalid_phone`, `list_not_found`, `provider_error`).
    </ResponseField>

    <ResponseField name="error" type="string">
      Human-readable error description.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="contacts_created" type="integer">
  Number of new Contact records created (phone numbers not previously in the system).
</ResponseField>

<ResponseField name="list_contacts_created" type="integer">
  Number of new ListContact records created (new contact-on-list memberships).
</ResponseField>

<ResponseField name="journeys_enrolled" type="integer">
  Total number of journey enrollments across all contacts in the batch.
</ResponseField>

<ResponseField name="warnings" type="array">
  Array of warning strings about missed configuration, such as active Welcome Journeys matching the list when `use_journeys` was not set.
</ResponseField>

## Examples

<RequestExample>
  ```bash cURL theme={null}
  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"
        }
      ]
    }'
  ```

  ```python Python theme={null}
  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']}")
  ```

  ```javascript Node.js theme={null}
  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}`);
  ```
</RequestExample>

<ResponseExample>
  ```json 201 - Success (partial errors) theme={null}
  {
    "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
  }
  ```

  ```json 201 - All successful theme={null}
  {
    "success_count": 3,
    "error_count": 0,
    "errors": [],
    "contacts_created": 3,
    "list_contacts_created": 3,
    "journeys_enrolled": 0
  }
  ```

  ```json 400 - Missing contacts array theme={null}
  {
    "error": "contacts array is required",
    "code": "missing_contacts"
  }
  ```

  ```json 413 - Too many contacts theme={null}
  {
    "error": "Maximum 1000 contacts per request",
    "code": "too_many_contacts"
  }
  ```

  ```json 413 - Too many contacts with use_validation theme={null}
  {
    "error": "Maximum 500 contacts with use_validation=true per request",
    "code": "too_many_contacts_with_validation"
  }
  ```

  ```json 413 - Too many contacts with double_optin theme={null}
  {
    "error": "Maximum 100 contacts with double_optin=true per request",
    "code": "too_many_contacts_with_double_optin"
  }
  ```

  ```json 402 - Billing gate (any contact has use_validation=true) theme={null}
  {
    "error": "An active payment method is required when use_validation=true",
    "code": "no_payment_method"
  }
  ```
</ResponseExample>

## 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 the `errors` 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.                                                                                      |

<Note>
  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.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Importing Contacts" icon="upload" href="/guides/contacts/importing">
    Learn about import options and formats
  </Card>

  <Card title="Create Audience" icon="users" href="/api-reference/v2/audiences/create-audience">
    Segment your new contacts
  </Card>
</CardGroup>
