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-onlyGET). - 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;0disables it) or the last Verify run from the audit page.unknownuntil 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