Skip to content

BATCH 2|Batch 1 is fully reserved. Reserving now puts you in Batch 2: estimated wait 2 to 3 weeks from the day you reserve, shipped in the order reserved. Reserve yours

myperchi

Developers

Build on Perchi: REST API, webhooks, MCP, CLI and a local API

Everything the MyPerchi dashboard does, you can do from code: a REST API described by an OpenAPI 3.1 document, inbound webhooks with 15 provider presets, an MCP server with 12 tools, the mtx CLI and TypeScript SDK, a no-code app builder, and an authenticated AWTRIX-3-compatible local API with MQTT on the device.

REST API

What can you do with the REST API?

Everything the MyPerchi dashboard does goes through the same REST API, so a script with a token can list devices, send a notification, push a value, install and configure apps, reorder a playlist or render a preview image at a given canvas size. Requests carry Authorization: Bearer <token>, every response shares one JSON envelope and an OpenAPI 3.1 document describes every route.

curl
# List the devices in a workspace (scope devices:read)
curl -H "Authorization: Bearer $MTX_TOKEN" \
  https://app.myperchi.com/api/v1/workspaces/$WS/devices

# Show a message on one device (scope notifications:send)
curl -X POST https://app.myperchi.com/api/v1/workspaces/$WS/devices/<device_id>/notifications \
  -H "Authorization: Bearer $MTX_TOKEN" -H "Content-Type: application/json" \
  -d '{"text":"DEPLOY OK","color":"#00FF80","icon":"rocket","ttl_s":10,"priority":"normal","message_id":"deploy-1234"}'

# Push a number to an installed app such as Live value (scope data:push)
curl -X POST https://app.myperchi.com/api/v1/workspaces/$WS/installations/<installation_id>/values \
  -H "Authorization: Bearer $MTX_TOKEN" -H "Content-Type: application/json" \
  -d '{"field":"value","value":1234,"ttl_s":300,"message_id":"sales-2026-09-19-001"}'
Every response
{ "status": true, "data": { }, "error": {}, "msg": "Device settings updated.", "request_id": "01923f6c-…" }
  • Base path /api/v1; workspace routes under /api/v1/workspaces/<id>/….
  • Paged lists take ?cursor=&limit=1..100 and return next_cursor; null means the end.
  • An Idempotency-Key header or a body message_id makes a create safe to retry: repeats within 24 hours return the original result with duplicate: true.
  • Edits carry a version; a stale one gets 409 CONFLICT with the current version. Over a limit you get 429 with Retry-After.
  • Every response has an X-Request-Id; quote it when you write to support.

Four token kinds

mtx_pat_
Personal: scripts and the mtx CLI, acts as you within the scopes you pick; any member
mtx_svc_
Service: servers and CI, belongs to the workspace; owners and admins
mtx_mcp_
MCP: AI assistants over the Model Context Protocol; owners and admins
mtx_ing_
Ingest: pushes text to one app via /v1/ingest; editors and above
Lifetime
7, 30, 90 or 365 days, or never; shown once; revocable any time; checksummed so secret scanners recognise a leak

Nine scopes

workspace:read
Name, settings, usage, members
devices:read / devices:write
Devices, status, telemetry; rename, settings, identify, brightness, playlist commands
plugins:read / plugins:write
Installed apps and previews; install, configure, reorder, remove apps and automations
data:push
Push live values to installed apps
notifications:send
Show notifications on devices
apps:publish
Create, preview and publish custom apps
audit:read
The workspace audit log

Push your own numbers with one line

An ingest token created on an app's Webhook tab accepts a text body at POST /v1/ingest, 60 requests a minute, and answers accepted or duplicate.

curl -X POST https://app.myperchi.com/v1/ingest \
  -H "Authorization: Bearer <ingest token>" -H "Content-Type: application/json" \
  -d '{"text":"42","ttl_sec":300,"message_id":"counter-000042"}'

Webhooks

How do webhooks work?

A MyPerchi webhook is an inbound address, POST /hooks/in/<id>, that any service or script can call to change what a display shows: set a value, add to a running total, show a message or record an event that triggers a celebration. Callers prove who they are with a bearer token, a secret URL, a Standard Webhooks signature or one of 15 provider presets, and the server answers 202 in a fraction of a second.

