# Server-side ingest API

> Send pageviews and custom events from your backend with the server-side ingest API: the endpoint, ingest key authentication, event fields, limits and responses.

Send events from your server when the browser can't or shouldn't: payment
webhooks, signups confirmed by email, API products, backend jobs, or
pages served to clients without JavaScript.

## Endpoint

```http
POST https://privatusanalytics.com/api/events
Authorization: Bearer pik_…
Content-Type: application/json
```

```json
{
  "events": [
    {
      "type": "event",
      "name": "Purchase",
      "url": "https://example.com/checkout",
      "revenue": 49.99,
      "currency": "USD",
      "props": { "plan": "pro" },
      "user_agent": "Mozilla/5.0 (Macintosh; …) Safari/605.1.15",
      "ip": "203.0.113.7"
    }
  ]
}
```

Authenticate with the site's secret **ingest key** (`pik_…`), either as a
Bearer token or in an `X-Ingest-Key` header. Each key belongs to one site.
See [Ingest keys](/docs/server-side/ingest-keys).

## Event fields

| Field | Required | Description |
|---|---|---|
| `type` | Yes | `pageview` or `event` (`custom` is an alias). `engagement` and `vital` are also accepted but need an existing visit |
| `name` | For events | Event name, up to 120 characters |
| `url` | Yes | Full `http(s)` URL of the page. Its hostname must be one of the site's domains (or a subdomain of one) |
| `referrer` | No | Where the visitor came from, which drives sources and channels |
| `props` | No | Up to 30 [properties](/docs/events/properties) |
| `revenue` | No | Amount in major units, e.g. `49.99` ([Revenue](/docs/events/revenue)) |
| `currency` | No | ISO 4217 code. Defaults to the site currency |
| `timestamp` | No | ISO 8601. Must be within the last **72 hours**. Otherwise (or if missing or in the future) the time of receipt is used |
| `user_agent` | In practice, yes | The **visitor's** browser User-Agent. Missing or library User-Agents are dropped as bots (see below) |
| `ip` | No | The **visitor's** IP. Used in memory for the country and the daily visit key, then dropped. Without it there's no location |
| `session_hint` | No | An opaque string that groups your server events into one visit. See [Sessions](/docs/server-side/sessions) |
| `language` | No | e.g. `en-US` |
| `screen_width` | No | Pixels, stored as a size bucket |

The short keys the browser tracker uses (`t`, `n`, `u`, `r`, `p`…) are
accepted too. See [What the tracker sends](/docs/tracker/payload).

## Limits

- Up to **100 events** per request. More returns `422`.
- The request body must be at most **32 KB**. Larger bodies are read as
  empty. Split big batches.
- Everything else (property limits, name length, PII scrubbing, path
  masking, traffic rules, bot filtering) works exactly as for browser hits.

## Response

`202 Accepted` with one result per event, in order:

```json
{
  "data": [
    { "index": 0, "status": "accepted", "reason": null },
    { "index": 1, "status": "dropped", "reason": "bot_automation" }
  ]
}
```

`accepted` means the event was queued and will appear in the dashboard
within seconds. `dropped` events aren't stored and don't count toward
usage. Reasons:

| Reason | Meaning |
|---|---|
| `bad_type` | `type` isn't one of the accepted values |
| `bad_url` | `url` is missing or not an `http(s)` URL |
| `foreign_host` | The URL's hostname isn't one of the site's domains |
| `privacy_signal` | The event carried DNT/GPC (`dnt`/`gpc` fields) and the site honours it |
| `bot_empty_user_agent` | No `user_agent` |
| `bot_known_bot` | A known crawler User-Agent |
| `bot_automation` | An automation or HTTP-library User-Agent (see [Bot rules](/docs/server-side/bots)) |
| `bot_datacenter` | The `ip` belongs to a cloud or hosting network |
| `bot_referrer_spam` | The referrer is on the referrer-spam list |
| `rule_<list>` | Blocked by a [traffic rule](/docs/privacy/traffic-rules), e.g. `rule_paths` |
| `vital_sampled` | A Web Vital outside the sample rate |
| `no_visit` | An engagement or vital hit with no visit to attach to |
| `paused` | Collection is paused for the workspace by Privatus Analytics support |
| `limit_reached` | The workspace reached its monthly event limit, so pageviews and custom events are not recorded until the reset or an upgrade (see [Monthly limit](/docs/billing/overage)) |

Errors:

| Status | Body `error.code` | When |
|---|---|---|
| `401` | `unauthorized` | Missing or unknown ingest key |
| `422` | `validation_failed` | More than 100 events, or `events` isn't an array |

## Next

- [Examples](/docs/server-side/examples) in curl, Node, Python, PHP, Go and
  Ruby.
- [SDKs](/docs/server-side/sdks) that batch, retry and handle timestamps
  for you.
- The browser's [no-JavaScript pixel](/docs/install/pixel) for pages
  without JavaScript.
