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

# POST /v1/track

> Record something someone did — an event name and its properties — on their timeline.

Records something someone did, like `cart_added` or `order_placed`, on their contact's timeline, where it can feed audiences and custom traits. It never creates a contact: an event for someone not yet known is held, and joins the contact once they are identified with a phone.

Server events need the **OMNI API** and **Customer data** in your plan, and an OMNI API key — not a Customer data source key (`sk_live_…`), which is now refused.

<Note>Needs the `cdp.events:write` scope.</Note>

## Body

| Field | Type | Required | Notes |
| :- | :- | :- | :- |
| `event` | string | Yes | What they did. Letters, digits, spaces and `_ . : -`, starting with a letter or digit; up to 80 characters. Names starting `message.`, `conversation.`, `note.`, `identity.`, `ticket.`, `appointment.`, `agent.`, `deal.`, `audience.` or `consent.` are FireFlo's own and refused. |
| `properties` | object | No | Details of it: up to 50, and 8 KB in all. |
| `timestamp` | string | No | When it happened, as an ISO 8601 time with its offset. Now when left out. |
| `user_id` | string | One of these | Your own customer id. `external_id` is the same thing. |
| `phone` | string | One of these | Their mobile number, with its country code. One without is read as your account's country. |
| `email` | string | One of these | Their email address. |
| `anonymous_id` | string | One of these | The website visitor id, to join what they did before logging in. |

Say who did it with at least one of `user_id`, `phone`, `email` or `anonymous_id`.

Send an `Idempotency-Key` header to take it once however often the request is retried.

Events are applied in the background, so the answer only says how many were taken. One that can't be applied — no one named, a reserved event name, properties too large — is dropped without stopping the rest, and shows with its reason under **Live events** on **Settings → Data sources** in the panel.

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.fireflo.au/v1/track" \
    -H "Authorization: Bearer $OMNI_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: cart-CUST-10482-KURTA-BLU-M" \
    -d '{
      "user_id": "CUST-10482",
      "event": "cart_added",
      "properties": {
        "sku": "KURTA-BLU-M",
        "price": 1499,
        "currency": "INR"
      },
      "timestamp": "2026-10-06T10:42:17+05:30"
    }'
  ```

  ```python Python theme={null}
  import os

  import requests

  response = requests.post(
      "https://api.fireflo.au/v1/track",
      headers={
          "Authorization": f"Bearer {os.environ['OMNI_API_KEY']}",
          "Idempotency-Key": "cart-CUST-10482-KURTA-BLU-M",
      },
      json={
          "user_id": "CUST-10482",
          "event": "cart_added",
          "properties": {
              "sku": "KURTA-BLU-M",
              "price": 1499,
              "currency": "INR",
          },
          "timestamp": "2026-10-06T10:42:17+05:30",
      },
  )
  print(response.status_code, response.json())
  ```

  ```javascript Node theme={null}
  const response = await fetch("https://api.fireflo.au/v1/track", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OMNI_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": "cart-CUST-10482-KURTA-BLU-M",
    },
    body: JSON.stringify({
      "user_id": "CUST-10482",
      "event": "cart_added",
      "properties": {
        "sku": "KURTA-BLU-M",
        "price": 1499,
        "currency": "INR"
      },
      "timestamp": "2026-10-06T10:42:17+05:30"
    }),
  });
  console.log(response.status, await response.json());
  ```
</CodeGroup>

## Response

`202 Accepted` — taken, to be applied in the background.

```json theme={null}
{
  "accepted": 1
}
```

## Errors

Every refusal is `{"error": {"code", "message", "field"}}`; `field` is there when one input is at fault.

| Status | Error code | When |
| :- | :- | :- |
| 400 | `invalid_request` | The body isn't a JSON object. |
| 413 | `too_large` | The request is over 256 KB. |
| 401 | `key_retired` | The key is a Customer data source key (`sk_live_…`). Those no longer work here: use an OMNI API key with `cdp.events:write` — see [Moving from source keys](/developers/customer-data#moving-from-source-keys). |
| 403 | `scope_missing` | The key doesn't have the `cdp.events:write` scope. |
| 404 | `module_off` | The account doesn't have Customer data on. |

Any request can also be refused for its key, its account or its rate (`key_required`, `invalid_key`, `account_suspended`, `plan_excludes_api`, `address_not_allowed`, `rate_limited`); see [the overview](/api-reference/overview).


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