Skip to content

Fleet API (MSP control plane)

An MSP runs one console per client. Its control plane (Tenantry) polls every console for a short, read-only status: is it up, are its domain controllers answering, is its audit chain intact, how much happened in the last 24 hours. This page is the contract of that endpoint. It never returns directory objects (no user, group, OU or attribute).

Tokens

Created in Configuration › Fleet access by an operator with settings.manage, after a step-up (authenticator code, or the Teleport session). Give each control plane its own token with a name that says who uses it.

Property Value
Format stf_ followed by 43 base64url characters (32 random bytes, 256 bits), e.g. stf_3q2-7wZk...
Shown Once, in the creation dialog. It cannot be displayed again; if it is lost, revoke it and create another one.
Stored SHA-256 of the token only, with an id, the name, the creation time and operator, and the last use.
Limit 5 active tokens per console.
Revocation Configuration › Fleet access › Revoke (step-up). Effective on the next request.
Audit fleet.token.created and fleet.token.revoked (target = token id, value = the name; never the token). Every refused request: fleet.auth, result failed, reason missing, malformed or unknown, subject = client IP. A successful request is not audited (it only updates "last used", at most once a minute), so polling does not flood the log.

The token is the only secret: keep it in the control plane's secret store, never in a URL.

Request

GET /api/fleet/v1/status
Authorization: Bearer stf_...
  • The bearer token is the only accepted authentication. A console session cookie, the antiforgery token or a Teleport identity are ignored (a signed-in browser gets 401). No CSRF token is needed (read-only GET).
  • HTTPS only, like the rest of the console.

Responses

Status Body When
200 the status below valid token
401 {"code":"unauthorized"} + WWW-Authenticate: Bearer no header, wrong scheme, malformed, unknown or revoked token: always the same answer
429 {"code":"rate_limited"} too many failed attempts from this IP (below)
500 {"code":"internal_error","correlationId":"..."} unexpected error (details in the console logs)

Rate limit. Failed attempts are counted per client IP with the login throttle's per-IP budget (Throttle:PerIpAttempts attempts per Throttle:PerIpWindow, default 20 per 5 minutes). Once it is reached, every request from that IP gets 429 until the window slides, even with a valid token, and those refused requests are not verified nor audited again. Successful requests do not count (the general limit of 600 requests per minute per IP still applies).

Status (schema 1)

camelCase JSON. Times are ISO 8601 UTC (...Z). The contract is stable: members may be added in schema 1, never renamed, removed or given another meaning; a breaking change gets a new schema number (and a new path, /api/fleet/v2/...). Clients must ignore members they do not know and treat unknown enum values as "unknown".

{
  "schema": 1,
  "time": "2026-10-18T12:00:00.000Z",
  "instance": { "title": "Acme Corp (ad.acme.com)", "clientCode": "acme", "version": "1.0.0" },
  "health": {
    "status": "degraded",
    "checks": [
      { "code": "configured", "status": "ok" },
      { "code": "dcs_redundant", "status": "warning" },
      { "code": "audit_chain_intact", "status": "ok" }
    ]
  },
  "directory": {
    "domain": "ad.acme.com",
    "dcs": [ { "fqdn": "dc1.ad.acme.com", "status": "up" }, { "fqdn": "dc2.ad.acme.com", "status": "down" } ]
  },
  "audit": { "chain": "intact", "lastEventAt": "2026-10-18T11:58:41.000Z", "events24h": 1532, "denied24h": 3, "failedLogins24h": 1 },
  "helpdesk": { "enabled": true },
  "setup": { "complete": true }
}
Member Type Meaning
schema number 1
time string When this snapshot was computed. The status is cached 30 seconds: polling faster returns the same snapshot.
instance.title string Instance title (Configuration › Instance identification); "" when none. Plain text.
instance.clientCode string | null Client code, [a-z0-9-]{1,32}, or null.
instance.version string Console version.
health.status ok | degraded | down down: the console cannot serve operators (not set up, or no domain controller answers). degraded: any other check is not ok. ok: every check is ok.
health.checks[] {code, status} status is ok, warning or error. Codes below.
directory.domain string | null AD domain (FQDN), null before setup.
directory.dcs[] {fqdn, status} Configured domain controllers in failover order. up: last contact worked. down: last contact failed (cooling down). unknown: not contacted yet, or not tested since a failure.
audit.chain intact | broken | unknown Latest audit-chain verification (below).
audit.lastEventAt string | null Time of the newest audit row.
audit.events24h number Audit rows in the last 24 hours.
audit.denied24h number Refused actions in the last 24 hours (as the dashboard; simulations excluded).
audit.failedLogins24h number Failed sign-ins in the last 24 hours.
helpdesk.enabled boolean Helpdesk (password service) enabled on this console.
setup.complete boolean First-run setup finished.

Health check codes

Code Status Meaning
configured ok Setup is complete.
not_configured error Setup is not finished: the console is down.
dcs_redundant ok / warning Several DCs configured; warning when at least one is down.
single_dc warning Only one DC configured: a single point of failure (same as the console diagnostics).
directory_unavailable error Every configured DC is down: the console is down.
audit_chain_intact ok The latest verification found the chain intact.
audit_chain_broken error The latest verification found a modified or removed row: investigate (see operations.md).

New codes may be added; treat an unknown code by its status.

How it stays cheap

  • No directory call per request: DC states come from the console's failover memory, fed by real traffic and by the background DC health probe (Directory:HealthProbeInterval, default 5 minutes).
  • The audit chain is never verified per request. The status reports the latest verification: the periodic background check (Audit:VerifyInterval, default 1 hour; first run at start-up; 0 disables it) or the last Verify run from the audit page. unknown until one has run since the console started.
  • Counters are one indexed query over the last 24 hours, and the whole snapshot is cached 30 seconds.

Example

curl -fsS -H "Authorization: Bearer $STF_TOKEN" https://steward.acme.local:8443/api/fleet/v1/status