Browse documentation
All documentation
Getting started
Screens & scenes
Widgets
Integrations
The assistant
Household & presence
Reception & visitors
Menus, art & broadcast
Account, plans & data
API & self-hosting
Troubleshooting

The REST API and your keys

How to create an API key, what a single key is allowed to reach, and every endpoint the public v1 API exposes.

The API is part of the Pro plan. The plan is checked on every request rather than only when a key is made, so an account that moves off Pro loses API access at once. Emergency broadcast is the deliberate exception and answers on any plan. The menu endpoints are gated twice — the API gate, and a second gate on the kind of customer: a household on Enterprise has the API and no menus, and a restaurant on Business Starter has both.

What the API is for

The API exists so that something other than a person can decide what a screen says. A booking system knows which meeting is next, a till knows what has sold out, a building management system knows the alarm has gone off — and none of that should need somebody to retype it onto a wall.

It is a small API on purpose. It reaches the things that change during a normal day, and it deliberately does not reach the things you would only ever do once, sitting down, looking at what you were doing.

  • Read every screen, the scenes on it, and the widgets on those.
  • Change a widget's settings, position, or whether it is showing at all.
  • Put a scene on a screen, take it off, reorder the list, or set the fallback.
  • Show a scene immediately, ignoring the schedule, and hand control back again.
  • Replace a screen's whole schedule.
  • Turn sharing on or off for a scene, and rename it.
  • Read and change lights — Home Assistant and WLED in one list.
  • Start and clear an emergency broadcast on every screen at once.
  • Read a menu and push prices, availability and descriptions into it.

There is no way to create a scene, delete one, add or remove a widget, pair a screen, upload a photo, or change the assistant through the API. Those are portal-only. Deleting a design in particular has no endpoint on purpose — it is not something to discover by sending a DELETE to the wrong URL.

Making a key

Keys are made while signed in, never with another key. A stolen key must not be able to mint itself a replacement, so key management lives behind your session and nowhere else.

API & webhooksYour keys2 of 5Home Assistantmk_a1b2c3_… used todayWhat is it for?e.g. Home AssistantCreate a keyReferenceEvery example uses this household's real ids
The API & webhooks page: your keys, then the reference, filled in with your own screen and scene ids.
  1. Open API keys

    It is in the account menu under your own name. The page is headed API & webhooks. If your plan does not include the API, a panel at the top says so and offers a See plans button; the form is still drawn, but Create a key stays disabled.

  2. Name it after the thing that will hold it

    The box asks what it is for — "Home Assistant", "till", "fire panel". Forty characters, and it is what appears in the audit trail beside everything that key does, so a name like "test" will not help anybody in six months.

  3. Press Create a key

  4. Copy it before you leave the page

    The key appears once, in a panel with a Copy button. Press I've saved it when it is somewhere safe.

Mirra stores a one-way hash of the key and the first few characters, so it genuinely cannot show it to you a second time. A lost key is not recoverable; revoke it and make another.

What one key can reach, and how to take it back

There are no scopes. A key is the household, not a permission set — it reaches every screen, scene, widget, schedule, light and menu the plan allows, plus the emergency broadcast. If you want an integration that can drive reception and not the boardroom, the honest answer today is that you cannot have one: give the key to a system you trust with the whole account, or do not give it one.

What a key cannot do is administer the account. It cannot create or revoke keys, read your billing, or touch another household's data.

  • Five keys at once. The page shows the count; revoke one you no longer use to make room.
  • Revoke is immediate — anything still using that key starts getting 401 on its next call.
  • A revoked key is marked revoked rather than deleted, so the record of what it was doing survives.
  • Every key shows when it was last used, which is the quickest way to find the one nobody needs any more.
  • Making and revoking keys is written to the audit log, as is everything a key changes — recorded against the key's name, because nobody was signed in.

Treat a key like a password. It is a bearer credential: anyone holding it is your household as far as Mirra is concerned. If one ends up in a screenshot, a chat message or a public repository, revoke it rather than hoping.

Calling it

The base address is /api/v1 on whatever address you reach Mirra at — the reference on the API page fills in the right one for your install. Send the key as a header on every request.

Everything answers JSON. Errors are an object with an error field and a real HTTP status, because an integration that only ever receives 200 has no way to tell success from a typo.

