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

{
  "name": "string",
  "email": "user@example.com",
  "external_id": "string",
  "properties": {},
  "occurred_at": "2026-05-17T20:00:00Z",
  "idempotency_key": "string",
  "dedup_mode": "retry"
}

Responses

202 Per-item admission results, in input order application/json
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