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

# Cano Analytics Events API — Track and Query Events

> Use the Events API to ingest events server-side and query your event stream by user, name, or time range with cursor-based pagination.

The Events API is the core of Cano Analytics — it lets you ingest events from your servers and query the full event stream by user, event name, or time window. All requests require a valid API key sent as a Bearer token in the `Authorization` header.

***

## POST /v1/events — Track an Event

Send a single event to Cano Analytics. This endpoint requires the **write** scope.

### Request headers

| Header          | Value                   |
| --------------- | ----------------------- |
| `Authorization` | `Bearer <YOUR_API_KEY>` |
| `Content-Type`  | `application/json`      |

### Body parameters

<ParamField body="event" type="string" required>
  The name of the event in `snake_case` format (e.g. `page_viewed`, `order_completed`).
</ParamField>

<ParamField body="user_id" type="string">
  The unique identifier for the authenticated user. Either `user_id` or `anonymous_id` must be present.
</ParamField>

<ParamField body="anonymous_id" type="string">
  A unique identifier for an anonymous user (e.g. a cookie or device ID). Required when `user_id` is not provided.
</ParamField>

<ParamField body="properties" type="object">
  A free-form key-value object containing additional metadata about the event (e.g. `{ "plan": "pro", "amount": 49.00 }`).
</ParamField>

<ParamField body="timestamp" type="string">
  An ISO 8601 timestamp indicating when the event occurred. Defaults to the server's current time if omitted.
</ParamField>

### Request example

```json POST /v1/events theme={null}
{
  "event": "order_completed",
  "user_id": "usr_4f8a1c",
  "properties": {
    "order_id": "ord_9921",
    "amount": 149.00,
    "currency": "USD",
    "plan": "pro"
  },
  "timestamp": "2024-06-15T14:32:00Z"
}
```

### Response — 200 OK

<ResponseField name="id" type="string">
  The unique ID assigned to this event by Cano Analytics.
</ResponseField>

<ResponseField name="status" type="string">
  Always `"received"` for a successful ingest.
</ResponseField>

<ResponseField name="received_at" type="string">
  ISO 8601 timestamp recording when Cano Analytics received the event.
</ResponseField>

```json 200 OK theme={null}
{
  "id": "evt_7kXm29pQrL",
  "status": "received",
  "received_at": "2024-06-15T14:32:01.043Z"
}
```

### Error responses

| Status             | Meaning                                                                                                                                |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`  | The payload is malformed or missing required fields. Check that `event` is present and either `user_id` or `anonymous_id` is provided. |
| `401 Unauthorized` | The API key is missing, expired, or lacks the `write` scope.                                                                           |

***

## POST /v1/events/batch — Batch Track Events

Send up to **1,000 events** in a single HTTP request to reduce network overhead. The batch endpoint accepts the same event shape as the single-event endpoint, wrapped in an `events` array.

### Body parameters

<ParamField body="events" type="array" required>
  An array of event objects. Each object follows the same schema as the single `POST /v1/events` body. Maximum 1,000 items per request.
</ParamField>

### Request example

```json POST /v1/events/batch theme={null}
{
  "events": [
    {
      "event": "page_viewed",
      "user_id": "usr_4f8a1c",
      "properties": { "path": "/pricing" },
      "timestamp": "2024-06-15T14:30:00Z"
    },
    {
      "event": "cta_clicked",
      "user_id": "usr_4f8a1c",
      "properties": { "button": "Start free trial" },
      "timestamp": "2024-06-15T14:30:45Z"
    },
    {
      "event": "signup_completed",
      "user_id": "usr_4f8a1c",
      "properties": { "plan": "pro", "referral": "google" },
      "timestamp": "2024-06-15T14:31:10Z"
    }
  ]
}
```

### Response — 200 OK

```json 200 OK theme={null}
{
  "received": 3,
  "failed": 0
}
```

<Note>
  If some events in the batch are invalid, Cano Analytics still ingests the valid ones and reports the count of failures. Inspect the optional `errors` array in the response for details on which items failed.
</Note>

***

## GET /v1/events — List Events

Query your event stream with optional filters. This endpoint requires the **read** scope and returns results in reverse-chronological order with cursor-based pagination.

### Query parameters

<ParamField query="event" type="string">
  Filter results to a specific event name (e.g. `event=order_completed`).
</ParamField>

<ParamField query="user_id" type="string">
  Filter results to events belonging to a specific user.
</ParamField>

<ParamField query="from" type="string">
  ISO 8601 timestamp. Return only events that occurred at or after this time.
</ParamField>

<ParamField query="to" type="string">
  ISO 8601 timestamp. Return only events that occurred at or before this time.
</ParamField>

<ParamField query="limit" type="integer">
  Number of events to return. Defaults to `100`. Maximum is `1000`.
</ParamField>

<ParamField query="cursor" type="string">
  Opaque pagination cursor returned in the previous response's `next_cursor` field. Pass this to retrieve the next page.
</ParamField>

### Request example

```bash cURL theme={null}
curl -G https://api.canoanalytics.io/v1/events \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-urlencode "event=order_completed" \
  --data-urlencode "user_id=usr_4f8a1c" \
  --data-urlencode "from=2024-06-01T00:00:00Z" \
  --data-urlencode "limit=2"
```

### Response fields

<ResponseField name="data" type="array">
  An array of event objects matching your query filters, each containing `id`, `event`, `user_id`, `properties`, and `timestamp`.
</ResponseField>

<ResponseField name="next_cursor" type="string">
  An opaque string to pass as `cursor` in your next request to retrieve the following page. `null` when no more pages exist.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  `true` if additional pages of results are available beyond the current response.
</ResponseField>

```json 200 OK theme={null}
{
  "data": [
    {
      "id": "evt_7kXm29pQrL",
      "event": "order_completed",
      "user_id": "usr_4f8a1c",
      "properties": {
        "order_id": "ord_9921",
        "amount": 149.00,
        "currency": "USD"
      },
      "timestamp": "2024-06-15T14:32:00Z"
    },
    {
      "id": "evt_3hWn01mBkA",
      "event": "order_completed",
      "user_id": "usr_4f8a1c",
      "properties": {
        "order_id": "ord_8810",
        "amount": 49.00,
        "currency": "USD"
      },
      "timestamp": "2024-06-03T09:17:22Z"
    }
  ],
  "next_cursor": "cur_eyJvZmZzZXQiOjJ9",
  "has_more": true
}
```
