# 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](/product-api/guide): 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](/ingest-api/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_id` that is unique within your datasource. Sending the same `_user_id` again 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 discarded on our side.

## A day in the life of a contact

#### 1. Your CRM emits a change

A new customer signs up, or an existing one updates their phone number. Your integration catches the change event.

#### 2. 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.

#### 3. Instasent merges the record

The datasource is updated synchronously; the audience view is refreshed within seconds.

#### 4. Later: POST /stream/events

The same customer places an order. You POST a `purchase` event with `_user_id` and `_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](/ingest-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_id` across datasources** → contacts merge automatically into one audience record, and Ingest values take precedence when they collide.

> **Tip**: Custom attributes are first-class citizens — just send them alongside the reserved `_`-prefixed ones. Validate the names against [`/stream/specs/attributes`](/ingest-api/reference) during integration to catch typos before they create new columns.

## How events flow

Two shapes for `POST /stream/events`:

- **Event-only** — just `_user_id`, `_event_id`, `_event_type` and `_event_parameters`. Skipped silently if the contact does not exist.
- **Event with embedded contact** — add `_user_data` to 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. Resend the same id and we drop the duplicate — that is what makes safe retries possible.

## 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_date`** when the event is happening "now" — it defaults to the current time and is guaranteed to be inside the window.
- **Send `_event_date`** only 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.

> **Warning**: A future-dated `_event_date` (for example, a producer whose clock is two hours ahead) silently skips automations. If your automations stop firing but events still land on the timeline, check the clock of whatever system is generating `_event_date`.

## What to read next

- [Quickstart](/ingest-api/quickstart) - Load your first contact and event in under five minutes.
- [Authentication](/ingest-api/authentication) - Datasource tokens, Product API tokens, rotation.
- [Errors](/ingest-api/errors) - Status codes and the partial-success response shape.
- [API Reference](/ingest-api/reference) - Every endpoint, every parameter.
