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
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.
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
code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json