Send browser and backend events, pageviews, identity updates, and group traits.
POST https://api.embrasure.ai/collect/v1/batchStart with the JavaScript quickstart, or submit JSON directly. The OpenAPI schema provides the native request and response models.
Send the public project_key in the JSON body. It permits event submission only, and resolves the workspace, project, and environment on the server. Do not send workspace IDs or storage destinations.
Requests that send an Origin header must match one of the project's allowed origins exactly. Backend requests can also send Authorization: Bearer sk_... with an unrevoked server key from the same environment as the project_key to mark their source as trusted. An Authorization header that does not validate returns 401; it does not fall back to an untrusted request. Public keys and browser origins do not authenticate individual users or grant report access. Do not put server keys in browser code.
For untrusted requests, the collector derives context.browser, os, device, and geo from the request and replaces any client-sent values. Trusted requests keep the context they send.
Send Content-Type: application/json. To compress the body, gzip it and send Content-Encoding: gzip; other encodings return 415.
curl --request POST 'https://api.embrasure.ai/collect/v1/batch' \
--header 'Content-Type: application/json' \
--data '{
"project_key": "pk_your_project_key",
"events": [{
"id": "signup_started:request_123",
"type": "track",
"timestamp": "2026-09-15T12:00:00Z",
"anonymous_id": "visitor_123",
"event": "signup_started",
"properties": { "source": "pricing" }
}]
}'Use the actual event time with a timezone and a new ID for every new action. Keep the ID unchanged across retries of that action.
Every message requires id, timestamp, and type:
| Type | Additional required fields | Typical optional fields |
|---|---|---|
track | event and either user_id or anonymous_id | properties, groups, context, session_id |
page | Either user_id or anonymous_id | properties, context, session_id |
identify | user_id | anonymous_id, traits, set_once |
group | group_type, group_key | traits, set_once, actor identity |
Identifiers are nonblank strings up to 200 characters. Custom event names cannot start with $; the SDK's friction signals ($rage_click, $dead_click, $error and $form_abandon) are the only reserved track names accepted. Only track messages take an event name. Properties, context, and traits are JSON objects; group associations map a group type to a group key. Unknown fields are rejected. There are no screen or alias messages. See the schema for the complete model.
properties, context, traits, and set_once, and 20 group associations per event.429 with Retry-After: 60.408, 429, and 5xx with backoff and unchanged event IDs. A partial delivery failure can return 503; stable IDs allow storage to deduplicate the retry.Stored events deduplicate by workspace, project, environment, and event ID. The first stored value wins; sending a changed payload with the same ID does not correct the original event.
Successful native collection returns HTTP 202:
{
"status": "accepted",
"received": 1,
"received_at": "2026-09-15T12:00:01Z",
"request_id": "example-request-id"
}accepted means the collection sink acknowledged the batch. It does not mean warehouse processing is complete or a report can already read it. received is the batch count, not the number of new unique stored events. Responses also include X-Request-ID, X-Embrasure-Collection-Status, and Cache-Control: no-store.
A collector configured to discard events, such as a local development collector, returns HTTP 200 with status: "discarded" and does not persist them.
Errors return detail containing message, code, request_id, and an optional hint. Invalid event contents are not echoed back.
| Status | Meaning |
|---|---|
400 | Invalid Content-Length or gzip stream |
401 | Unknown or revoked project key, collection turned off for the workspace, or invalid server credential |
403 | Browser origin is not allowed |
408 | Request body read timed out |
413 | Request body or decoded JSON exceeds 1 MiB |
415 | Unsupported content type or encoding |
422 | Malformed JSON, invalid event schema, batch or event limit exceeded, or unsupported PostHog event |
429 | Request rate guard reached; honor Retry-After |
503 | Collection disabled (collection_disabled) or delivery temporarily unavailable (Retry-After: 1) |
The collector accepts an analytics subset at /collect/e/, /collect/i/v0/e/, and /collect/batch/. Supported payloads are a single event, a browser event array, or { "api_key": "pk_...", "batch": [...] }, sent as JSON or text/plain, optionally compressed with ?compression=gzip-js. The project key can also come from properties.token or a per-event api_key. Send each event's PostHog uuid: events without one get a generated ID and cannot be deduplicated across retries. These routes return HTTP 200 with the same receipt fields; inspect status to distinguish acceptance from discard mode. Their flexible request bodies are documented here rather than modeled in OpenAPI.
For posthog-js version 1.427.2, use the compatibility helper:
import posthog from "posthog-js";
import { posthogConfig } from "@embrasure/analytics/posthog";
posthog.init("pk_your_project_key", {
...posthogConfig("https://api.embrasure.ai/collect"),
});
posthog.capture("signup_started", { source: "pricing" });
posthog.capture("$pageview");Keep the helper settings intact: they disable unsupported remote configuration, automatic capture, and replay. Explicit custom events, $pageview, $identify, $set, $set_once, and $groupidentify are supported, subject to identity requirements. This is not full PostHog compatibility: alias/merge, anonymous profile updates, screen events, autocapture, historical import, replay, flags, surveys, query/admin APIs, and v1 capture protocol are unsupported. Unsupported reserved events return 422; unsupported endpoints return 404.
The compatibility helper does not apply the native SDK's privacy defaults to PostHog. Apply your application's consent and property-filtering policy. Use the native server SDK when you need a separate server credential.