Your requestAuthorization:Bearer mk_…Key checkedPrefix looked up,hash comparedPlan checked403 if the API isnot on itRate limit120 a minute, perkeyAnswerJSON, with a realstatus
What happens to a request before it reaches your screens.
  • Authorization: Bearer mk_… on every request.
  • Anything that changes something also accepts POST, not only PATCH or PUT. Plenty of services can send nothing else, and an API those services cannot call is not much use.
  • 401 means the key is missing, malformed, unknown or revoked. 403 means the plan does not reach that endpoint. 404 means the thing you named is not on this account. 409 means the change needs confirming first.
  • 429 means you have passed 120 requests a minute for that key. The reply carries retryAfterSeconds, so a client does not have to guess.
  • 503 on a light means the house could not be reached, not that the request was wrong — no screen was awake to carry it. Retrying is the right response.
  • Cross-origin requests are allowed from anywhere. The API is authenticated by a header rather than a cookie, so a permissive origin carries no risk of a browser being tricked into using your session.

The rate limit is counted per key rather than per address. One noisy integration cannot throttle the household's other ones, and several keys sharing an office IP do not fight each other.

Screens, scenes and schedules

A screen is identified by its device id — the same id the pairing flow gave you and the one in the portal's URL. A scene can live on more than one screen, so two questions need two endpoints: /screens answers "what is on this display", and /scenes answers "where is this design, and what would change if I edited it".

The schedule is replaced whole rather than edited rule by rule. A start time only means something relative to the rules either side of it, so sending the full set is the only way to be unambiguous about what the day looks like afterwards. An empty array clears the schedule; sixty rules is the limit for one screen.

  • GET /screens — every paired screen, with its scenes and the widgets on them. Each screen carries online, orientation and when it was last seen.
  • GET /screens/{screenId} — one screen in full, including pixel size and its schedule.
  • GET, PUT, POST /screens/{screenId}/scenes — the scenes on a screen. POST adds one and leaves the rest; PUT replaces the list and takes defaultSceneId for the scene shown when nothing is scheduled.
  • GET, DELETE /screens/{screenId}/scenes/{sceneId} — one scene as this screen has it, or take it off. Removing is not deleting, and a screen's last scene cannot be removed.
  • POST /screens/{screenId}/scene — show a scene straight away, ignoring the schedule. Send null to hand control back. The override lives on the screen, so a restart also returns it to the schedule.
  • GET, PUT /screens/{screenId}/schedule — read or replace the whole schedule. days runs Monday first as seven characters of 0 or 1; startTime is HH:MM in 24-hour time.
  • GET /scenes — every scene in the account, whether it is shared, and which screens use it.
  • GET, PATCH /scenes/{sceneId} — rename a scene, or turn sharing on and off.

Transitions on a schedule rule are cut, fade, fadeBlack, pushLeft, pushRight, pushUp and pushDown. A rule may name any scene the screen has, or any shared scene in the account — scheduling a shared one also puts it on the screen, because a rule for a scene the screen has not got would never fire. The reply says how many were added.

Changing a widget

PATCH /screens/{screenId}/scenes/{sceneId}/widgets/{widgetId} is the endpoint most integrations spend their life calling. GET the same address to read one back.

Send only what you are changing. Sending nothing recognisable is a 400 rather than a silent success.

config
The widget's own settings, merged into what is already there — the same settings its panel shows in the scene editor. Send one field and the rest are left alone.Default: Unchanged
hidden
True takes the widget off the wall without deleting it; false puts it back. This is the widget's own switch and leaves any visibility rule you set alone, so turning it back on restores that rule.Default: Unchanged
visibility
A time rule for the widget — an object with from, to, days and presence. Send null to clear it and show the widget whenever the scene is up.Default: Unchanged
x, y, w, h
Position and size on the twelve-column grid. Whole numbers, rounded, and never negative.Default: Unchanged

Positions are in the grid the scene was drawn in, which the scene's orientation names — not necessarily this screen's. A screen mounted the other way round draws a re-flow of them. And if the scene is shared, this changes it on every screen it is on, not only the one in the URL.

Lights

Home Assistant and WLED appear in one list, because from outside the house they are the same kind of thing. Each light carries caps, which says what it can actually be asked — a colour sent to a strip with no colour channel does nothing, and this is how you know before you send it.

