Overview
Send events and read feature flags over plain HTTP, from curl or any language, without an SDK.
The ingest API is the HTTP interface every MIRA FIVE SDK speaks: it takes batches of events and serves feature-flag documents. Call it directly when there is no SDK for your language, when you write an SDK, or to test a key from a terminal. For JavaScript and PHP, an SDK does all of this for you.
Base URL
https://events.mirafive.ioEvery path starts with /v1. Bodies are UTF-8 JSON. No endpoint sets or reads cookies.
Every /v1 path answers CORS for any origin: Access-Control-Allow-Origin: *, methods GET, POST and OPTIONS, request headers Authorization and Content-Type, Retry-After exposed, no credentials, preflights cached for 86,400 seconds. Which sites may use a website key is checked separately, against the source's allowed origins (see Website key in the path).
Authenticate
A source has exactly one kind of key, and the kind decides where the key travels. See Keys for where to create them.
| Key | Source | Where it goes | Who sends it |
|---|---|---|---|
| Website key | website source | the URL path | browsers, and any client whose code is public |
| Secret key | server source | Authorization: Bearer mf_… | servers only |
Keys look like mf_ab12cd34_…: mf_, eight lower-case letters or digits, _, then the secret part (mf_[a-z0-9]{8}_[A-Za-z0-9_-]+). Older keys of the form mira_ik_… are still accepted. A missing, unknown, revoked or archived key gets one answer: 401 unauthorized.
Website key in the path
Browsers send the website key in the path and the JSON body as Content-Type: text/plain;charset=UTF-8:
POST https://events.mirafive.io/v1/batch/mf_…That makes the request a CORS simple request: there is no preflight, and it works with navigator.sendBeacon() and fetch(…, { keepalive: true }). The server reads the body as JSON whatever the content type says.
When a request carries an Origin header, the origin must be one of the source's allowed origins, or the answer is 403 origin_not_allowed. An apex domain and its www. twin count as one site (same scheme and port). localhost and 127.0.0.1 pass on any port. A source with no allowed origins accepts every origin, and a request without an Origin header (curl, a server) is not checked.
Secret key as a bearer
Servers send the secret key in the Authorization header and the body as Content-Type: application/json:
POST https://events.mirafive.io/v1/batch
Authorization: Bearer mf_…The scheme name is case-insensitive (bearer works too).
Never ship a secret key to a browser. A secret key that arrives with an Origin or Sec-Fetch-Site header is marked exposed: events are still accepted, but the flag endpoints refuse it with 403 secret_key_exposed until you rotate it.
Swapped keys are refused: a website key sent as a bearer gets 403 website_key_as_bearer, a secret key in a path gets 403 secret_key_in_path. See Exposed secret keys.
Endpoints
| Method and path | Key | Does |
|---|---|---|
POST /v1/batch | secret key, bearer | Stores a batch of events from a server. Send events |
POST /v1/batch/{websiteKey} | website key, path | Stores a batch of events from a browser. Send events |
GET /v1/flags/{websiteKey} | website key, path | Returns the browser (or values) flag document. Feature flags |
POST /v1/flags/{websiteKey} | website key, path | Returns the browser flag document with the browser's segment membership. Feature flags |
GET /v1/flags | secret key, bearer | Returns the server flag document, with an ETag. Feature flags |
POST /v1/flags/segments | secret key, bearer | Returns segment membership for up to 100 users or browsers. Feature flags |
A path whose key segment does not look like a key answers 404.
Check a key
Send a batch with one $install_check event. It proves the key and the host work, and is never stored or billed:
curl -i https://events.mirafive.io/v1/batch \
-H "Authorization: Bearer $MIRAFIVE_SECRET_KEY" \
-H 'Content-Type: application/json' \
-d '{"v":1,"batch":"0192d4a8-7b1c-4e8a-9c1d-2b3e4f5a6b7c","mode":"consentless","events":[{"name":"$install_check"}]}'{ "batch": "0192d4a8-7b1c-4e8a-9c1d-2b3e4f5a6b7c", "accepted": 0, "dropped": 1, "reason": "install_check" }For a website key, send the same body to /v1/batch/$MIRAFIVE_WEBSITE_KEY without the Authorization header. A 401 means the key is wrong or revoked; "reason": "ingestion_paused" means the key works but the organization is paused.
Limits
| Limit | Value | Over it |
|---|---|---|
| Batch body | 1 MiB (1,048,576 bytes) | 413 payload_too_large |
| Events per batch | 1 to 1,000 | 400 validation_failed |
| Properties per event | 32,768 bytes as UTF-8 JSON, 64 leaf values, 5 levels, keys up to 128 characters | 400 validation_failed |
Segment lookup body (POST /v1/flags/{websiteKey}) | 1,024 bytes | membership left out |
Units per segment lookup (POST /v1/flags/segments) | 100 | 422 too_many_units |
| Requests and events per second or minute | per address, per key | 429 rate_limited with Retry-After |
Field limits are on Send events, rate limits on Errors and retries.
Versioning
This is version 1 of the protocol, in the path (/v1) and in the bodies:
- A batch MUST carry
"v": 1. Any other value is400 validation_failed. - Flag documents carry
"v": 1. Treat a document with any other version as unreadable and keep the last one you had. - The server ignores fields in a batch that it does not know, so an SDK can add hints without breaking older servers. Do not use that to send data the protocol does not define.
- Ignore unknown fields at every level of an answer. New fields, new
reasonvalues and new error codes may appear within v1. Decide retries by status code, never by error code.
When to use an SDK instead
The SDKs implement everything on these pages: batching, retries with the same batch id, consent and collection modes, URL cleaning, flag evaluation, bootstrap blocks and deduplicated exposures. Use one when your stack has it:
Script tag
Browser
Node and edge
PHP
Use the ingest API directly for other languages, for a one-off import, or to debug what an SDK sends.