MIRA FIVE

Send events

Send a batch of events to MIRA FIVE with one HTTP request, from a server with the secret key or from a browser with the website key.

Events travel in batches: one JSON object with up to 1,000 events, sent with POST /v1/batch from a server (secret key as a bearer) or POST /v1/batch/{websiteKey} from a browser (website key in the path). Both take the same body and answer 202 with a receipt. Keys and CORS are on the Overview.

Send from a server

Put the secret key of a server source in MIRAFIVE_SECRET_KEY and send the batch as JSON:

curl -i https://events.mirafive.io/v1/batch \
  -H "Authorization: Bearer $MIRAFIVE_SECRET_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @- <<EOF
{
  "v": 1,
  "batch": "$(uuidgen)",
  "mode": "full",
  "context": { "sdk": "acme-importer/1.0.0" },
  "events": [
    {
      "name": "signup",
      "time": $(date +%s)000,
      "userId": "u_42",
      "properties": { "plan": "pro" }
    }
  ]
}
EOF
202 Accepted
{ "batch": "3b1f0e2a-8c4d-4e7f-9a6b-5c2d1e0f9a8b", "accepted": 1, "dropped": 0 }

The shell fills in a new batch id and the current time in epoch milliseconds. Write a $ in an event name as \$ inside this heredoc ("\$identify"), or the shell expands it.

Send from a browser

Put the website key of a website source in the path and send the body as text/plain, so the browser sends no preflight:

curl -i https://events.mirafive.io/v1/batch/$MIRAFIVE_WEBSITE_KEY \
  -H 'Content-Type: text/plain;charset=UTF-8' \
  -H 'Origin: https://shop.example' \
  -d '{"v":1,"batch":"0192d4a8-7b1c-4e8a-9c1d-2b3e4f5a6b7c","mode":"consentless","events":[{"name":"$pageview","page":{"url":"https://shop.example/pricing","title":"Pricing"}}]}'
202 Accepted
{ "batch": "0192d4a8-7b1c-4e8a-9c1d-2b3e4f5a6b7c", "accepted": 1, "dropped": 0 }

https://shop.example stands for one of the source's allowed origins. In a page, navigator.sendBeacon() with a string body sends exactly this request, and keeps working while the tab closes:

beacon.ts
const batch = {
  v: 1,
  batch: crypto.randomUUID(),
  mode: 'consentless',
  sentAt: Date.now(),
  events: [
    {
      name: '$pageview',
      time: Date.now(),
      page: { url: location.origin + location.pathname, title: document.title, referrer: document.referrer },
    },
  ],
}

navigator.sendBeacon('https://events.mirafive.io/v1/batch/mf_…', JSON.stringify(batch))

A client you write for browsers sends nothing when navigator.doNotTrack is "1", navigator.globalPrivacyControl is true, the page is prerendering, or the host is localhost, 127.*, [::1], *.local or a file: URL. The browser SDK does all of this; use it where you can.

The batch

NameTypeDefaultDescription
vinteger—Required. The protocol version, 1.
batchstring—Required. A UUID (any version), new for every batch and kept for its retries. It is the idempotency key: see Idempotency.
modestring—Required. "consentless" or "full". See Collection modes.
sentAtinteger—Epoch milliseconds when the request left the device. Stored with every event, and used as the time of events without time.
contextobject—Shared by every event of the batch. See Context.
eventsarray—Required. 1 to 1,000 events.

Integers must be JSON integers: "1727430000000" or 1727430000000.5 is refused. null in an optional field counts as absent. Fields the protocol does not define are ignored.

Events

NameTypeDefaultDescription
namestring—Required. 1 to 128 characters, no leading or trailing whitespace. A leading $ is reserved: see Reserved event names.
timeintegersentAt, else the time receivedEpoch milliseconds when it happened. A time more than 5 minutes ahead of the server or more than 30 days behind it is replaced by the time received.
idstringderivedA UUID, unique within the batch. Without one, the server derives a stable id from batch, the event's index and time, so a resent batch gets the same ids.
pageobject—url up to 2,048, title up to 512, referrer up to 2,048 characters. See Clean page URLs.
propertiesobject—The event's data. See Properties.
anonymousIdstring—The browser or device, 1 to 256 characters, not blank. Full mode only.
userIdstring—Your id for the signed-in person, 1 to 256 characters, not blank. Full mode only.
sessionIdstring—A UUID for the visit. Full mode only.

