Reference6 min read

HTTP ingest API

There is one ingest endpoint, and it is built to receive a genuine browser beacon. It is documented here so you can debug what your own pages are sending, not so you can send to it yourself.

There is no server-side tracking API

Mrkr has no backend event API, no API key, and no supported way to post events from a server, a script, or curl. Requests that don't look like a real browser are classified as bots and discarded. Read What this is not before you build anything against this endpoint.

The endpoint

POST /api/collect (aliased as /api/data on a managed proxy subdomain). The body is JSON. The tracker sends it as Content-Type: text/plain, which makes the request a CORS simple request and skips the preflight round trip; the server parses the body as JSON whatever the declared type. CORS is open (the endpoint replies with Access-Control-Allow-Origin: *) because the whole point is accepting cross-origin beacons from whatever domain a customer's site runs on.

The envelope

One request carries a batch. The identity and browser-context fields sit once on the envelope, and each event in events carries its own name, props, and path:

FieldTypeRequiredNotes
sitestringYesSite id. Missing or empty returns 400.
eventsarrayNoUp to 25 entries per request; anything beyond that is discarded. Each entry takes event, props, path, url, t.
eventstringNoSingle-event shorthand, used when events is absent or empty. Defaults to "pageview".
propsobjectNoSanitized with the same 8-key / 256-char rules track() uses client-side.
sessionstringNoWithout it, the event still passes the bot gate and updates rollup counters, but skips the sessions upsert and the events-table mirror entirely.
client_idstringNoCookie-mode persistent visitor id. Ignored on a cookieless site.
path, url, referrer, utmstring / objectNoUsed for page/source classification. A per-entry path wins over the envelope's.
tz, lang, screen, uastringNoBrowser context; tz also feeds the geo/timezone bot check.
wd, pr, nl, vp, nbbooleanNoClient-side bot signals (webdriver, prerendering, empty languages, zero viewport, empty UA-data brands). Omitting them just means those specific checks can't fire.
Five event names are refused outright

click_link, click_button, submit_form, select_change and toggle_change are the retired autocapture names. They are dropped before any write, with no rollup, no event row, and no session marker consumed, so a tracker still cached in a visitor's browser cannot keep writing click data. Do not reuse these names for your own events.

What this is not

Server-side event ingestion is not a supported feature

There is no API key, no server-side authentication, and no documented, stable contract for posting events from a backend process. The gate that decides whether a request counts as real traffic assumes a genuine browser: it requires an Origin header that resolves to the site's configured domain, a Sec-Fetch-Site header (which only real browsers attach to fetch/sendBeacon requests), and a User-Agent that starts with Mozilla/5.0 and passes header-consistency and pattern checks. A plain POST from a server, a script, or curl fails these by construction and is classified and discarded as a bot, silently, with a 200 response that looks successful.

Do not try to work around the gate. There is no authentication model, no stability commitment on the payload contract, and no rate-limit tier designed for backend traffic, so anything built that way will break without notice and its numbers will not be trustworthy in the meantime. If your product needs backend-originated events, tell us and we will treat it as the feature request it is.

Response shape

SituationHTTP statusBody
Accepted200{ ok: true, accepted: n, replay: { enabled, sample_rate } }
Classified as a bot200{ ok: true, bot: true, reason }
Every event in the batch was on an excluded path200{ ok: true, excluded: true }
Every event in the batch was a retired autocapture name200{ ok: true, dropped: "autocapture" }
Hard pageview quota exceeded (grandfathered free plan only)200{ ok: true, quota: "exceeded" }
Malformed JSON or missing site400{ ok: false, error }
Unknown site404{ ok: false, error: "unknown site" }
Heads up

Every rejection short of a malformed request or an unknown site still returns HTTP 200. The tracker is designed to never retry a rejected event, so a 200 does not mean the event was stored. If you're probing this endpoint directly, you have to read the body, not just the status code.

For the client-side API this endpoint exists to serve, see the JavaScript API reference. For what triggers a bot classification, see bots and spam.