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

# Import Events (Many Subscribers)

> Record events for many subscribers in one request

Record a bounded batch of up to 25 events for many subscribers in one request - the
pipeline companion to [Trigger Event](/api-reference/subscribers/events/trigger)
and [Trigger Events (Bulk)](/api-reference/subscribers/events/trigger-bulk),
built for data pipelines and warehouse syncs. Each event carries its own
subscriber identity.

Events are grouped per contact:

* A contact whose events are all more than an hour old is imported as
  **history** in one idempotent write: stored with real timestamps, visible to
  segments and the timeline, but no sequences, sync rules, waiting steps,
  goals, webhooks, or double-opt-in confirmation emails run.
* A contact with any recent event takes the **live** path with normal side
  effects, processed in order.

<Note>
  Contacts process independently, so an error response may still include
  recorded events. Treat errors as partial success and retry the same request -
  with an `eventId` on every event, a retry records nothing twice.
</Note>

## Request Body

<ParamField body="events" type="object[]" required>
  One to 25 events. Every event requires a `name`, a source-owned `eventId`, and
  an `email` or an `externalId`. Email is required when creating a contact; an
  external-ID-only row must resolve to an existing contact. `null` identifier
  and timestamp values are treated as absent, so warehouse exports with null
  columns import cleanly. Keys outside the documented fields are ignored - keep
  row data inside `properties`, or it is silently discarded.
</ParamField>

<ParamField body="events[].email" type="string">
  Subscriber delivery email address. Required when the event may create a new
  contact.
</ParamField>

<ParamField body="events[].externalId" type="string">
  Your app/customer/user ID for this subscriber. It can be used without email
  only when it resolves to an existing contact.
</ParamField>

<ParamField body="events[].name" type="string" required>
  Event name (e.g., `purchase_completed`, `saas.purchase`)
</ParamField>

<ParamField body="events[].properties" type="object">
  Event properties/metadata.
</ParamField>

<ParamField body="events[].occurredAt" type="string">
  ISO 8601 timestamp of when the event actually happened. Defaults to now.
  Classification is per contact: only when all rows for that contact are more
  than an hour old is the group historical. Any recent row makes every event for
  that contact live while preserving each supplied timestamp.
</ParamField>

<ParamField body="events[].eventId" type="string" required>
  Your source-owned ID for this event, used as an idempotency key on both the
  live and historical paths. Derive it from your source data (an order ID, a
  warehouse row key) so re-running the import is safe.
</ParamField>

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/subscribers/events/imports" \
  -H "Authorization: Bearer API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "email": "one@example.com",
        "name": "purchase_completed",
        "eventId": "order_1001",
        "occurredAt": "2026-08-01T12:00:00Z",
        "properties": { "amount": 99.99 }
      },
      {
        "email": "two@example.com",
        "externalId": "user_42",
        "name": "purchase_completed",
        "eventId": "order_1002",
        "occurredAt": "2026-08-02T09:30:00Z",
        "properties": { "amount": 49.99 }
      }
    ]
  }'
```

## Responses

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "total": 2,
    "recorded": 2,
    "duplicates": 0,
    "failed": 0,
    "sideEffectFailed": 0,
    "subscribers": 2
  }
  ```

  ```json 200 (partial failure) theme={null}
  {
    "success": false,
    "total": 2,
    "recorded": 1,
    "duplicates": 0,
    "failed": 1,
    "sideEffectFailed": 0,
    "subscribers": 2,
    "failures": [{ "index": 1, "error": "Subscriber identity conflict" }],
    "error": "Subscriber identity conflict"
  }
  ```

  ```json 200 (post-write failure) theme={null}
  {
    "success": false,
    "total": 1,
    "recorded": 1,
    "duplicates": 0,
    "failed": 0,
    "sideEffectFailed": 1,
    "subscribers": 1,
    "sideEffectFailures": [{ "index": 0, "stages": ["automation-triggering"] }],
    "error": "One or more events were recorded, but downstream side effects failed."
  }
  ```

  ```json 400 theme={null}
  {
    "success": false,
    "error": "events[3] needs an email or an externalId."
  }
  ```

  ```json 401 theme={null}
  { "success": false, "error": "Unauthorized" }
  ```

  ```json 403 theme={null}
  {
    "success": false,
    "error": "API key is missing required scope: automations:trigger"
  }
  ```

  ```json 500 theme={null}
  { "success": false, "error": "Internal server error" }
  ```
</ResponseExample>

## Counts

`total = recorded + duplicates + failed`. `duplicates` counts events whose
`eventId` was already recorded for that contact and event name, so no second
receipt was written. Live duplicate retries may idempotently re-attempt
downstream recovery. `sideEffectFailed` is orthogonal to receipt accounting:
it can accompany either a newly `recorded` row or a `duplicate` row whose
downstream work (or historical automation shielding) still failed.
`subscribers` is the number of distinct identities in the request.

## Throughput

At 25 events per request within the standard
[rate limits](/api-reference/introduction#rate-limiting), this endpoint
sustains up to 2,500 events per minute while keeping each synchronous request
bounded - use it instead of looping over
the single-subscriber endpoints when syncing events from a pipeline or
warehouse. See the
[data pipeline ingestion guide](/guides/data-pipeline-ingestion) for the full
recipe.