The list is answered from the last snapshot a screen reported rather than by waking one and waiting on the home network. That is a real trade, so the reply says so: stale is true when the freshest thing Mirra knows is over ninety seconds old.

  • GET /lights — every light, with asOf and stale.
  • GET /lights/{lightId} — one light. Add ?live=true to go and ask rather than answer from the snapshot; worth the round trip when you are about to act on the answer.
  • POST /lights/{lightId} — change one light. Accepts on, brightnessPct (0–100), colour as #rrggbb, gradient (one to sixteen colours), kelvin (1500–10000), effect, palette, preset, speed and intensity (0–100), sunriseMinutes (1–60) and transitionSeconds (0–60). PATCH and PUT do the same thing.
  • POST /lights/scene — one change across several lights at once. Give lights as a list of ids, or target as a name a person would use: a room, a device, or "all the lights". The reply is per light, because one controller can be unplugged while the rest of the house is fine.

Anything not on that list of fields is dropped rather than passed through. A WLED controller has no authentication of its own, and the same channel that sets a colour reboots the device and destroys its segments — so this endpoint speaks a small whitelist and nothing else.

Emergency broadcast

This is the endpoint a fire panel, a lockdown button or a building management system is wired to, so it is the one call in the API that has to work while everything else is going wrong. It is not gated on your plan.

There are no screen ids and no ordering to get right: POST takes over every screen in the account, DELETE gives them back, GET says whether one is running. Anything you send alongside a templateId overrides that template, so one stored "Fire — evacuate" covers every zone in a building without a template per zone.

  • GET /broadcast — whether one is live, and what it says. Safe to poll.
  • POST /broadcast — start one. Only title is required. severity is emergency, warning or info; icon is alert, fire, lockdown, weather, medical, evacuate or info.
  • DELETE /broadcast — clear it. Sent to every screen even if nothing was live, so a screen that missed the end of an earlier one is put right too.
  • A second POST replaces a running broadcast rather than queueing behind it.
  • A mistyped templateId is a 404 and nothing is sent.

Every reply carries reached and total. Check them. Accepted is not the same as arrived, and in an evacuation the difference is the whole message — screens that did not acknowledge will pick the broadcast up when they next reach the server, which may be too late to be useful.

Menus

The menu endpoints exist for the case where the prices already live somewhere else — a till, or a spreadsheet somebody maintains by hand — and the board should follow rather than be retyped.

Prices are integers in the smallest unit of the currency: 1250 is £12.50. Not a decimal, because a board that prints 3.2 instead of 3.20 looks wrong from across a room, and floating point is how that happens.

  • GET /menus — every menu, with its sections and dishes, in the same shape the screen renders.
  • GET, PATCH /menus/{menuId} — the menu's own fields: name, currency and allergenNote.
  • PATCH /menus/{menuId}/items — change many dishes in one call, up to three hundred. A dish is named by id, or by name where that name is unique within the menu.
  • PATCH /menus/{menuId}/items/{itemId} — one dish, and the only endpoint that will rename one.
  • Fields per dish: price, priceLabel (replaces the figure entirely — "market price"), description, calories, tags and available. Tags are vegan, vegetarian, gluten-free, dairy-free, contains-nuts, spicy, halal and new; anything else is dropped rather than shown as a mark nobody can read.

The bulk endpoint checks everything before it writes anything, so one bad row changes nothing and comes back in problems saying which row and why. Half-applied prices — a board showing two price lists at once — are the failure it exists to prevent. Photographs cannot be set through the API; upload those under Menus where you can see what you are choosing.

Related

  • WebhooksHow to give a widget its own web address so another service can push a value straight onto a screen, and what Mirra does and does not send back.
  • Plans and allowancesWhat each Mirra plan includes — screens, household members and assistant credits — how credits are metered, and what happens when you reach a limit.
  • Emergency broadcastHow to take every screen on the account at once with one message, using a template and a two-step confirmation — and how to wire a physical button to a screen to do the same thing.
  • Menu boardsHow to build a menu in Mirra — sections, dishes, prices and photographs — put it on a screen through the menu board widget, and give kitchen staff a sign-in that reaches nothing else.
  • Lights, light rules and WLEDOne light model across Home Assistant bulbs, WLED strips, Govee lamps and Hue bulbs: adding a controller, what a rule can do, how a segment becomes a light, and which of the four leave your network at all.
  • The schedulerSchedule rules say which scene a screen shows from a given time on given days, and the Scheduler draws every screen's day as a timeline you can drag scenes onto.