MIRA FIVE

Overview

Read MIRA FIVE projects, reports, people and events from a script with the read-only REST API and an API key.

The REST API serves the numbers the MIRA FIVE dashboard shows: projects, the overview, people, raw events, goals, funnels and acquisition. Use it from scripts and servers, for example a nightly export or an internal report. It only reads. To let an AI agent read and set things up, use the MCP server instead.

Base URL

https://app.mirafive.io/api/v1

Every endpoint answers GET and HEAD. Any other method gets 405 Method Not Allowed with Allow: GET, HEAD. Unknown query parameters are ignored.

Authenticate

Send a credential as a bearer token:

Authorization: Bearer mf_pat_…

Two kinds work:

CredentialLooks likeLifetime
API keymf_pat_, eight lower-case letters or digits, _, then 40 letters or digits30 days, 90 days, 1 year or never, chosen when you create it
OAuth access tokenissued to an app you approved (see MCP server)1 hour, renewed with a 30-day refresh token

Create an API key as described in Keys. The examples on these pages read it from MIRAFIVE_API_KEY.

A credential acts as its owner. It sees every project in every organization you belong to, and loses a project the moment you leave its organization. The REST API is read-only whatever the key's switches allow.

An API key reads everything you can read, people included. Keep it on the server, in an environment variable. Never ship it to a browser or commit it.

Make a first request

curl https://app.mirafive.io/api/v1/projects \
  -H "Authorization: Bearer $MIRAFIVE_API_KEY"
200 OK
{
  "data": [
    {
      "id": "01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f",
      "type": "projects",
      "attributes": {
        "name": "Nordlicht Shop",
        "slug": "nordlicht-shop",
        "timezone": "Europe/Berlin",
        "reportingCurrency": "EUR",
        "archived": false,
        "createdAt": "2025-11-04T09:12:44.318+01:00",
        "organization": {
          "id": "01932c4d-1f2e-7a3b-8c4d-5e6f7a8b9c0d",
          "slug": "nordlicht",
          "name": "Nordlicht GmbH"
        },
        "plan": "pro",
        "retentionDays": 366
      },
      "links": {
        "self": "https://app.mirafive.io/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f"
      }
    }
  ],
  "links": {
    "self": "https://app.mirafive.io/api/v1/projects",
    "next": null
  },
  "meta": {
    "perPage": 50,
    "nextCursor": null
  }
}

Every other endpoint sits under a project: /api/v1/projects/{project_id}/…. The project_id is the id from this list, a UUID. A slug gets 404.

Response format

Successful answers are JSON:API documents with Content-Type: application/vnd.api+json, whatever Accept header you send. URLs and names are written unescaped (no \/).

  • data holds one resource object, or a list of them. Each has id, type, attributes and, where it has an address, links.self.
  • meta holds what the answer is about: the period it read, the page size, totals.
  • Lists that page have top-level links (self, next). See Pagination.
  • There are no relationships and no include.

Sparse fieldsets

Ask for only some attributes with fields[<type>], a comma-separated list:

curl -G https://app.mirafive.io/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f \
  -H "Authorization: Bearer $MIRAFIVE_API_KEY" \
  --data-urlencode 'fields[projects]=name,timezone'
200 OK
{
  "data": {
    "id": "01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f",
    "type": "projects",
    "attributes": { "name": "Nordlicht Shop", "timezone": "Europe/Berlin" },
    "links": { "self": "https://app.mirafive.io/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f" }
  }
}

Timestamps

Moments (createdAt, firstSeen, occurredAt, …) are ISO 8601 with milliseconds, in the project's timezone with its offset: 2026-09-23T13:55:00.000+02:00. The bounds in meta.period and the start of each trend bucket (t) are ISO 8601 without milliseconds: 2026-09-28T00:00:00+02:00.

Money and ratios

Amounts stay per currency and are never added across currencies: [{ "currency": "EUR", "amount": 49 }, { "currency": "CHF", "amount": 25 }]. Ratios such as rate, conversion, coverage and ctr are between 0 and 1.

Periods

Reports read a period. Pass a preset, or period=custom with two days.

NameTypeDefaultDescription
periodstring30d1h, 24h, 7d, 30d, 90d, 12m or custom. The events endpoint takes only 1h, 24h and 7d, and defaults to 24h.
fromstringnoneFirst day, YYYY-MM-DD, in the project's timezone. Required with period=custom, refused otherwise.
tostringnoneLast day, inclusive, YYYY-MM-DD. Required with period=custom, refused otherwise.

A preset ends with the current minute, hour, day or month and includes it: 7d on 27 September is 21 to 27 September, today included. Days and months are cut in the project's timezone.

