# Overview

Source: https://docs.mirafive.io/ingest-api

> 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](https://docs.mirafive.io/sdks) does all of this for you.

## Base URL

```text
https://events.mirafive.io
```

Every 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](#website-key-in-the-path)).

## Authenticate

A source has exactly one kind of key, and the kind decides where the key travels. See [Keys](https://docs.mirafive.io/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`:

```text
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`:

```text
POST https://events.mirafive.io/v1/batch
Authorization: Bearer mf_…
```

The scheme name is case-insensitive (`bearer` works too).

> **Important:** 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](https://docs.mirafive.io/ingest-api/errors#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](https://docs.mirafive.io/ingest-api/send-events) |
| `POST /v1/batch/{websiteKey}` | website key, path | Stores a batch of events from a browser. [Send events](https://docs.mirafive.io/ingest-api/send-events) |
| `GET /v1/flags/{websiteKey}` | website key, path | Returns the browser (or values) flag document. [Feature flags](https://docs.mirafive.io/ingest-api/feature-flags) |
| `POST /v1/flags/{websiteKey}` | website key, path | Returns the browser flag document with the browser's segment membership. [Feature flags](https://docs.mirafive.io/ingest-api/feature-flags) |
| `GET /v1/flags` | secret key, bearer | Returns the server flag document, with an `ETag`. [Feature flags](https://docs.mirafive.io/ingest-api/feature-flags) |
| `POST /v1/flags/segments` | secret key, bearer | Returns segment membership for up to 100 users or browsers. [Feature flags](https://docs.mirafive.io/ingest-api/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:

```bash
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"}]}'
```

```json title="202 Accepted"
{ "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](https://docs.mirafive.io/ingest-api/send-events#the-batch), rate limits on [Errors and retries](https://docs.mirafive.io/ingest-api/errors#rate-limits).

## 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 is `400 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 `reason` values 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](https://docs.mirafive.io/sdks/script-tag): One line of HTML for any website.
- [Browser](https://docs.mirafive.io/sdks/browser): The JavaScript client for single-page apps.
- [Node and edge](https://docs.mirafive.io/sdks/node): Node, Bun, Deno, Cloudflare Workers.
- [PHP](https://docs.mirafive.io/sdks/php): Plain PHP; Laravel and Symfony have their own packages.

Use the ingest API directly for other languages, for a one-off import, or to debug what an SDK sends.
