> ## 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.

# Import Send History

> Import historical send records into your account.

Bulk import up to 1,000 historical send records per request. Use this endpoint to migrate data from another platform into Trackly SMS. Duplicate message IDs are automatically skipped, and invalid phone numbers are flagged in the response.

## Authentication

<ParamField header="X-Api-Key" type="string" required>
  Your Trackly SMS API key. Format: `trk_[32-char-alphanumeric]`.
</ParamField>

## Body Parameters

<ParamField body="records" type="array" required>
  An array of send records to import. Maximum of 1,000 records per request. Each record accepts the following fields:

  <Expandable title="Record fields">
    <ParamField body="id" type="string" required>
      A unique message ID. Duplicate IDs are skipped.
    </ParamField>

    <ParamField body="list_id" type="integer">
      The sending list ID. Either `list_id` or `list_number` is required.
    </ParamField>

    <ParamField body="list_number" type="string">
      The sending list phone number in E.164 format (e.g., `+14155551234`). Either `list_id` or `list_number` is required.
    </ParamField>

    <ParamField body="to_number" type="string" required>
      The recipient phone number in E.164 format.
    </ParamField>

    <ParamField body="message" type="string">
      The message text that was sent. Accepted for compatibility but not currently stored — the imported record does not persist message text.
    </ParamField>

    <ParamField body="creative_id" type="integer">
      Reference to a creative ID in Trackly SMS. Accepted for compatibility but not currently stored on the imported record.
    </ParamField>

    <ParamField body="schedule_id" type="string">
      Reference to a schedule ID in Trackly SMS. Accepted for compatibility but not currently stored on the imported record.
    </ParamField>

    <ParamField body="send_type" type="string" default="campaign">
      Type of send. One of: `campaign`, `transactional`, or `test`.
    </ParamField>

    <ParamField body="delivered" type="boolean">
      Whether the message was successfully delivered.
    </ParamField>

    <ParamField body="length" type="integer">
      Length of the message in characters.
    </ParamField>

    <ParamField body="timestamp" type="datetime" required>
      The original send timestamp in ISO 8601 format.
    </ParamField>
  </Expandable>
</ParamField>

<Note>
  All records imported via this endpoint are automatically marked as `imported=true`. This flag is set by the server and cannot be overridden. Imported records can be selectively cleared later via the [Clear History](/api-reference/v2/history/clear-history) endpoint.
</Note>

## Response Fields

<ResponseField name="success_count" type="integer">
  Number of send records successfully imported.
</ResponseField>

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

<ResponseField name="duplicates_skipped" type="integer">
  Number of records skipped because their ID already exists.
</ResponseField>

<ResponseField name="invalid_phones" type="integer">
  Number of records skipped due to invalid phone numbers.
</ResponseField>

<ResponseField name="errors" type="array">
  Array of error objects (maximum 100 returned). Each object contains:

  * `index` (integer) -- Position of the failed record in the input array.
  * `id` (string) -- The message ID of the failed record.
  * `code` (string) -- Machine-readable error code.
  * `error` (string) -- Human-readable error description.
