curl:
Endpoint reference
Management endpoints are API-key authenticated withAuthorization: Bearer mantis_live_....
Dashboard helper endpoints that need browser access also accept the mantis_session
httpOnly cookie. Public trigger, status, health, wallet, and dev-inbox routes
are called out explicitly below.
Parsed request bodies are bounded.
POST /api/keys and
POST /api/api-keys reject JSON bodies over 64 KiB with
413 payload_too_large. Apple Wallet log bodies are capped at 32 KiB, and the
dev inbox capture path is capped at 1 MiB.
Supported installer type values are shell, shell-sudo,
macos-login, macos-boot, macos-wake, macos-network,
linux-boot, linux-wake, linux-network, windows-logon,
windows-wake, windows-network, css-background, js-clone-detector,
nfc-ndef, homeassistant, homeassistant-receiver, and scrypted.
Webhook destinations get an HMAC secret. Outbound raw-webhook deliveries include
X-Mantis-Timestamp and X-Mantis-Signature: sha256=<hex> over
<timestamp>.<json body>. The plaintext secret is only shown on create, replace,
explicit reveal, or rotate responses; normal listing returns a fingerprint.
API key scope: full vs enroll
Every API key carries ascope, orthogonal to is_admin:
full(default) — behaves as described throughout this page. Subject to the admin / non-admin visibility rules.enroll— create-only. An enroll key may call onlyPOST /api/keys. Every other management route (list/read/update/delete keys — including the ones it created — plus/api/hits/recent,/api/api-keys, the audit log, and any session-reachable route) returns403 forbidden, and an enroll key cannot log in to the dashboard.is_admin: truetogether withscope: "enroll"is rejected at validation.
- It can confirm and retrieve any key whose
external_idit guesses. APOST /api/keysthat collides with an existingexternal_idreturns that key’s trigger URL, memo,public_idand expiry ("reused": true, HTTP200) — see Idempotent creation — regardless of which API key created it.mantis devicederivesexternal_ids deterministically asmantis:device:<os>:<normalized-name>:<slug>, so an attacker who knows your naming convention can guess a machine’s ids and read back that machine’s canary URLs, which is exactly what lets an intruder route around the tripwires. Each such claim is recorded in the audit log askey.claimed. - It can supply
destinationson creation, and Mantis fires the activation ping synchronously — so the key can make your instance POST to an attacker-chosen HTTP(S) endpoint (private, loopback and metadata addresses are rejected unlessALLOW_PRIVATE_WEBHOOKS=1) or, ifSMTP_URLis set, send it mail.
deploy/kandji/.
Idempotent creation
POST /api/keys also accepts an optional external_id (1–128 chars, matching
^[A-Za-z0-9][A-Za-z0-9._:-]*$) stored on a unique column. When supplied, a
repeat POST with the same external_id returns the existing key —
"reused": true with HTTP 200 instead of 201 — rather than minting a
duplicate. The other body fields (memo, destinations, …) apply only when the
row is actually created; a later claim never mutates what the key was first
configured with. Keys created without an external_id are unaffected (unique
constraint treats NULLs as distinct).
This is the mechanism the fleet-enrollment flow relies on — one key per machine
serial, so re-running enrollment on a reimaged machine reuses its key instead of
littering the list. Enroll-scoped callers (and callers claiming another creator’s
external_id) get a reduced response shape — trigger URL and identity only, no
alert routing or signing secrets. A claim that races a concurrent delete returns
409 conflict; retry.
Response kinds for the trigger endpoint
Webhook payload
host_context object when the hit came from one of our installer snippets:
shell-sudo hit you’d see "source": "shell-sudo", "sudo_cmd": "apt update --quiet". For a linux-network hit, "source": "linux-network", "network_interface": "wlan0". Fields not relevant to the installer are null.
host_context is null for hits that didn’t include X-Mantis-* headers (e.g., a file/folder key, or a regular curl to the URL).
Webhooks are sent through a Postgres-backed retry queue (no Redis required). On failure the notification is retried with exponential backoff: 1m, 5m, 30m, 2h, 12h (each with ±20% jitter), giving up after 5 attempts. Delivery state is tracked on each hit’s notifications array.