# JSON Schema (https://www.getfounderhq.com/docs/analytics/protocol-reference/json-schema)

The Events v2 batch has a published JSON Schema. Point a validator at it
and you find a malformed payload on your own machine, before you spend a
request finding it in production.

**Download:** [`/protocol/events-v2.schema.json`](/protocol/events-v2.schema.json)

It is draft 2020-12. Its `$id` is
`https://www.getfounderhq.com/schemas/events-v2.json`.

## What the schema covers

| Rule | Enforced |
| --- | --- |
| `sent_at` and `batch` are required, and nothing else is allowed at the top level | yes |
| A batch holds 1 to 100 events | yes |
| Every event requires `uuid`, `event`, `distinct_id`, `timestamp`, `properties`, and `options` | yes |
| Cookieless events require `distinct_id: "$founderhq_cookieless"` and both cookieless options | yes |
| Cookieless properties match the allowlist, with their length and range limits | yes |
| A cookieless `$pageview` cannot carry page-leave properties | yes |

Two rules live in the API, not the schema, because they need context:

- `$` event names must be on the reserved allowlist.
- Key permissions and the server-side cookieless capability gate are enforced
  per event.

## Validate your payload

Add [ajv](https://ajv.js.org), then check a batch before you send it.

```js
import Ajv from "ajv";
import addFormats from "ajv-formats";

const schema = await fetch("https://www.getfounderhq.com/docs/protocol/events-v2.schema.json").then((r) => r.json());
const validate = addFormats(new Ajv({ strict: false })).compile(schema);
if (!validate(batch)) console.error(validate.errors);
```

ajv is the only thing you add. The schema itself has no dependencies and
no remote references, so you can vendor the file into your repository and
validate offline.

<Callout title="Passing the schema is not the same as being accepted">
  The schema proves the shape is right. FounderHQ still checks your key,
  your event names, and your distinct IDs at ingest. Read
  [Wire protocol](/analytics/protocol-reference/envelope) for the per-event
  result codes.
</Callout>
