Ingest contact activities (single or batch)

Search Documentation

Search across all developer documentation

activities

Ingest contact activities (single or batch)

POST /v1/activities
Auth required api.activities.write

Records free-form behavioral signals about contacts. The body is one activity object or an array of up to 1000. Always returns 202 Accepted with a per-item result — a batch is best-effort, never all-or-nothing. Accepted items are written asynchronously; a brief delay before they appear on the read endpoints is expected.

Identity: supply email or external_id (or both). An unknown email creates a stub contact (no consent state — a stub can be segmented later but is not mailable). An unknown external_id without an email is rejected per-item; when both are supplied, the external_id is attached to the contact resolved (or created) by email.

Idempotency: an optional per-item idempotency_key dedups literal retries for 24 hours (org-scoped) — a best-effort retry-collapse window, not an exactly-once or once-per-lifetime guarantee (it can both fail open on a Redis outage and simply expire). A replayed key returns status: accepted with duplicate: true, usually with the original activity_id so you can reconcile. The request-level Idempotency-Key header is not used on this endpoint.

Dedup mode: set dedup_mode: "once" on an item (default is "retry", the 24h window above) to durably claim it at most once per (contact, name), forever — for once-per-lifetime milestones (e.g. signed_up, first_send) where a periodic re-emit or a dedup-window expiry must never produce a second activity. idempotency_key is ignored when dedup_mode is "once". Unlike the default mode, dedup_mode: "once" fails closed: if the durable claim store is unavailable the item is rejected with reason: dedup_unavailable rather than risking an unclaimed duplicate — retry it.

For once-only enrollment into a Drip Workflow (as opposed to once-only activity ingestion), the correctness boundary is the workflow's reentry policy, not activity dedup — see the Drip Workflows guide.

Limits: max 1000 items per request, 1 MiB body, 32 KiB serialized properties per item; a per-org item-volume cap rejects items with per-item reason: rate_limited when exceeded.

Request body

Content type: application/json

Accepts a single object as shown below, or a batch — an array of up to 1000 of the same shape.

name string required

Free-form activity name (e.g. purchase, page_view). Names are values — no registry, no schema.

Constraints: length 0–200

email string<email> optional

Contact email. Unknown emails create a stub contact.

external_id string optional

Customer-supplied stable contact key (unique per org). Unknown without an email → per-item reject.

Constraints: length 0–256

properties object optional

Free-form JSON payload (max 32 KiB serialized). Descriptive only — never evaluated server-side.

occurred_at string<date-time> optional

Client event time (RFC 3339). Defaults to receipt time.

idempotency_key string optional

Optional per-item retry-dedup key, org-scoped, 24-hour window. Ignored when dedup_mode is "once".

Constraints: length 0–255

dedup_mode string enum optional

"retry" (default) is the 24h idempotency_key retry-collapse window, best-effort. "once" durably claims the item at most once per (contact, name), forever — fails closed (reason: dedup_unavailable) rather than risking a duplicate if the claim store is unavailable.

One of: retry, once

Default: retry

Responses

Errors follow the RFC 7807 problem format — see the error reference.

202 Per-item admission results, in input order application/json
data array<object> required

Each entry in data:

index integer required

Position of the item in the request (0-based).

status string enum required

One of: accepted, rejected

duplicate boolean optional

True when the item replayed a prior claim (idempotency_key or dedup_mode once) — nothing new was written.

activity_id string<uuid> optional

Server-assigned id. Set on every accepted item, including duplicates — for a duplicate this is the id of the write that already exists (always set for dedup_mode=once; best-effort for the default 24h retry-collapse mode).

contact_id string<uuid> optional

The contact the activity resolved to.

reason string enum optional

Set on rejections only.

One of: invalid_name, invalid_email, invalid_external_id, invalid_idempotency_key, invalid_dedup_mode, missing_identity, unknown_external_id, external_id_conflict, properties_too_large, rate_limited, dedup_unavailable

401 Missing, malformed, or unknown API key application/problem+json
403 Key lacks the required scope or plan limit violated application/problem+json
422 Query parameter or path value failed validation application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json