Platform API · Ingest API
Ingest API guide
The Ingest API imports contacts and events into an Instasent project so every downstream surface — audiences, campaigns, analytics — sees the same source of truth.
The Ingest API is the door that customer data walks through on its way into Instasent. Every contact you create, every event you record, lands in a project as a datasource and feeds the audience that your campaigns, segments and analytics read from.
It is a focused subset of the Product API: three write endpoints for contacts and events, plus a handful of read endpoints for validating your payloads against the datasource specs. If you already run Product API calls, the same tokens work here — see Authentication.
When to reach for it
Pick the Ingest API when an external system of record owns your customer data and Instasent needs a steady feed of it:
- CRM or e-commerce syncs — push new and updated contacts as your source platform mutates them.
- Product event streams — record purchases, sign-ups, page views or any custom interaction you want to segment on.
- Backfills — load historical data into a fresh project before flipping campaigns on.
If instead you need to read back an audience, trigger a campaign, or orchestrate messaging, that is the Product API's job.
Mental model
Three nouns and one rule of thumb:
- Contacts — the people in your audience. Identified by a
_user_idthat is unique within your datasource. Sending the same_user_idagain updates the existing record. - Events — time-stamped interactions tied to a contact (
purchase,viewed_product,reservation_made, anything you define). Events are immutable once accepted. - Datasource — the logical stream your API token writes to. A project can have several datasources, and when two disagree about the same contact, the Ingest datasource wins.
The rule of thumb: treat Ingest writes as idempotent for contacts and append-only for events. Your client should be safe to retry contact uploads; event uploads must use a stable _event_id so duplicates are rejected on our side.
A day in the life of a contact
Your CRM emits a change
A new customer signs up, or an existing one updates their phone number. Your integration catches the change event.
POST /stream/contacts
Send the contact —
_user_id, identity fields, any custom attributes — as a single-item array. Omit attributes you do not own; nothing you leave out gets overwritten.Instasent merges the record
The request answers
202 Acceptedas soon as the contact is queued. It is stored in the datasource shortly after, usually within a few seconds, and the audience view follows.Later: POST /stream/events
The same customer places an order. You POST a
purchaseevent with_user_idand_event_parameters. The event lands on the contact's timeline and becomes queryable for segmentation.
Shape of the API
All endpoints live under https://api.instasent.com/v1/project/{project}/datasource/{datasource}/:
| Endpoint | Purpose |
|---|---|
POST /stream/contacts | Create or update up to 100 contacts per call. |
DELETE /stream/contacts/{userId} | Permanently remove a contact. |
POST /stream/events | Record up to 100 events per call. |
GET /stream | Stream metadata and quota — also the simplest auth probe. |
GET /stream/specs/* | Discover the attributes, event types and per-event parameters the datasource accepts. |
Per-parameter detail lives in the API Reference.
How contacts are merged
Attributes in a contact payload follow a few simple rules:
- Omitted attribute → existing value kept. Send partial updates without fear.
- Explicit
null→ attribute cleared. - Same
_user_idacross datasources → contacts merge automatically into one audience record, and Ingest values take precedence when they collide.
How events flow
Two shapes for POST /stream/events:
- Event-only — just
_user_id,_event_id,_event_typeand_event_parameters. The contact must already be stored: if it does not exist, or is still being processed, the event is rejected (see below). - Event with embedded contact — add
_user_datato create or update the contact in the same call. Saves a round-trip when your source system can produce both at once.
_event_id must be stable and unique per event. An event whose _event_id is already stored for the same _event_type is rejected as a duplicate instead of being stored twice — that is what makes retries safe.
Sending a new contact and its event together
A contact sent to POST /stream/contacts is stored a moment after the 202 response, usually within a few seconds. An event-only request sent in that gap finds no contact and is rejected with 400 and a message ending in Contact with that id must be created prior sending any events (inside a batch where other items were accepted, it is listed in the errors of a 202 instead). Two ways to handle it:
- Embed the contact in the event (recommended). Send the event with
_user_dataand skip the separate contacts call: the event creates or updates the contact itself. See embedding the contact in the event. - Retry the rejected event after a short wait, for example after 1, 2 and 4 seconds. The rejected event was not stored, so resending it with the same
_event_idcannot create a duplicate.
Automations and the event time window
Every event you send is stored on the contact's timeline regardless of its date, but only events whose _event_date is close to reception time trigger automations. The Ingest API applies a symmetric window of ±1 hour around the moment we receive the request:
abs(_event_date − reception_time) ≤ 1 hour
Events outside that window are accepted, persisted and queryable for segmentation — they just do not fire any automation. This is a safeguard: without it, a single backfill of historical orders, a producer with clock drift, or a queue replayed after a long stall could enqueue thousands of SMS, emails and other messages for things that already happened.
In practice:
- Omit
_event_datewhen the event is happening "now" — it defaults to the current time and is guaranteed to be inside the window. - Send
_event_dateonly when you genuinely need a specific timestamp, and make sure it is within ±1h of reception. - Backfills of historical events for segmentation or analytics should be sent with their real dates. Automations will not fire for them — by design.