Lengths count Unicode code points; counting UTF-16 code units, as JavaScript does, is never more permissive. Send well-formed strings: a lone UTF-16 surrogate, for example from truncating an emoji in half, makes the whole body 400 invalid_json.

An anonymousId or userId that is a placeholder is stored as empty, so a broken integration cannot merge every visitor into one person. Placeholders are, compared after lower-casing ASCII letters and trimming spaces, tabs, line breaks and quotes: undefined, null, none, nan, 0, true, false, anonymous, guest, id, email, distinct_id, distinctid, not_authenticated, [object object].

Properties

properties is a JSON object. The server refuses the event's batch (400 validation_failed) when it:

  • encodes to more than 32,768 bytes as UTF-8 JSON, counted without escaping / or non-ASCII characters;
  • holds more than 64 leaf values (a list counts as one value, however long; an empty object counts as one);
  • nests objects more than 5 levels deep;
  • has a key longer than 128 characters.

Two properties have a meaning: revenue, a number, and currency, an ISO 4217 code such as "EUR". Revenue without a currency is counted in the project's reporting currency. See Track events.

Context

context describes the sender once for the whole batch:

NameTypeDefaultDescription
sdkstring—name/version: name up to 64 characters without /, version up to 32 characters. For example acme-ruby/0.3.0.
localestring—BCP 47 language tag, 2 to 35 characters, e.g. de-DE. Full mode only.
timezonestring—IANA time zone, up to 64 characters, e.g. Europe/Berlin. Full mode only.
screenarray—[width, height], integers from 0 to 32,768. Full mode only.

Device type, browser and location come from the request's User-Agent and address, only for website sources. Events from a server source carry no device and no location.

Collection modes

mode says what the batch may carry. The concepts are in Consent.

Consentless needs no consent banner and carries nothing that identifies a person or a device. A consentless batch MUST NOT carry:

  • anonymousId, userId or sessionId on any event;
  • context.locale, context.timezone or context.screen.

The server refuses the whole batch with 400 validation_failed rather than stripping the fields, so a misconfigured client is noticed. $identify, $search and $exposure events in a consentless batch are dropped, and the rest is kept. A client in consentless mode also reads no language, time zone or screen size and writes nothing to the device.

Full is for visitors who consented, or for servers acting on consent you already hold. It may carry identifiers and device context.

The source's mode is a ceiling. A full source accepts consentless batches (for example, before the visitor answers the banner). A consentless source refuses full batches with 400 collection_mode_not_allowed.

A request with Sec-GPC: 1 or DNT: 1 has its full batch stored as consentless: identifiers and ad click ids are not kept, and $identify, $search and $exposure are dropped.

Reserved event names

Names starting with $ belong to MIRA FIVE. Only these are accepted; any other $ name makes the batch 400 validation_failed.

NameSent byCarries
$pageviewbrowser clientspage. Optional property $boot: 1 when the page's consent answer was known when it first drew.
$autocapturebrowser clientsProperties $event_type (click, submit or change), $el_tag, $el_selector, $el_id, $el_classes, $el_text, $el_href, $el_name, $el_type, $el_attrs.
$identifyany client, full onlyuserId, and the person's traits as properties. See Identify users.
$searchany client, full onlyProperty query. The server lower-cases it, replaces emails and runs of six or more digits, and cuts it to 100 UTF-16 code units. An event without a usable query is dropped.
$exposureany client, full onlyProperties $experiment (^[a-z][a-z0-9-]{1,63}$), $variant (^[a-z][a-z0-9-]{0,39}$), optional $boot and $snippet; needs an anonymousId or a userId. An exposure missing any of these is dropped alone. See Exposures.
$install_checksetup toolsNothing. Proves a key and the host work; never stored or billed.

An event named flagProperties is dropped: old trackers sent it by mistake.

Clean page URLs

The server stores a page URL as scheme, host, port and path only. From the query it keeps utm_source, utm_medium, utm_campaign, utm_term and utm_content, and the ad click ids gclid, gbraid, wbraid, fbclid, msclkid, ttclid and li_fat_id (the click id itself only in full mode). The referrer loses its query. A URL that is not http or https is not stored.

