> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useboom.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Events

> Record behavioral events, then read and list them back.

An **event** is something a person or object *did* at a point in time — a
`payment_made`, a `checkout_started`, a `loan_disbursed`. Unlike people and
custom objects (which you upsert to a current state), events are an
append-only log: each one is recorded once and read back later, individually
or as a filtered, paginated stream.

## Anatomy of an event

| Field                                         | Required | Notes                                                                                                                                 |
| --------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                                        | ✅        | Letters, numbers, and underscores only — no spaces or symbols.                                                                        |
| `externalId`                                  | ✅        | Your stable id for this event. It's the **idempotency key**: re-sending the same one returns `created: false` instead of duplicating. |
| `timestamp`                                   | —        | ISO 8601 event time. Defaults to now() when omitted.                                                                                  |
| `personExternalId`                            | —        | The person subject.                                                                                                                   |
| `customObjectType` + `customObjectExternalId` | —        | The object subject (sent together).                                                                                                   |
| `properties`                                  | —        | Free-form JSON describing the event.                                                                                                  |

<Note>
  At least one subject is required: `personExternalId`, **or** the
  `customObjectType` + `customObjectExternalId` pair. You may set both (a
  person event about an object).
</Note>

Subjects are referenced by **your** external ids. They're stored as-is and
Boom resolves the internal link best-effort — recording an event for a person
who hasn't synced yet still succeeds, and the link is back-stitched later. A
missing subject never fails the request.

## Record an event

```bash theme={null}
curl -X POST "$BASE/events" \
  -H "Authorization: Bearer $BOOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"payment_made","externalId":"evt_123","personExternalId":"user_42","properties":{"amount":1200}}'
```

Returns `{ "created": true, "eventId": "evt_db_1" }`.

For historical loads, **Record events in bulk** accepts up to 1000 per request.
Bulk ingest is treated as backfill — it does **not** fire journey enrollment,
so use the single endpoint for real-time, journey-triggering events.

## Read events back

Fetch one by the `externalId` you supplied:

```bash theme={null}
curl "$BASE/events/evt_123" -H "Authorization: Bearer $BOOM_KEY"
```

Or **list** them, newest first, with optional filters (combined with AND):

```bash theme={null}
# every payment_made for one person, in June
curl "$BASE/events?name=payment_made&personExternalId=user_42&start=2026-06-01T00:00:00Z&end=2026-07-01T00:00:00Z" \
  -H "Authorization: Bearer $BOOM_KEY"
```

Filters match the external references you supplied at ingest, so events
recorded before their subject synced are still returned.

## Pagination

Every list endpoint (events, people, custom objects) is **cursor-paginated**.
A response carries `next_cursor`; pass it back as `?cursor=` to get the next
page, and loop until `next_cursor` is `null`:

```
GET /events?limit=1000              → { data:[1000], next_cursor:"c1" }
GET /events?limit=1000&cursor=c1    → { data:[1000], next_cursor:"c2" }
GET /events?limit=1000&cursor=c2    → { data:[42],   next_cursor:null }   # done
```

`limit` defaults to 100 (max 1000). The cursor is opaque and stable under
concurrent writes — no skipped or duplicated rows.

## Related

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Record your first event alongside people and objects.
  </Card>

  <Card title="Relationship types" icon="shapes" href="/relationship-types">
    Model the links between the people and objects your events reference.
  </Card>
</CardGroup>