Webhook value
# Any script: a plain-text body sets the value on the target app
curl -X POST https://app.myperchi.com/hooks/in/<id> \
  -H "Authorization: Bearer <token>" \
  -d 42

Bodies can be JSON, form data or plain text up to 256 KB. Each automation takes 60 deliveries a minute in bursts of 20, a workspace 600 a minute; a repeated delivery id is answered 200 with duplicate: true and changes nothing. Delivery details stay in the log for 7 days.

ActionWhat each delivery can do
Set a valueReplace a value with a JSONPath ($.data.amount) or JSON Pointer (/data/amount) pick; a plain-text body is the value
Add to a valueAdd the delivery's amount, or 1, to a running total that resets never, daily, weekly or monthly
Show a messageUp to 64 characters with up to 3 template fields from the body, colour, effect, priority, 1 to 30 seconds
Record an eventFire the app's event reactions, for example a sale animation
FiltersUp to 5, comparing a path with ==, !=, >, <, exists or not exists; a JSONPath tester and test deliveries in the dashboard

15 provider presets, verified with the provider's own secret

  • Shopify
  • Stripe
  • GitHub
  • Paddle
  • Lemon Squeezy
  • Sentry
  • Netlify
  • Razorpay
  • Ko-fi
  • Buy Me a Coffee
  • Gumroad
  • RevenueCat
  • Telegram
  • Vercel
  • Patreon

Signatures are compared in constant time inside their timestamp window. A request that fails authentication is refused with 401, failed attempts never switch an automation off, and you get a security notification if one receives more than 100 failed attempts in an hour. Token and secret rotation keeps the previous value working for 24 hours so you can update the caller.

Recipes, ready to paste

Every automation page shows curl, Apple Shortcuts, Home Assistant, Claude Code, GitHub Actions, n8n and Zapier recipes with your real URL filled in. Two of them, as they come out of the dashboard:

GitHub Actions step
- name: Update display
  if: always()
  run: >
    curl -s -X POST "https://app.myperchi.com/hooks/in/<id>"
    -H "Authorization: Bearer ${{ secrets.MATRIX_HOOK_TOKEN }}"
    -H "Content-Type: application/json"
    -d "{\"status\":\"${{ job.status }}\",\"run\":\"${{ github.run_id }}-${{ github.run_attempt }}\"}"

Store the token as the repository secret MATRIX_HOOK_TOKEN. The run id makes every run a new delivery, so two green runs in a row both update the display. The GitHub Actions status app does this with the GitHub preset and workflow_run events, no token needed.

.claude/settings.json
{
  "hooks": {
    "Notification": [{ "hooks": [{ "type": "command",
      "command": "curl -s -X POST \"https://app.myperchi.com/hooks/in/<id>\" -H \"Authorization: Bearer <token>\" -H \"Content-Type: application/json\" -d '{\"state\":\"needs_input\",\"at\":'$(date +%s)'}'" }] }],
    "Stop": [{ "hooks": [{ "type": "command",
      "command": "curl -s -X POST \"https://app.myperchi.com/hooks/in/<id>\" -H \"Authorization: Bearer <token>\" -H \"Content-Type: application/json\" -d '{\"state\":\"done\",\"at\":'$(date +%s)'}'" }] }]
  }
}

Paired with the AI agent status app, the display shows needs_input when the agent waits for you and done when it stops. The at field makes a repeated state a new delivery instead of a duplicate.

MCP

Can an AI assistant control it?

Yes. The MyPerchi MCP server speaks MCP Streamable HTTP at /mcp on your dashboard host, authenticates with an MCP token and exposes 12 tools that run the same permission checks as the REST API, so Claude, or any other MCP client, can read your displays, send a message, install an app, or build, check, preview and publish a custom app, at up to 60 tool calls a minute.

.mcp.json
{
  "mcpServers": {
    "myperchi": {
      "type": "http",
      "url": "https://app.myperchi.com/mcp",
      "headers": { "Authorization": "Bearer mtx_mcp_…" }
    }
  }
}

An owner or admin creates the token under Settings, Tokens, with only the scopes the assistant needs. Tool descriptions carry tips for designing legible apps on a 24x8 display, and there is deliberately no tool that submits an app to the public store.

