Portal API
The portal is a plain client of a documented REST API under /api/v1.
Everything the portal does, you can do with the API.
The full endpoint reference is the API reference,
rendered from entrosity-axis.backend/api/openapi.yaml. That spec is the source of
truth: the server's router and request validation are generated from it,
and the TypeScript types of the portal too, so the reference cannot drift
from the code.
Conventions
- Base paths: portal
/api/v1, agent/api/agent/v1, connector/api/connector/v1(the last two are on Agent and connector API). Browsers and new scripts reach it under Axis's path on the Hub's host,https://hub.entrosity.com/axis/api/v1(Caddy strips/axis). Earlier addresses (/manage/api/v1, and/api/v1on the old Axis host) keep working for existing scripts (What old addresses keep working). - JSON bodies with
snake_casekeys. Request schemas refuse unknown fields (422). - Authentication:
Authorization: Bearer <product token>, a five-minute token from Entrosity Hub for the productrmm(POST /api/platform/v1/auth/product-token, Hub API). Axis has no sign-in endpoints of its own; users, tenants and roles are managed on the Hub, and Axis's user and tenant lists are read-only. - Tenant scoping: tenant users may only call
/tenants/{their tenant id}/…; any other tenant ID answers 404. - Lists:
?page=1&page_size=50&sort=-last_seen_at&q=…→{"items": […], "page": 1, "page_size": 50, "total": 1234}. Maximumpage_sizeis 200. - Idempotency:
POSTendpoints that create jobs accept anIdempotency-Keyheader. - Every response carries
X-Request-ID. - Rate limit: 20 requests per second per client IP, burst 60
(
RMM_API_RATE_PER_SECOND,RMM_API_RATE_BURST); request bodies up to 1 MB.
Errors
Errors are RFC 7807 application/problem+json with a stable code:
{
"type": "/problems/validation",
"title": "Validation failed",
"status": 422,
"code": "validation",
"detail": "validation failed",
"fields": { "name": "minimum string length is 1" },
"request_id": "…"
}
All codes are listed in Error codes.
Signing in from a script
Sign in on the Hub once (its session cookie goes into a cookie jar), then get product tokens from the session as they expire:
HUB=https://hub.entrosity.com
# 1. Sign in on the Hub (add "totp_code" when two-factor authentication is on)
curl -s -c jar -b jar "$HUB/api/platform/v1/auth/login" \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"…"}'
# 2. A product token for Axis (valid 5 minutes; repeat when it expires)
TOKEN=$(curl -s -c jar -b jar "$HUB/api/platform/v1/auth/product-token" \
-H 'Content-Type: application/json' -d '{"product":"rmm"}' | jq -r .token)
# 3. Call Axis's API
curl -s "$HUB/axis/api/v1/me" -H "Authorization: Bearer $TOKEN"
The Hub rate-limits sign-ins, not product tokens: keep the cookie jar and mint a new token rather than signing in again.
Live events (SSE)
GET /tenants/{tenantID}/events is a server-sent event stream of the
tenant's changes (device status, jobs, deployment progress, script output,
alerts, AD sync). Browsers cannot send headers with EventSource, so:
POST /auth/sse-tokenwith{"tenant_id": "…"}returns a 60-second stream token bound to that tenant and your Hub session (signed by Axis withRMM_JWT_SECRET);- open
/axis/api/v1/tenants/{tenantID}/events?sse_token=<token>.
The product token is never accepted in the query string.
Endpoint groups
| Group | Paths |
|---|---|
| Health | /healthz |
| Event streams | /auth/sse-token |
| Me | /me (the user with their tenants and roles), /tenants/{id}/me/notification-prefs |
| Admin | /admin/overview, /admin/tenants and /admin/tenants/{id} (list, read, PATCH of settings only), /admin/users (the global admins, read-only), /admin/audit, /admin/packages…, /admin/winget/search, /admin/scripts…, /admin/alert-rules…, /admin/agent-releases… |
| Tenant | /tenants/{id} (PATCH of settings only), /dashboard, /users (members and their Axis role, read-only), /sites…, /audit, /events, /enrollment-tokens… (deleting one takes a Hub step_up_token) |
| Devices | /tenants/{id}/devices… (list, detail, inventory sections, metrics, jobs, actions, merge, bulk actions), /jobs/{jobID}, /jobs/{jobID}/cancel |
| Active Directory | /connectors…, /ad-sync/configs…, /ad-computers… |
| Packages and deployments | /packages…, /winget/search, /deployments… |
| Alerts | /alerts, /alerts/acknowledge, /alerts/resolve, /alert-rules… |
| Scripts | /scripts…, /script-runs… |
Who may call what is defined per route in
entrosity-axis.backend/internal/http/portal/access.go and summarised in
Roles and permissions.
Changing the API
- Edit
entrosity-axis.backend/api/openapi.yamlfirst. make genregenerates the Go server interface and the portal's TypeScript types; implement the handler.- Add the route's access rule and a row in the RBAC matrix test.
make api-docsregeneratesdocs/api/index.html. This site renders the spec directly.- Update this documentation (Writing docs).