Skip to content
Everframe Docs
Documentation

The AI-ready envelope

One JSON document per report, identical on every platform. If you are writing a receiver, this is the contract.

Updated

Every report — from a React web app, an iPhone, or a Roku box — arrives in the same shape. That is the whole point: your triage prompts and integrations are written once.

{
  "protocolVersion": "1.0",
  "reportId": "<uuid>",
  "submittedAt": "2026-06-15T12:00:00.000Z",
  "sdk": { },
  "reporter": { },
  "captures": { },
  "captureControl": { },
  "payload": { },
  "context": { },
  "attachments": []
}

Top level

FieldWhat it is
protocolVersionSchema version. Currently "1.0".
reportIdUUID. Stable across retries — this is the idempotency key.
submittedAtISO-8601 submission timestamp.
sourceHow the report was filed: manual, crash or error. Optional — absent means manual.
sdkname (everframe-react, everframe-web, everframe-react-native, everframe-ios, everframe-android), version, platform (web, ios, android, tvos, androidtv, tizen, webos) and formFactor (phone, tablet, desktop, tv).
reporterWhat the user typed: title and description, plus optional user. Protocol caps are 50 / 600 characters, but ingest clips the description to 400 — see Errors & limits.
capturesBooleans: which capture types were enabled for this report.
captureControlWhat was actually included / excluded, plus degradedReason when a capture was enabled but could not run.
payloadThe captured artifacts.
contextApp, device and route.
attachmentsReferences to the binary parts.

captures and captureControl are not redundant. The first is intent, the second is outcome — a receiver can tell “replay was off” apart from “replay was on but the buffer was empty”, which is the difference between a config problem and a bug.

payload

FieldWhat it is
breadcrumbsThe action timeline. Up to 128 kind-tagged entries on one epoch-ms clock, shared with the replay.
networkBodiesRequest/response bodies, joined to network crumbs by ref === crumb.data.reqId. Off unless enabled.
resourcesCPU/memory samples from the seconds before the report, one entry per sample tick. Off unless enabled — see Server config. cpu is absent on web (no browser API exposes it).
annotationsThe user’s drawings on the screenshot.
redactionsRegions the user blurred.
crashException details. Present only when the top-level source is crash or error.
extraFree-form metadata you attached — a string, or an object the SDK serialises. ≤ 16 384 chars (16 KiB).
logs / networkDeprecated. Derived from breadcrumbs; read breadcrumbs instead.
{
  "t": 1765800000123,
  "seq": 42,
  "kind": "network",
  "level": "error",
  "message": "POST /api/cart",
  "data": {
    "reqId": 17,
    "status": 500
  }
}

t (epoch ms) and seq (a per-session tiebreaker) are required and numeric. kind is one of navigation, tap, console, network, lifecycle, error, custom; level, when present, is debug, info, warn or error. message is capped at 2 048 characters; truncated: true marks a message that was clipped.

data.reqId on a network crumb is a number, and it is what payload.networkBodies[].ref joins to.

The chain is capped at 128 entries — 100 by config plus headroom for the per-kind trim markers, which tell you that entries were dropped rather than silently losing them. A trim marker is an ordinary crumb of the trimmed kind whose data.droppedCount says how many were dropped.

context

{
  "app": {
    "name": "Acme",
    "version": "2.4.0",
    "build": "1182"
  },
  "device": {
    "os": "iOS",
    "osVersion": "18.2",
    "model": "iPhone15,3",
    "screenSize": {
      "width": 393,
      "height": 852
    },
    "pixelRatio": 3,
    "locale": "en-GB",
    "timezone": "Europe/Vilnius"
  },
  "route": "/checkout"
}

device.userAgent is present on web only — native SDKs omit it.

attachments

Binary parts live outside the JSON and are referenced from it:

{
  "partName": "screenshot",
  "kind": "screenshot",
  "contentType": "image/png",
  "byteLength": 84213,
  "sha256": "<hex>",
  "width": 1170,
  "height": 2532
}

kind is screenshot, annotated-screenshot, video, audio, session-replay or other. width, height and durationMs are optional. Replay attachments carry a format discriminator — rrweb on web, everframe-vtree-v1 natively — so a player knows what it is holding without sniffing bytes.

Always verify sha256 before trusting an attachment. The ingest API verifies it on the way in and will reject a mismatch, but a receiver that writes attachments to storage should check it too.

In a webhook delivery each reference also carries a presigned url and expiresAt, or a marker explaining why it does not — see Webhooks.

Compatibility

The envelope is additive. New optional fields appear without a version bump, so a receiver must ignore unknown fields rather than reject them — every object in the schema is a passthrough. A field is only ever removed behind a protocol version change, and deprecated fields are kept alive for a full deprecation window first, as logs and network are now.

The one place that is strict is the envelope root at ingest: an unknown top-level key is rejected with schema_validation_failed. Nested objects stay open, so a custom client may add fields inside payload or context, but not beside them.