# Ingesting events

`POST /v1/objects/{object_id}/events` records one or more events about an
object and schedules its analysis.

```json
{
  "object_type": "user",
  "events": [
    { "type": "event", "name": "profile.updated", "metadata": { "field": "bio" } },
    { "type": "event", "name": "payout.requested", "metadata": { "amount": 950, "currency": "usd" } },
    { "type": "message", "content": "is this still available? can I pay by wire?", "occurred_at": "2026-09-27T09:58:00Z" }
  ]
}
```

| Field | | |
|-------|---|---|
| `events` | required | One or more events, below. |
| `object_type` | optional | A classification such as `user` or `listing`. |
| `wait` | optional | `true` scores the object inline instead. See [Direct moderation](https://sigwise.ai/docs/guides/moderation.md). |
| `signals` | optional | With `wait`, the signals to score. |
| `include_history` | optional | With `wait`, whether to score over the object's history (default) or only these events. |

Each event:

| Field | | |
|-------|---|---|
| `type` | required | `event` (an action) or `message` (text). |
| `name` | optional | The action's name, like `login.failed`, or a message's subject. |
| `content` | optional | Free text, such as a message body. |
| `metadata` | optional | Any JSON object. |
| `occurred_at` | optional | RFC 3339 time it happened. Defaults to when the API received it. |

The response is `202 Accepted`:

```json
{ "object_id": "user-42", "accepted": 3, "analysis_status": "scheduled", "analysis_delay_ms": 5000 }
```

## Debouncing

The analysis runs `analysis_delay_ms` after the latest event. Events that
arrive in the meantime push it back and join it, so a burst of activity costs
one analysis. Send events as they happen; there is no need to batch them
yourself, though batching several in one request saves round trips.

## What to send

The analyzer reasons over whatever you send, so send the evidence a human
moderator would want to see:

- **Messages** between users, reviews, listing descriptions, support tickets.
- **Actions** that matter for your signals: sign-ups, profile and payout
  changes, failed logins, reports by other users, refunds.
- **Context** in `metadata`: amounts, counts, account age, country. Keep it
  small and relevant; avoid secrets and data you don't need analyzed.

## Limits and validation

- A request body is at most **1 MiB**.
- Bodies are decoded strictly: an unknown field is a `400`, so a typo like
  `"evnts"` fails loudly instead of being ignored.
- `type` must be `event` or `message`, and `metadata` must be valid JSON.
- When the prepaid balance is empty, ingestion answers `402` and records
  nothing. See [Billing and usage](https://sigwise.ai/docs/guides/billing.md).

## Retrying safely

A timeout or a `5xx` doesn't tell you whether your events were recorded. Send
an `Idempotency-Key` header, any unique string such as a UUID, and retry with
the same key:

```bash
curl -X POST "$API/v1/objects/user-42/events" \
  -H "Idempotency-Key: 6f1c0e3e-5b8a-4c7e-9f2d-1a2b3c4d5e6f" \
  -H "Content-Type: application/json" \
  -d '{"object_type":"user","events":[{"type":"message","content":"hi"}]}'
```

- The events are recorded once, however many times the request is sent.
- A repeat of an asynchronous request is answered like the first and carries
  `Idempotent-Replayed: true`.
- A repeat of a `wait: true` request returns `409` and does not analyze (or
  charge) again. Read the verdict with `GET /v1/objects/{object_id}`.
- Using a key for a different object returns `422`.
- Keys are remembered for 24 hours, per account.

## Re-analyzing without new events

- `POST /v1/objects/{object_id}/analyze` schedules an immediate analysis of one
  object, for example after changing a signal. It answers `404` for an object
  with no events.
- `POST /v1/analyze` schedules every object at bulk priority, behind live
  traffic. Each analysis is billed, so prefer a [backfill](https://sigwise.ai/docs/guides/backfills.md)
  when you only added a signal.

## Reading what was recorded

`GET /v1/objects/{object_id}/events` returns the 200 most recent events, newest
first, and `GET /v1/objects/{object_id}/state` returns the compacted summary of
older ones.
