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

# Track outcomes

> Report what users did with POST /events: request and response, naming, timestamps, properties versus context, idempotent ids, and batches of up to 200.

`POST /events` reports what a user did. Curious attributes each event to the decisions that preceded it for that user, within the windows of the rewards and constraints that name it; how attribution works is on [Events and attribution](https://docs.trycurious.ai/concepts/events.md). Full field reference: [the endpoint](https://docs.trycurious.ai/api-reference/events.md).

```bash theme={null}
curl https://api.trycurious.ai/v1/events \
  -H "Authorization: Bearer $CURIOUS_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_123",
    "name": "purchased",
    "value": 49.99,
    "properties": { "plan": "pro" },
    "context": { "device": "mobile" },
    "timestamp": "2026-04-30T22:33:42Z",
    "id": "ord_8f3a_purchased"
  }'
```

```json theme={null}
{ "status": "success" }
```

## Naming

Names are past tense: `purchased`, `clicked`, `viewed`, `signed_up`. Use one name with properties rather than many names: `email_opened` with `{ "email_name": "welcome" }`, not `welcome_email_opened`, so a reward can match the family or a member of it.

Names starting with `$` are reserved for the browser SDK's own system events (`$pageview`, `$click`, `$form_submit`), which the endpoint accepts only from that SDK under a publishable key. Any other caller sending a `$` name gets a `400` with `` `$`-prefixed names are reserved for Curious SDK events ``; if you forward events from another tool, rename at the source (`$pageview` to `pageview`).

## `value`

Optional, defaults to 1; `null` also means 1. For a `semicontinuous` or `continuous` reward it is the amount, in your units. A `binary` reward ignores it and a `count` reward counts each event once.

## `properties` versus `context`

`properties` describe the event: the plan bought, the email opened, the currency. Rewards and constraints can filter on them. `context` describes the situation at event time, such as device or page, and is kept with the event but is not a filter. Neither is written to the user; user state changes through [`decide`](https://docs.trycurious.ai/guides/decide) or [`identify`](https://docs.trycurious.ai/guides/identify).

## `timestamp`

When the event happened, as an ISO-8601 datetime with an explicit timezone (`2026-04-30T22:33:42Z`, `2026-04-30 22:33:42+00:00`), or a date alone, read as UTC midnight. A naive datetime such as `2026-04-30 22:33` is a `400` with `timestamp must be an ISO-8601 datetime with an explicit timezone`. Omit it for events reported as they happen; send it for buffered flushes and backfills, so a late event still attributes to the right decision. A value more than five minutes in the future is rejected as a units or timezone bug.

The write time is recorded separately as `created_at`, which is system-owned: sending it is a `400`.

## `id`: idempotent delivery

`id` is your idempotency key for the event, unique per organization and environment. A retry with the same `id` is deduplicated and answered as a success with `duplicate: true`, so a flaky network can never double-count a purchase. Use something stable from your side, such as an order id plus the event name, or let the [SDK](https://docs.trycurious.ai/sdks/node/events.md) mint one and keep it across retries. Events without an `id` are delivered at least once. See [Idempotency](https://docs.trycurious.ai/api-reference/idempotency.md).

## Unknown fields

Events ignore fields they do not know, so an integration can forward a richer record. Two names are the exception and are rejected rather than ignored: `created_at`, and the retired name `as_of` (the message says to send the time as `timestamp`).

## The batch form

```json theme={null}
{
  "user_id": "user_123",
  "timestamp": "2026-04-30T22:33:42Z",
  "events": [
    {
      "name": "viewed",
      "properties": { "page": "/pricing" },
      "id": "v-1"
    },
    {
      "name": "clicked",
      "properties": { "target": "upgrade" },
      "id": "c-1"
    },
    {
      "name": "purchased",
      "value": 49.99,
      "user_id": "user_456",
      "id": "p-9"
    }
  ]
}
```

```json theme={null}
{
  "results": [
    { "index": 0, "id": "v-1", "status": "ok" },
    { "index": 1, "id": "c-1", "status": "ok", "duplicate": true },
    { "index": 2, "id": "p-9", "status": "ok" }
  ],
  "duplicates": 1
}
```

Up to 200 items. The envelope's `user_id`, `context` and `timestamp` apply to items that omit them, and items may override them, including the user for cross-user syncs. Results come back per item in input order inside a `200`; an item that fails validation gets its own `{ "status": "error", "error", "message" }` and never fails its siblings. See [Batch calls](https://docs.trycurious.ai/guides/batch.md).

## What makes a good event stream

* Report the events your rewards and constraints name, with the properties your filters need. Nothing more is required.
* Report cost-like outcomes too (`refunded`, `unsubscribed`); a negatively weighted reward turns them into what Curious avoids.
* Keep `user_id` the same id you decide with. If a user starts anonymous and later logs in, [`identify`](https://docs.trycurious.ai/guides/identify) links the two so earlier events count.
* Report for everyone, not only users Curious decided for. Attribution finds the decision when there is one.

## With the server SDK

```ts theme={null}
curious.track({
  userId: user.id,
  name: "purchased",
  value: 49.99,
  properties: { plan: "pro" },
});
await curious.flush(); // or hand it to your runtime's lifecycle
```

See [Events and identify with the server SDK](https://docs.trycurious.ai/sdks/node/events.md).

Next: [Fallbacks and timeouts](https://docs.trycurious.ai/guides/fallbacks.md). If events arrive but the experiment shows no rewards, see [Rewards are not being attributed](https://docs.trycurious.ai/troubleshooting/rewards-not-attributed.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.