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

# Track Events API

> Send pageviews and custom events to POST /api/track as typed JSON; the endpoint validates and queues each event, then returns 204.

`POST /api/track` is the ingestion endpoint every event flows through — the browser [tracking script](/docs/install-tinyanalytics-tracking-script) uses it, and you can call it directly from a server to send events yourself. The body is a single JSON event, and the endpoint returns **`204 No Content`**: the event is validated and queued, then written asynchronously.

```
POST https://dash.tinyanalytics.io/api/track
```

## The event shape

Every event carries a `type` and the page context tinyanalytics can't derive on its own. The server always derives identity, geo, channel, and device itself — **it never trusts the client for those** — so you send page context only.

Send a **pageview**:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dash.tinyanalytics.io/api/track \
    -H "Content-Type: application/json" \
    -d '{
      "type": "pageview",
      "siteId": 1,
      "pathname": "/pricing",
      "hostname": "example.com",
      "referrer": "https://google.com",
      "pageTitle": "Pricing"
    }'
  ```

  ```js JavaScript theme={null}
  await fetch("https://dash.tinyanalytics.io/api/track", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      type: "pageview",
      siteId: 1,
      pathname: "/pricing",
      hostname: "example.com",
      referrer: "https://google.com",
      pageTitle: "Pricing",
    }),
  })
  ```

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

  requests.post(
      "https://dash.tinyanalytics.io/api/track",
      json={
          "type": "pageview",
          "siteId": 1,
          "pathname": "/pricing",
          "hostname": "example.com",
          "referrer": "https://google.com",
          "pageTitle": "Pricing",
      },
  )
  ```
</CodeGroup>

Send a **custom event** with properties:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dash.tinyanalytics.io/api/track \
    -H "Content-Type: application/json" \
    -d '{
      "type": "custom_event",
      "siteId": 1,
      "pathname": "/checkout",
      "eventName": "purchase",
      "props": { "plan": "pro", "seats": 5 },
      "revenue": 49.0,
      "currency": "USD"
    }'
  ```

  ```js JavaScript theme={null}
  await fetch("https://dash.tinyanalytics.io/api/track", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      type: "custom_event",
      siteId: 1,
      pathname: "/checkout",
      eventName: "purchase",
      props: { plan: "pro", seats: 5 },
      revenue: 49.0,
      currency: "USD",
    }),
  })
  ```

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

  requests.post(
      "https://dash.tinyanalytics.io/api/track",
      json={
          "type": "custom_event",
          "siteId": 1,
          "pathname": "/checkout",
          "eventName": "purchase",
          "props": {"plan": "pro", "seats": 5},
          "revenue": 49.0,
          "currency": "USD",
      },
  )
  ```
</CodeGroup>

## Fields

Every event includes these base fields:

<ParamField body="type" type="string" required>
  The event type. Valid values are listed below.
</ParamField>

<ParamField body="siteId" type="number" required>
  Your numeric site ID.
</ParamField>

<ParamField body="pathname" type="string" required>
  The page path, up to 2048 characters.
</ParamField>

<ParamField body="hostname" type="string">
  Up to 253 characters.
</ParamField>

<ParamField body="querystring" type="string">
  Up to 2048 characters.
</ParamField>

<ParamField body="referrer" type="string">
  Up to 2048 characters.
</ParamField>

<ParamField body="pageTitle" type="string">
  Up to 512 characters.
</ParamField>

<ParamField body="language" type="string">
  Up to 35 characters.
</ParamField>

<ParamField body="screenWidth" type="number">
  Viewport width in pixels, 0–65535.
</ParamField>

<ParamField body="screenHeight" type="number">
  Viewport height in pixels, 0–65535.
</ParamField>

<ParamField body="userId" type="string">
  A stable ID set via [identify](/docs/api-reference/identify-users-api).
</ParamField>

A **`custom_event`** (and an `error`) adds:

<ParamField body="eventName" type="string" required>
  Up to 256 characters — the grouping key in your Events reports.
</ParamField>

<ParamField body="props" type="object">
  Free-form JSON, up to 8 KB serialized.
</ParamField>

<ParamField body="revenue" type="number | string">
  A revenue amount in major currency units (e.g. `49.0` = \$49.00). See [Revenue analytics](/docs/revenue-analytics).
</ParamField>

<ParamField body="currency" type="string">
  A 3-letter currency code, e.g. `USD`.
</ParamField>

<Note>
  Valid `type` values are `pageview`, `custom_event`, `error`, `performance`, `engagement`, and the auto-captured interactions (`outbound`, `file_download`, `button_click`, `copy`, `form_submit`, `input_change`). The browser tracker emits most of these for you — from a server you'll typically send `pageview` and `custom_event`. Unknown fields are rejected with a `400`.
</Note>

## Sending events from a server

When you call `/api/track` from your backend, add an [API key](/docs/api-reference/api-authentication) so the event is attributed to the **visitor**, not your server:

```bash theme={null}
curl -X POST https://dash.tinyanalytics.io/api/track \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "custom_event",
    "siteId": 1,
    "pathname": "/api/webhook",
    "eventName": "subscription_renewed",
    "ipAddress": "203.0.113.10",
    "userAgent": "Mozilla/5.0 (Macintosh; …)"
  }'
```

With a valid key, tinyanalytics honors the `ipAddress` and `userAgent` you supply so geo, device, and cookieless identity reflect the real visitor — without them, every server-sent event would collapse to your server's own IP. **These override fields are ignored on a request without a valid key.** Server-side ingestion is subject to a [per-key rate limit](/docs/api-reference/rate-limits-cors).

## Related

<Columns cols={2}>
  <Card title="Identify API" icon="user" href="/docs/api-reference/identify-users-api">
    Attach a stable user ID to these events.
  </Card>

  <Card title="API keys" icon="key" href="/docs/api-reference/api-authentication">
    Create the key server-side ingestion needs.
  </Card>

  <Card title="Rate limits & CORS" icon="shield-halved" href="/docs/api-reference/rate-limits-cors">
    The ingestion rate limit and origin rules.
  </Card>

  <Card title="Track custom events" icon="hand-pointer" href="/docs/custom-event-tracking">
    The browser way to send the same events.
  </Card>
</Columns>


## Related topics

- [tinyanalytics API Reference](/docs/api-reference/tinyanalytics-api.md)
- [Custom Event Tracking](/docs/custom-event-tracking.md)
- [JavaScript Error Tracking](/docs/javascript-error-tracking.md)
