Skip to main content

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/v1 on the old Axis host) keep working for existing scripts (What old addresses keep working).
  • JSON bodies with snake_case keys. Request schemas refuse unknown fields (422).
  • Authentication: Authorization: Bearer <product token>, a five-minute token from Entrosity Hub for the product rmm (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}. Maximum page_size is 200.
  • Idempotency: POST endpoints that create jobs accept an Idempotency-Key header.
  • 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:

  1. POST /auth/sse-token with {"tenant_id": "…"} returns a 60-second stream token bound to that tenant and your Hub session (signed by Axis with RMM_JWT_SECRET);
  2. open /axis/api/v1/tenants/{tenantID}/events?sse_token=<token>.

The product token is never accepted in the query string.

Endpoint groups​

GroupPaths
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​

  1. Edit entrosity-axis.backend/api/openapi.yaml first.
  2. make gen regenerates the Go server interface and the portal's TypeScript types; implement the handler.
  3. Add the route's access rule and a row in the RBAC matrix test.
  4. make api-docs regenerates docs/api/index.html. This site renders the spec directly.
  5. Update this documentation (Writing docs).