ToolScopeWhat it does
list_devicesdevices:readLists devices
get_devicedevices:readDevice details, never credentials
send_notificationnotifications:sendShows a message: text up to 128 characters, colour, icon, placement, time, effect
set_brightnessdevices:writeSets brightness 0 to 255
list_catalog_appsplugins:readSearches the catalogue, up to 50 results
install_appplugins:writeInstalls an app with settings and optional style
push_valuedata:pushPushes a value to an installed app
create_app_draftapps:publishCreates a builder draft
update_app_draftapps:publishUpdates a draft's definition (optimistic lock)
validate_app_draftapps:publishRuns the checks and stores the result
preview_app_draftapps:publishReturns a 30-frame GIF preview and the check results
publish_app_privateapps:publishPublishes a draft privately to the workspace

CLI and SDK

What is the mtx CLI?

mtx is the MyPerchi command line tool: it signs in with a personal token, lists devices, sends a notification, pushes a value, checks an app definition offline, renders a GIF preview locally and publishes an app privately, with --json output and exit codes 0, 1 and 2 for scripts. It comes from the @mx/sdk package, which also exports defineApp(), parseAppDefinition(), lintAppDefinition(), renderAppPreviewGif() and a typed MatrixApiClient.

Terminal
mtx login --api-url https://app.myperchi.com --token - --workspace my-desk
mtx devices --json
mtx send <device_id> "DEPLOY OK" --icon rocket --color "#00FF80" --ttl 10
mtx push <installation_id> 1234 --ttl 300 --message-id sales-2026-09-19-001
mtx validate signups.json
mtx preview signups.json --gif signups.gif --canvas 24x8
mtx preview signups.json --gif signups-perchi.gif --mono
mtx publish signups.json --version 1.0.0

Configuration lives in ~/.config/mtx/config.json, readable only by you; MTX_API_URL, MTX_TOKEN and MTX_WORKSPACE_ID override it in CI. mtx validate runs the offline checks; provider rules and the full design checks run on the server during mtx publish. --mono previews the same app on Perchi's 128x64 monochrome screen.

signups.ts
import { defineApp } from "@mx/sdk";

export default defineApp({
  schema_version: 1,
  name: "Signups today",
  summary: "A number pushed from any script.",
  category: "generic",
  icon: "rocket",
  target: { min_width: 24, ideal: "24x8" },
  settings: [],
  sources: [{ alias: "push", kind: "push", fields: { value: "number" } }],
  scene: {
    layers: [
      { id: "icon", type: "icon", x: 0, y: 0, w: 8, h: 8, icon: "rocket" },
      { id: "count", type: "number", x: 9, y: 0, w: 15, h: 8, value: "{{count}}",
        format: { style: "compact" }, font: "normal5x8", color: "$accent", align: "right", stale: "mark" },
    ],
    bindings: { count: { ref: "push.value", stale_after_s: 3600, expire_after_s: 86400 } },
    triggers: [{ when: "delta_up", binding: "count", min_delta: 1, do: { effect: { effect: "confetti", ms: 800 } } }],
    bg: "#000000",
  },
  outputs_primary: "push.value",
});

defineApp() checks the definition against the same schema the server stores drafts with and returns it typed, so the file can be written to JSON, previewed and published with the CLI. The preview renderer is the one the displays use, so the GIF matches the LEDs pixel for pixel.

App builder

How do you build an app without code?

An app is a manifest, not a program: up to 8 data sources, a scene of up to 12 layers drawn on a 24x8 canvas with extra layouts for 32x8, 64x16 and 64x32, up to 4 triggers, and up to 12 settings that people fill in when they install it. The builder in the dashboard, the mtx CLI and the MCP server all produce the same definition, check it the same way and publish it privately or submit it to the store.

  • Sources

    Plugin output (reuse any catalogue app's data), JSON URL (public https only, JSONPath or Pointer, secret headers), Webhook, Push value, and Computed: sum, difference, ratio, percent, min or max of up to 4 fields.

  • Layers

    Text, Number, Icon, Progress, Chart, Clock, Date, Countdown, Shape, Bitmap and Group, with visibility rules (fresh, stale, above, below, after a rise or fall), bindings as {{name}} and number formats including Indian lakh and crore.

  • Triggers

    When a bound value rises, falls, crosses, changes or passes every multiple: play one of 14 effects, show a text overlay with {{value}} and {{delta}}, or hold the app on screen.

  • Checks

    References, allowed URLs, provider rules (no derived numbers where a provider forbids them, such as YouTube), missing glyphs, contrast, overflow and size. Errors block publishing; warnings link to the layer.

Local API

How does the local API work?

The local API is an HTTP server on the display itself, on port 80 at mx-<short id>.local, that implements an authenticated subset of the AWTRIX 3 API: notify, custom apps, switch, settings, power and indicators. It is off by default, an owner turns it on per display and gets a token once, and it keeps working with no internet at all. AWTRIX 3 is archived upstream; Perchi Matrix keeps that API alive for the scripts and automations you already have.

Bearer mode
# Read the display's status
curl -H "Authorization: Bearer $TOKEN" http://mx-7k3f.local/api/stats

# Show COFFEE for 5 seconds
curl -X POST http://mx-7k3f.local/api/notify \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"text":"COFFEE","duration":5}'

# Create a local app with an icon, then switch to it
curl -X POST "http://mx-7k3f.local/api/custom?name=desk" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"text":"42","icon":"heart"}'
curl -X POST http://mx-7k3f.local/api/switch \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"desk"}'

