Docs
REST API (Pro)
A documented, stable API for MSPs’ own scripts and tools: RMM onboarding, dashboards, approval workflows, change
management, billing. Licensed with the api_tokens feature (Pro). Agreed 2026-10-13.
- Base URL:
https://<console>/api/v1. - Docs: Swagger UI at
/api/v1/docs, for signed-in users of a licensed console. - Spec: the OpenAPI document at
/api/v1/openapi.json, also committed as api-v1.json, so it can be shown before buying.
Tokens
curl -H "Authorization: Bearer mrsh_..." https://marshal.example.com/api/v1/me
- Who makes them: every user, under Account › API tokens. Admins also see and revoke everyone’s under Integrations › API tokens.
- Shown once: only a token’s sha256 is stored. The list shows its prefix (
mrsh_xxxxxx…), when and from where it was last used, and its state. - Never more than the owner can do: their role and their customers.
- A token can be narrowed further: read-only, and/or some of the owner’s customers.
- Disabling the user stops their tokens.
- Every token expires: after 30, 90, 180 or 365 days.
- Audit: changes made with a token are audited under its owner, with
api_tokennaming the token. - Only on
/api/v1: a token works nowhere else, so it can’t reach users, settings or the license.- Calls with a token need no
X-Requested-Withheader: a token isn’t a cookie, so cross-site forms can’t send one. - Browser sessions still need the header for writes. The docs page adds it.
- Calls with a token need no
- Rate limit: at most 600 requests a minute per token (
429withRetry-After).
What it covers
| Area | Read | Change |
|---|---|---|
| Me | GET /me | |
| Customers | GET /customers, /customers/{id} | POST /customers/{id}/scan |
| Servers | GET /servers (filters, paged), /servers/{id}, /servers/{id}/updates, /servers/{id}/packages (Linux) | POST /servers/{id}/scan |
| Compliance | GET /compliance | POST /approvals (approve, decline, reset) |
| Runs | GET /runs (paged), /runs/{id} | POST /runs, POST /runs/{id}/abort (admins) |
| Windows, blackouts | GET /windows, GET /blackouts | create, edit, delete |
| Enrollment | POST /enrollment-tokens (returns the install script and one-liners) | |
| Reports | GET /reports, /reports/{id}/pdf |
Users, settings, the license and the tokens themselves stay in the console. Writing needs a read-write token and the operator role (admin for runs), as in the console.
Status codes:
| Code | Meaning |
|---|---|
| 401 | no token, or a bad, expired or revoked one |
| 402 | not licensed |
| 403 | not allowed (a read-only token, or the role) |
| 404 | not found, or not one of your customers |
| 409 | conflict |
| 422 | invalid input |
| 423 | Marshal is read-only (expired license): reads still work |
| 429 | too many requests |
Stability
-
v1 only gains fields. Its responses are the models in
app/api/v1/routes.py, built from the console’s own answers. The console’s internal/apican change freely. -
The spec is checked on every test run.
tests/test_api_v1.py::test_spec_is_committedcompares the generated OpenAPI document withdocs/api-v1.json. After a deliberate, additive change, regenerate it:docker run --rm -v ./backend:/app marshal:latest python -m app.tools.openapi > docs/api-v1.json -
Breaking changes go into
/api/v2.
How it works
app/api/v1/is its own FastAPI app, mounted at/api/v1.auth.pyworks out the caller: a token, or a signed-in browser for the docs page. It then checks the rate limit, then the license (401 before 402, as for premium routes).routes.pycalls the console’s endpoint functions with the caller’s user, role and customers, so the checks and the audit log are the same as in the UI.
- Tokens:
services/api_tokens.py, theapi_tokenstable, and the console’s API atapi/premium/api_tokens.py. - Swagger UI is served from the console’s own files (
/swagger/, copied fromswagger-ui-distat build), because the CSP allows no outside scripts.