API
The API is in development and may change before the first release.
The REST API lives at https://<your-control-center>/functions/v1/api/v1. Every request needs an API key from the control center:
Authorization: Bearer mk_live_…
Content-Type: application/json
Errors always have the same shape:
{ "error": { "code": "label_not_found", "message": "No label with id …" } }
Find something on the floor
POST /locate makes every label bound to a record blink.
curl -X POST https://your-server/functions/v1/api/v1/locate \
-H "Authorization: Bearer mk_live_…" \
-H "Content-Type: application/json" \
-d '{ "entity": { "doctype": "Warehouse", "name": "KIT 164" }, "preset": "locate" }'
{ "labels": [{ "label_id": "3f0c…", "request_id": "9b1e…" }] }
Presets set colour and timing. You can override color, duration_s, on_ms, off_ms and brightness.
| Preset | Colour | Pattern | Runs for |
|---|---|---|---|
locate | white | fast blink | 60 s |
pick | green | long on, short off | 5 min |
attention | yellow | slow blink | 2 min |
alert | red | very fast blink | 60 s |
ack | green | one short flash | 1 s |
off | turns the LED off |
Labels
| Method | Path | What it does |
|---|---|---|
GET | /labels | List labels, filter by site_id, status or text |
GET | /labels/{id} | One label with its binding and current content |
PATCH | /labels/{id} | Rename a label or move it to another site |
PUT | /labels/{id}/binding | Bind a label to a record and a template |
DELETE | /labels/{id}/binding | Remove the binding |
PUT | /labels/{id}/content | Set content by hand: a template and its data |
POST | /labels/{id}/led | Blink one label |
POST | /refresh | Re-read bound records now, for some labels or all |
Bindings
A binding says what a label represents and where its data comes from. This one shows the open work order that is packed in kitting box KIT 164:
{
"entity": { "system": "erpnext", "doctype": "Warehouse", "name": "KIT 164" },
"template_id": "…",
"empty_template_id": "…",
"resolver": {
"kind": "erpnext-query",
"doctype": "Work Order",
"filters": [["kitting_box", "=", "{{entity.name}}"], ["status", "not in", ["Completed", "Cancelled"]]],
"fields": ["name", "item_name", "qty", "expected_delivery_date", "priority"],
"map": { "wo": "name" }
}
}
When no work order matches, the label shows empty_template_id, for example "Box free".
Templates
Templates are HTML fragments with Mustache placeholders, laid out with flexbox. Every value is escaped. Two extra tags exist:
<mk-qr value="{{wo}}" size="96" />draws a QR code.<mk-fit max="22" min="12">…</mk-fit>shrinks long text until it fits.
Colours are limited to what the panel can show: white, black, red and yellow (red and yellow only on colour panels).
Webhooks
Subscribe with POST /webhooks and a list of events. Each delivery is signed:
X-Merkora-Signature: t=1760000000,v1=<hex HMAC-SHA256 of "t.body">
Check the signature with your subscription secret and reject anything older than five minutes.
| Event | When |
|---|---|
button.pressed | Someone pressed the button on a label |
content.displayed | A label confirmed it shows new content |
content.failed | A label could not be updated after retries |
label.offline | A label stopped answering |
label.battery_low | A label's battery is running low |
led.done | A blink request was delivered |
{
"id": "…",
"type": "button.pressed",
"at": "2026-10-09T09:14:03Z",
"org_id": "…",
"data": {
"label_id": "…",
"label_name": "KIT 164",
"entity": { "system": "erpnext", "doctype": "Warehouse", "name": "KIT 164" },
"event": { "button": 0, "kind": "short" }
}
}