# HTTP Basic works too: user name mx, the token as the password
curl -u "mx:$TOKEN" http://mx-7k3f.local/api/apps
  • Bearer token or HTTP Basic (user name mx, the token as the password); the display stores only a hash of the token.
  • Optional signed-request mode: X-Mx-Ts and X-Mx-Sig (HMAC-SHA256 over method, path, timestamp and body hash), a 60-second window, every signature single-use.
  • Host header allow-list against DNS rebinding, any browser Origin refused, 30 requests a second, up to 8 local apps kept in memory, not available in safe mode.
  • AWTRIX coordinates assume 32x8; on a 24x8 display anything past the edge is cut off and center: true centres on the real width.
  • Announced over mDNS as _http._tcp with api=awtrix3 and mx=2.
EndpointMethodWhat it does
/api/statsGETBrightness, uptime, Wi-Fi signal, current app, version and a count of ignored keys; bat and lux read 0, temp and hum are null (no such sensors)
/api/settingsGET, POSTBRI, ABRI, ATRANS, TSPEED, ATIME, TCOL, UPPERCASE, TFORMAT, DFORMAT
/api/custom?name=<app>POSTCreates or updates a local app from an AWTRIX custom-app body; an empty body deletes it
/api/notifyPOSTShows a notification (text, icon, color, duration, effect and the other AWTRIX keys)
/api/notify/dismissPOSTDismisses the current notification
/api/appsGETThe current loop as [{ name }]
/api/switchPOSTSwitches to a named app
/api/nextapp, /api/previousappPOSTSteps through the loop
/api/indicator1, 2, 3POSTCorner dots: color, blink, fade
/api/powerPOSTDisplay on or off
/api/mx/capsGETNative: the capability report (size, fonts, effects, limits)
/api/mx/scenePOSTNative: shows a temporary MyPerchi scene for 1 to 3600 s, validated like cloud content
/api/screenGETNot supported: answers 501. Use the dashboard's Live mirror instead

Keys accepted by /api/custom and /api/notify: text (a string or coloured parts), textCase, topText, textOffset, center, color, gradient, blinkText, fadeText, background, rainbow, icon (a built-in id or base64 8x8), pushIcon, repeat, duration, hold, noScroll, scrollSpeed, progress, progressC, progressBC, bar, line, autoscale, barBC, draw (dp, dl, dr, df, dc, dfc, dt, db), lifetime, lifetimeMode, stack, wakeup, effect, overlay. Unknown keys are ignored and counted in the messages field of /api/stats.

Home Assistant

How does Perchi Matrix show up in Home Assistant?

Through MQTT discovery, with no custom component: turn on the MQTT broker under the display's Local API settings and Home Assistant creates a light (power and brightness), a text entity for notifications, a select entity for the current app and sensors for Wi-Fi signal and uptime. On the HTTP side a rest_command can call the local API in bearer mode or an automation webhook, and the Home Assistant value app shows any sensor an automation pushes.

rest_command
# configuration.yaml: call the display's local API in bearer mode (HMAC off)
rest_command:
  desk_notify:
    url: "http://mx-7k3f.local/api/notify"
    method: POST
    headers:
      Authorization: "Bearer <local API token>"
    content_type: "application/json"
    payload: '{"text":"{{ text }}","duration":5}'

# An automation then calls it
action: rest_command.desk_notify
data:
  text: "DOOR OPEN"