Clean URLs before they leave the device anyway, so nothing personal travels:

  1. Keep only query parameters whose name starts with utm_ or is ref, source, gclid, gbraid, wbraid, fbclid, msclkid, ttclid or li_fat_id. Names compare case-sensitively; keep the kept parameters' original encoding.
  2. Drop the fragment, unless the site routes by hash. Then keep it, and clean a query inside it the same way.
  3. A URL that does not parse loses everything from the first ? or #.

https://shop.example/pricing?utm_source=news&email=a%40b.example#plans becomes https://shop.example/pricing?utm_source=news.

The receipt

A batch the server has finished with answers 202 Accepted:

FieldTypeDescription
batchstringThe batch id you sent.
acceptedintegerEvents kept, exposures included.
droppedintegerEvents not kept.
reasonstringPresent only when nothing was kept for one of the reasons below.
reasonMeaning
botThe User-Agent of a website batch belongs to a bot.
install_checkThe batch held only $install_check events.
ingestion_pausedThe organization is paused.
allowance_exhaustedThe plan's monthly events are used up.

dropped above 0 without a reason means single events were dropped: person events in a consentless batch, a $search without a query, an invalid $exposure, or the part of a batch that crossed the monthly allowance (its first events are kept).

A 202 is final, whatever it says. Do not retry it. Every other status is described in Errors and retries.

Complete example

A full-mode batch from a browser after consent: a pageview, a click, an identify, an experiment exposure and a purchase.

batch.json
{
  "v": 1,
  "batch": "6c1f0c2e-8f4a-4b7e-9a3d-5e2b1c0d9f8a",
  "mode": "full",
  "sentAt": 1727430060000,
  "context": {
    "sdk": "mirafive-browser/1.0.0",
    "locale": "de-DE",
    "timezone": "Europe/Berlin",
    "screen": [1512, 982]
  },
  "events": [
    {
      "name": "$pageview",
      "time": 1727430000000,
      "page": { "url": "https://shop.example/checkout", "title": "Kasse", "referrer": "https://shop.example/pricing" },
      "properties": { "$boot": 1 },
      "anonymousId": "5f0c1c8e-3e0e-4a57-9d59-3f7f2a6d1e44",
      "sessionId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
    },
    {
      "name": "$autocapture",
      "time": 1727430012000,
      "page": { "url": "https://shop.example/checkout" },
      "properties": {
        "$event_type": "click",
        "$el_tag": "button",
        "$el_selector": "form > button.primary",
        "$el_text": "Jetzt kaufen"
      },
      "anonymousId": "5f0c1c8e-3e0e-4a57-9d59-3f7f2a6d1e44",
      "sessionId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
    },
    {
      "name": "$identify",
      "time": 1727430030000,
      "properties": { "plan": "pro" },
      "anonymousId": "5f0c1c8e-3e0e-4a57-9d59-3f7f2a6d1e44",
      "userId": "u_42",
      "sessionId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
    },
    {
      "name": "$exposure",
      "time": 1727430000500,
      "properties": { "$experiment": "hero", "$variant": "b", "$boot": 1, "$snippet": "fa1adf55" },
      "anonymousId": "5f0c1c8e-3e0e-4a57-9d59-3f7f2a6d1e44",
      "userId": "u_42",
      "sessionId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
    },
    {
      "name": "order completed",
      "time": 1727430055000,
      "page": { "url": "https://shop.example/thanks" },
      "properties": { "revenue": 49.9, "currency": "EUR", "items": [{ "sku": "tee-black", "quantity": 2 }] },
      "anonymousId": "5f0c1c8e-3e0e-4a57-9d59-3f7f2a6d1e44",
      "userId": "u_42",
      "sessionId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
    }
  ]
}
202 Accepted
{ "batch": "6c1f0c2e-8f4a-4b7e-9a3d-5e2b1c0d9f8a", "accepted": 5, "dropped": 0 }

JSON Schema

JSON Schemas (draft 2020-12) describe what a client sends and receives. Validate your batches against them in tests:

SchemaDescribes
batch.schema.jsonthe batch, including the consentless rules
receipt.schema.jsonthe 202 receipt
error.schema.jsonthe error body

A batch that passes the schema can still be refused for what a schema cannot express: event ids repeated within the batch, properties over 32,768 bytes, 64 leaf values or 5 levels, and a body over 1 MiB.

On this page