# Standalone tag — ingest schema (v0003)

Contract for batches posted by
[`webmcp.js`](https://files.bread.net.ai/standalone/webmcp.js).
Use this when you set `data-ingest-url` (or `?ingest=` /
`window.__BREAD_INGEST_URL__`) to your own endpoint.

## Transport

- **Method:** `POST`
- **Body:** one JSON object (a *batch*)
- **Client:** prefers `navigator.sendBeacon`; may fall back to `fetch`
  with `Content-Type: application/json`
- **CORS:** allow `POST` from your site’s origin (or proxy same-origin)

Default destination when unset: `https://ingest.bread.net.ai`.

## Envelope

```json
{
  "sessionId": "qaseusw4pvsmrz85ovj",
  "pageUrl": "/pricing",
  "siteInfo": {
    "trackerVersion": "0003",
    "accountId": "",
    "href": "https://example.com/pricing",
    "origin": "https://example.com",
    "hostname": "example.com",
    "pathname": "/pricing",
    "referrer": ""
  },
  "init": { },
  "events": [ ]
}
```

| Field | Required | Notes |
| --- | --- | --- |
| `sessionId` | yes | Stable for the tab session (cookie-backed when cookies work) |
| `pageUrl` | yes | Pathname at batch time |
| `siteInfo` | yes | Always includes `trackerVersion: "0003"` |
| `siteInfo.accountId` | no | From `data-account-id` on the script tag |
| `init` | first batch only | Environment snapshot (see below); omit on later flushes |
| `events` | yes | Array of behavioral and/or `webmcp_tool_call` events |
| `labels` | no | Usually absent on the standalone tag |

Treat unknown top-level keys as forward-compatible; ignore what you do not store.

## Behavioral events

Emitted by the tracker into `events[]`. Common shapes:

| `type` | Typical fields |
| --- | --- |
| `mousemove` | `x`, `y`, `t` |
| `mousedown` / `mouseup` / `click` | `x`, `y`, `t` (+ click target hints when present) |
| `scroll` | `scrollY`, `t` |
| `keydown` / `keyup` | `key`, `t` |
| `visibility` | `state`, `t` |
| `unload` | `t` |

`t` is a millisecond timestamp. Batches flush on a timer, size threshold, or page unload.

## `init` (first batch only)

A **session-start snapshot**, not a live stream. Roughly ~3 KB. Groups include:

- **identity** — `userAgent`, `vendor`, `platform`, languages, `uaData` / `uaDataHigh`
- **hardware** — concurrency, memory, screen / window metrics
- **webgl** / **canvas** — renderer strings and small hashes
- **automation** — `webdriver`, chrome object probes, plugin counts, known leaks
- **permissions** / **locale** / **performance** / **misc** — as available in the browser

Async fields may be `null` or `{ "_error": "timeout" }` if they miss a short deadline. Values that change later (resize, etc.) are not re-sent.

## WebMCP tool calls

When an agent (or DevTools) invokes a registered tool, one event is appended:

```json
{
  "type": "webmcp_tool_call",
  "tool": "get_page_content",
  "args": {},
  "resultStatus": "ok",
  "ts": 1712700000123
}
```

| Field | Required | Notes |
| --- | --- | --- |
| `type` | yes | Always `webmcp_tool_call` |
| `tool` | yes | Catalog name (`navigate`, `click`, …) |
| `args` | yes | Object (tool input) |
| `resultStatus` | no | `ok` or `error` |
| `ts` | no | Handler time (ms) |

Same envelope and `sessionId` as behavioral traffic, so you can join clicks and tool use in one stream.

## Minimal receiver

Accept `POST`, parse JSON, require `sessionId` + `events[]`, persist the batch, return `2xx`. Rejecting malformed bodies with `4xx` is fine; the client does not retry aggressively.

## Session replay (rrweb) — not in this tag

This build does **not** capture rrweb. There is no `ENABLE_RRWEB` switch on the script or on `data-ingest-url`.

If you want replay yourself: load [rrweb](https://github.com/rrweb-io/rrweb) (or similar) beside the tag, `record()` into your own store, and correlate with `sessionId` from `window.__trackerSessionId`. Keep replay payloads on a separate path from the v0003 batch URL — the shapes differ (packed event chunks vs. the envelope above).

## Tag reminder

```html
<script
  src="https://files.bread.net.ai/standalone/webmcp.js"
  data-ingest-url="https://your.example/ingest"
  data-account-id="optional-tenant"
  async
></script>
```