MQTT
# Topics the display listens to (prefix "matrix" by default)
matrix/notify           {"text":"HELLO","duration":5}
matrix/custom/<app>     same body as /api/custom (empty body deletes the app)
matrix/switch           {"name":"<app>"}
matrix/settings         {"BRI":64}
matrix/power            {"power":false}

# Topics the display publishes
matrix/stats            every 60 seconds
matrix/status           online | offline

Longevity

What keeps working without our servers?

The core loop never depends on our servers. Perchi's face, Phone Nap, streaks and clock run on the device with no Wi-Fi at all, and a block that finished offline syncs later. Perchi Matrix keeps its clock, date, text, countdowns and every cached value, marks stale values instead of inventing numbers and draws -- when a value expires. The local API and MQTT run on your LAN, and every firmware update is signed by a key that is kept offline.

On the device

Face, Phone Nap, streaks and clock on Perchi; clock, date, text, countdowns, cached apps and the 8 local apps on Perchi Matrix. Stale values are dimmed or marked, expired ones drawn as --, never a made-up number.

On your LAN

The authenticated local API and MQTT with Home Assistant discovery answer on your network without any server. A Live value that was pushed keeps its last value until its TTL runs out.

Signed updates

Firmware is signed by an offline key the device verifies before installing; a new image must confirm itself or the bootloader rolls back. A shutdown cannot push an unsigned image, and neither can we.

What does need the server: pairing a new device, fetching app data from providers, live pushes and the dashboard itself. Designed so a shutdown cannot brick it; the details of every mechanism, and what is still pending, are on the security page. Firmware and dashboard releases are dated on the changelog.

Questions from developers

Is there an OpenAPI spec for the MyPerchi API?

Yes. The REST API is described by an OpenAPI 3.1 document generated from the same contracts the dashboard uses, with a JSON envelope, cursor pagination, idempotency keys and request ids on every response. Tokens are scoped (nine scopes) and can never do more than the member who created them.

Can I push my own numbers to a display?

Yes. Create an ingest token and one curl line puts any number or short text on Perchi Matrix, or on Perchi as a card beside the face, with a time to live from 1 second to 2 hours. The limit is 60 requests a minute per token.

Does the MCP server work with Claude and other assistants?

Yes. Any MCP client that speaks Streamable HTTP connects with a bearer token and gets 12 tools: list and inspect devices, send a notification, set brightness, browse and install apps, push a value, and create, validate, preview and privately publish an app draft. The same permission checks apply as in the REST API, at 60 tool calls a minute.

Are there outbound webhooks?

No. Webhooks are inbound: you paste a URL into Stripe, Razorpay, GitHub or any of 15 provider presets and the display reacts. Notifications to you go to the in-app inbox and, if you opt in per kind, to email.

Is the local API encrypted?

No. The local API is plain HTTP on port 80 on your LAN, off by default, and authenticated with a token shown once or with HMAC-signed requests. Turn it on only on a network you trust; the token works only for that display and grants nothing in the cloud.

Can Home Assistant use the signed-request mode?

No. Home Assistant cannot sign requests, so turn HMAC mode off for that display and use the bearer token; MQTT discovery entities need the same. With HMAC on, Home Assistant's calls are refused rather than silently accepted.

Does the local API keep working without your servers?

Yes. The local API and MQTT run on the device and keep answering on your LAN whether or not it can reach us, and the core loop (the face, Phone Nap, the clock, timers and cached apps) keeps working offline. Firmware updates are signed and roll back by themselves, so a device you own stays yours to run.

Are the CLI and SDK included?

Yes. The mtx CLI (login, devices, send, push, validate, preview, publish) and the TypeScript SDK are part of every account, as are the REST API and the MCP server; there is no separate developer plan. Rate limits apply per token and are listed on the Developers page.

Building for a bigger canvas? The 32x8, 64x16 and 64x32 formats, their power budgets and what you can reserve today are on Perchi Matrix sizes; the product itself is on the Perchi Matrix page, and Perchi's own loop is explained in how Phone Nap works.

Batch 2 - estimated 2 to 3 weeks, no payment today

Put a real number on your desk

Perchi Matrix is ₹4,499 incl. GST (MRP ₹4,999). No subscription needed.

No payment today. We email a checkout link when your unit is bench-tested; current estimated wait 2 to 3 weeks from the day you reserve. Cancel any time before then.