Ingest contact activities (single or batch)
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
{
"name": "string",
"email": "user@example.com",
"external_id": "string",
"properties": {},
"occurred_at": "2026-05-17T20:00:00Z",
"idempotency_key": "string",
"dedup_mode": "retry"
} Responses
code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json