</ResponseField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST https://api.tracklysms.com/api/v2/history/sends \
      -H "X-Api-Key: trk_your_api_key_here" \
      -H "Content-Type: application/json" \
      -d '{
        "records": [
          {
            "id": "hist_0001",
            "list_number": "+14155551234",
            "to_number": "+12025559876",
            "message": "Check out this deal!",
            "send_type": "campaign",
            "delivered": true,
            "length": 21,
            "timestamp": "2025-11-15T14:30:00Z"
          },
          {
            "id": "hist_0002",
            "list_id": 42,
            "to_number": "+13105558888",
            "message": "Limited time offer",
            "creative_id": 101,
            "send_type": "campaign",
            "delivered": true,
            "timestamp": "2025-11-15T14:31:00Z"
          }
        ],
        "imported": true
      }'
    ```

    ```python Python theme={null}
    import requests

    response = requests.post(
        "https://api.tracklysms.com/api/v2/history/sends",
        headers={
            "X-Api-Key": "trk_your_api_key_here",
            "Content-Type": "application/json",
        },
        json={
            "records": [
                {
                    "id": "hist_0001",
                    "list_number": "+14155551234",
                    "to_number": "+12025559876",
                    "message": "Check out this deal!",
                    "send_type": "campaign",
                    "delivered": True,
                    "length": 21,
                    "timestamp": "2025-11-15T14:30:00Z",
                },
                {
                    "id": "hist_0002",
                    "list_id": 42,
                    "to_number": "+13105558888",
                    "message": "Limited time offer",
                    "creative_id": 101,
                    "send_type": "campaign",
                    "delivered": True,
                    "timestamp": "2025-11-15T14:31:00Z",
                },
            ],
            "imported": True,
        },
    )

    print(response.json())
    ```

    ```javascript Node.js theme={null}
    const response = await fetch("https://api.tracklysms.com/api/v2/history/sends", {
      method: "POST",
      headers: {
        "X-Api-Key": "trk_your_api_key_here",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        records: [
          {
            id: "hist_0001",
            list_number: "+14155551234",
            to_number: "+12025559876",
            message: "Check out this deal!",
            send_type: "campaign",
            delivered: true,
            length: 21,
            timestamp: "2025-11-15T14:30:00Z",
          },
          {
            id: "hist_0002",
            list_id: 42,
            to_number: "+13105558888",
            message: "Limited time offer",
            creative_id: 101,
            send_type: "campaign",
            delivered: true,
            timestamp: "2025-11-15T14:31:00Z",
          },
        ],
        imported: true,
      }),
    });

    const data = await response.json();
    console.log(data);
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 201 - Success theme={null}
  {
    "success_count": 2,
    "error_count": 0,
    "duplicates_skipped": 0,
    "invalid_phones": 0,
    "errors": []
  }
  ```

  ```json 201 - Partial Success theme={null}
  {
    "success_count": 1,
    "error_count": 0,
    "duplicates_skipped": 1,
    "invalid_phones": 0,
    "errors": []
  }
  ```

  ```json 400 - Validation Error theme={null}
  {
    "error": "records array is required",
    "code": "missing_records"
  }
  ```

  ```json 413 - Payload Too Large theme={null}
  {
    "error": "Maximum 1000 records per request",
    "code": "too_many_records"
  }
  ```
</ResponseExample>

## Error Codes

Only two conditions reject the whole request. Every other error is reported **per record**: the request still returns `201`, and each failed record appears in the response's `errors[]` array with a `code` (see the Partial Success example above).

### Request errors (HTTP status)

| HTTP Status | Error Code         | Description                                                    |
| ----------- | ------------------ | -------------------------------------------------------------- |
| 400         | `missing_records`  | The `records` field is required and must be a non-empty array. |
| 413         | `too_many_records` | Exceeded the maximum of 1,000 records per request.             |

Authenticated requests can also fail with `401 invalid_credentials`, `403 account_suspended`, or `429 rate_limited` — see [Error Codes](/api-reference/v2/error-codes).

### Per-record errors (`errors[].code`, returned with HTTP 201)

| Error Code          | Description                                                          |
| ------------------- | -------------------------------------------------------------------- |
| `missing_id`        | A record is missing the `id` field.                                  |
| `missing_to_number` | A record is missing the `to_number` field.                           |
| `missing_timestamp` | A record is missing the `timestamp` field.                           |
| `invalid_phone`     | The phone number is not a valid E.164 format.                        |
| `list_not_found`    | No sending list found matching the given `list_id` or `list_number`. |
| `invalid_timestamp` | The timestamp is not a valid ISO 8601 datetime.                      |
| `save_error`        | An unexpected error occurred while saving the record.                |

## Next Steps

<CardGroup cols={2}>
  <Card title="Reporting Overview" icon="chart-bar" href="/guides/reporting/overview">
    Analyze your imported data
  </Card>

  <Card title="Bulk Create Contacts" icon="users" href="/api-reference/v2/contacts/bulk-create">
    Import contacts alongside history
  </Card>
</CardGroup>
