---
title: "Events API"
description: "Ingest event batches with sdk.events.v1.EventsService/BatchCreate — full request schema, property wrapping and validation rules."
---

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

# Events API

Ingest events from SDKs and server-side integrations using `sdk.events.v1.EventsService/BatchCreate`.

**Auth:** `x-api-key` header (public key `pub_…` or private key `prv_…`). No `Authorization` header or `x-project-id` is required — the project is resolved from the key. See [Authentication](/get-started/authentication).

## `BatchCreate`

```
POST /sdk.events.v1.EventsService/BatchCreate
```

Validates and enqueues up to 1 000 events in a single request. Returns the count of accepted events. There is no single-event endpoint — all ingestion goes through this method.

All `google.protobuf.Timestamp` fields (here, `occurTime`, and `timestampValue` inside a `PropertyValue`) serialize as **RFC 3339 strings** — e.g. `"2026-06-05T10:00:00Z"`.

## `Event` fields

| Proto field | JSON name | Type | Required | Notes |
|---|---|---|---|---|
| `event_id` | `eventId` | string | Yes | UUID (any version; UUIDv7 recommended for time-sortability) |
| `auto_properties` | `autoProperties` | `map<string, PropertyValue>` | No | Context set by the SDK or server. Keys must start with `$`. `$bot_score` and `$verified_bot` are server-only — any client-supplied value is stripped and replaced from CDN headers |
| `custom_properties` | `customProperties` | `map<string, PropertyValue>` | No | Your event properties. Keys must not start with `$` |
| `distinct_id` | `distinctId` | string | Yes | Anonymous or identified user ID |
| `kind` | `kind` | string | Yes | Event name. Must not start with `pug.` (reserved prefix) |
| `occur_time` | `occurTime` | `google.protobuf.Timestamp` → RFC 3339 | Yes | When the event occurred |
| `session_id` | `sessionId` | string | Yes | UUID grouping events in a visit |

Proto field names (snake_case) and lowerCamelCase are both accepted by the Connect JSON codec. The lowerCamelCase forms shown above are canonical.

## `PropertyValue` — typed property wrapper

Every value in `customProperties` and `autoProperties` is a wrapped object that names its type explicitly. This is the Connect JSON encoding of the protobuf `oneof`.

| Variant | JSON shape | Notes |
|---|---|---|
| String | `{ "stringValue": "hello" }` | Max 1 024 Unicode code points. Values exceeding the limit are rejected with `invalid_argument` (not truncated) |
| Integer | `{ "intValue": "42" }` | int64 — the value is a **JSON string**, not a number |
| Double | `{ "doubleValue": 3.14 }` | Must be finite (NaN and ±Infinity are rejected) |
| Boolean | `{ "boolValue": true }` | |
| Timestamp | `{ "timestampValue": "2026-06-05T10:00:00Z" }` | RFC 3339; stored at millisecond precision |

Exactly one variant must be set per `PropertyValue` object. Proto snake_case aliases (e.g. `string_value`) are accepted as well.

## Validation summary

All rules above enforced together; any violation fails the request with `invalid_argument`:

- **Batch size** — at most 1 000 events per `BatchCreate` call.
- **`eventId`, `sessionId`** — must be valid UUIDs.
- **`$bot_score`, `$verified_bot`** — server-only; any client-supplied value is dropped and re-set from the CDN-injected `CF-Bot-Score`/`CF-Verified-Bot` headers.

(Property-key prefix rules, `stringValue` length, and `doubleValue` finiteness are covered in the [`Event` fields](#event-fields) and [`PropertyValue`](#propertyvalue--typed-property-wrapper) tables above.)

## Request / response shape

> **Property values are typed `PropertyValue` wrappers.** Every value in `customProperties` and `autoProperties` names its type explicitly — `intValue` (int64) must be a JSON **string**, doubles are bare numbers, etc. (see the table above). This is **not** how profile traits work: Identify's `traits` are plain JSON, **not** `PropertyValue`-wrapped — see [Profiles](/api/profiles).

### `BatchCreateRequest`

```json
{
  "events": [
{
  "eventId":   "<uuid>",
  "distinctId": "anon-abc123",
  "kind":       "page_view",
  "sessionId":  "<uuid>",
  "occurTime":  "2026-06-05T10:00:00Z",
  "customProperties": {
    "plan":  { "stringValue": "pro" },
    "seats": { "intValue": "5" },
    "mrr":   { "doubleValue": 299.0 },
    "trial": { "boolValue": false }
  },
  "autoProperties": {}
}
  ]
}
```

### `BatchCreateResponse`

```json
{ "accepted": 1 }
```

`accepted` (`uint32`) is the number of events that passed validation and were enqueued. It equals the number of events in the request when all events are valid.

## Errors

These are the codes `BatchCreate` returns in practice — a subset of the full
[error model](/api#error-model):

| Connect code | HTTP status | Cause |
|---|---|---|
| `unauthenticated` | 401 | Missing or invalid `x-api-key` |
| `invalid_argument` | 400 | Validation failed — see the `message` field for which rule |
| `resource_exhausted` | 429 | Rate limit exceeded |
| `internal` | 500 | Server error — retry with exponential backoff |

Error responses follow the Connect envelope:

```json
{
  "code": "invalid_argument",
  "message": "event kind must not use reserved prefix 'pug.'"
}
```

## Further reading

- [HTTP transport](/sdks#errors-and-the-raw-http-api) — full curl, Python, and Go examples for `BatchCreate`
- [Auto-properties](/reference/auto-properties) — the `$`-prefixed property catalog set by the server
- [Authentication](/get-started/authentication) — public vs. private keys, key rotation

Source: https://docs.pug.sh/api/events/index.mdx