Rules for a custom range:

  • to must be on or after from.
  • from after today (in the project's timezone) is refused. A to after today reads up to today.
  • The plan keeps a limited history (retentionDays on the project). A range that starts before it starts at the first day the plan keeps instead, and meta.period.clampedToRetention is true. A range that ends before it is refused, for example with to is before 2025-09-26, the first day the plan keeps.

Trends are bucketed by the period:

PeriodBucket (interval)
1hminute
24hhour
7d, 30d, 90dday
12mmonth
customday up to 120 days, week beyond

Every report says which window it read in meta.period:

"period": {
  "preset": "30d",
  "from": "2026-08-29T00:00:00+02:00",
  "to": "2026-09-28T00:00:00+02:00",
  "timezone": "Europe/Berlin",
  "interval": "day",
  "comparison": null,
  "clampedToRetention": false
}

to is exclusive. comparison holds the from and to of the previous window when the answer compares, else null.

Compare with the previous period

compare=previous adds the window before it, of the same length. Only three endpoints take it: the overview, a goal report and search. Everywhere else it is refused with This endpoint does not compare periods. Leave out compare.

The overview's key numbers and the search summary carry the previous values even without compare. A previous window that starts before the history the plan keeps, or before the project's first event, is left out: comparison is then null.

Filters

The overview, events and channels endpoints narrow their events with filters. Each filter is three query parameters with the same index. All filters must hold (AND). At most 20.

filters[0][field]=page_path&filters[0][op]=starts_with&filters[0][value]=/products

With curl, let -G and --data-urlencode encode the brackets:

curl -G https://app.mirafive.io/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f/overview \
  -H "Authorization: Bearer $MIRAFIVE_API_KEY" \
  --data-urlencode 'period=7d' \
  --data-urlencode 'filters[0][field]=channel' \
  --data-urlencode 'filters[0][op]=is' \
  --data-urlencode 'filters[0][value]=organic_search' \
  --data-urlencode 'filters[1][field]=country' \
  --data-urlencode 'filters[1][op]=is' \
  --data-urlencode 'filters[1][value]=DE'

field is at most 150 characters, op 20, value 1,000. The People list has its own filters on people.

Fields

FieldOperatorsValue
event_nametexte.g. checkout_started, $pageview
page_pathtexte.g. /products/linen-shirt
page_hosttexthost without www., compared in lower case
referrer_hosttexthost without www., compared in lower case
utm_source, utm_medium, utm_campaign, utm_term, utm_contenttextas tagged
regiontext
channelis, is_notpaid_search, paid_social, email, ai_assistant, organic_search, organic_social, campaign, referral, direct
countryclosed settwo-letter code, e.g. DE
click_source, device_type, browser, os, locale, source_type, collection_modeclosed setas the events carry it
properties.<key>alla custom event property, e.g. properties.plan. The key matches ^[A-Za-z0-9_$][A-Za-z0-9_.$-]{0,127}$.

"text" means every operator except gt and lt. "closed set" means is, is_not, set and not_set.

channel is the channel of the visit the event belongs to. A visit collected without consent keeps only its first page, so under a channel filter it counts by that page. A project that collects only without consent refuses a channel filter: This project collects without consent, so it is not filtered by channel.

Operators

OperatorMatches when the fieldValue
isequals the valuerequired
is_notdiffers from the valuerequired
containscontains the value, ignoring caserequired
not_containsdoes not contain the value, ignoring caserequired
starts_withstarts with the valuerequired
matchesmatches the pattern; * is the only wildcardrequired
setis not emptynone
not_setis emptynone
gtis a number greater than the valuea number, e.g. 49
ltis a number less than the valuea number

Pagination

Lists of projects, people and events come 50 at a time and page by cursor. There is no page number and no page size parameter.

"links": {
  "self": "https://app.mirafive.io/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f/events",
  "next": "https://app.mirafive.io/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f/events?cursor=eyJhdCI6IjIwMjYtMDktMjcgMDg6MTQ6MDIuNTEyIn0"
},
"meta": {
  "perPage": 50,
  "nextCursor": "eyJhdCI6IjIwMjYtMDktMjcgMDg6MTQ6MDIuNTEyIn0"
}

Follow links.next until it is null, or pass meta.nextCursor as cursor with the same other parameters. A cursor the list did not hand out gets 422 with The cursor is not one this list handed out; follow links.next.

Rate limits

Each person has 120 requests per minute, shared by all their API keys and tokens. The MCP server has a budget of its own. Every answer carries the budget:

RateLimit-Policy: "api";q=120;w=60
RateLimit: "api";r=117;t=42

r is the requests left, t the seconds until the window resets. Past the limit the answer is 429 with Retry-After and The limit is 120 requests per minute. Try again in 42 seconds.

Refused tokens are limited too: 60 per minute from one network. Past that, a bad token gets 429 with Too many refused tokens came from this network. Try again in N seconds. A valid token from the same network still gets through.

Browsers and CORS

The API sends no CORS headers, so a page in a browser cannot read it from another origin. Call it from a server or a script.

Every answer carries X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin and Content-Security-Policy: default-src 'none'; frame-ancestors 'none'.

Audit trail

Every call is recorded under API & MCP in MIRA FIVE as recent access: the key or app, the endpoint, the project and how the call ended. Query parameters are never stored, since they can name people. A call with a revoked or expired key is recorded against its owner, so a leaked key shows up there.

Endpoints

EndpointPage
GET /api/v1/projectsProjects
GET /api/v1/projects/{project_id}Projects
GET /api/v1/projects/{project_id}/overviewOverview report
GET /api/v1/projects/{project_id}/peoplePeople
GET /api/v1/projects/{project_id}/people/{ref}People
GET /api/v1/projects/{project_id}/eventsEvents
GET /api/v1/projects/{project_id}/goalsGoals
GET /api/v1/projects/{project_id}/goals/{goal_id}Goals
GET /api/v1/projects/{project_id}/funnelFunnel
GET /api/v1/projects/{project_id}/acquisition/channelsAcquisition
GET /api/v1/projects/{project_id}/acquisition/searchAcquisition
GET /api/v1/projects/{project_id}/acquisition/adsAcquisition

There is no OpenAPI document. Errors are listed in Errors.

On this page