Endpoints
All routes are under /v1. Every route except health requires a bearer token.
| Method & path | Auth | Purpose |
|---|---|---|
GET /v1/health | public | Liveness check. |
POST /v1/resolve | bearer | Native package names → canonical slugs for a release. |
POST /v1/translate | bearer | Canonicals → target packages (+ confidence, caveats, conditionals). |
POST /v1/config-paths | bearer | Canonicals → config-path relocations for a release pair. |
POST /v1/usage | bearer | Record a depersonalized VM usage event (preflight / readiness / cve_scan / drift / migration / verify). |
POST /v1/authorize | bearer | Pre-check whether a usage event would be allowed (the non-recording twin of /v1/usage); returns the gate decision without claiming a seat. |
POST /v1/enroll | bearer | License (seat) a VM by fingerprint — the lightweight first-class alternative to seating via a full migrate/verify. Capacity-gated (402 quota); idempotent by fingerprint. |
POST /v1/cve-scan | bearer | Match a VM's packages against advisories (license-gated; never metered; auto-claims a seat when the account has capacity). |
GET /v1/account | bearer | The account's billing/usage summary. |
GET /v1/account/usage | bearer | The account's billable-operation events, newest first. Paginated (limit default 50, max 1000; offset). |
GET /v1/account/vms | bearer | Every licensed VM on the account, with when it first became billable, its last activity, and the operation kinds it has run. Paginated. |
GET /v1/account/statement | bearer | The account's billing statement (instances, rate, annual total). |
GET /v1/account/capacity | bearer | Provisioned licence capacity and the active term (seats granted, valid-from / valid-until), plus a per-term terms breakdown. |
GET /v1/account/entitlements | bearer | Alias of /v1/account/capacity — same payload. |
GET /v1/account/terms | bearer | Alias of /v1/account/capacity — same payload. |
POST /v1/findings | bearer | File a finding (bug / coverage / measurement). Hostnames, domains and paths are stripped. Not billable. |
GET /v1/findings | bearer | This account's findings, newest first (default 50, max 100). |
GET /v1/findings/:id | bearer | One finding belonging to this account. 404 if missing or not yours. |
POST /v1/findings/:id/resolve | bearer, plan internal | Mark fixed. Body is { "channel": "stable" | "beta" } only — the tag is the newest staged release on that channel, not a caller string. |
POST /v1/questions | bearer | File a question. Hostnames, domains and paths are stripped. Not billable. |
GET /v1/questions | bearer | This account's questions, newest first (default 50, max 100). |
GET /v1/questions/:id | bearer | One question belonging to this account. 404 if missing or not yours. |
POST /v1/questions/:id/answer | bearer, plan internal | Write the team's answer. |
Metering & usage
resolve, translate, and config-paths each record a metered request against the account (recorded, not charged — billing is per-instance-seat, not per-lookup). POST /v1/usage records a per-VM event whose kind is one of:
preflight— a feasibility check; free and ungated;readiness— a fleet feasibility check; free and ungated;migration— a real migration; a billable seat kind (claims a paid instance seat, 402 quota-gated);verify— a signed parity attestation; also a billable seat kind (claims the same per-VM seat);cve_scan— a CVE scan; license-gated and never metered. On an unlicensed VM it auto-claims a lightweightlicenseseat when the account has capacity (VRTMV-620), and is refused with402only when capacity is genuinely exhausted. The response carriesseat_claimed: trueon the call that claimed the seat andfalseotherwise;drift— ongoing monitoring; license-gated and never metered, but unlikecve_scanit requires a pre-existing license — refused with402 UNLICENSED_VMon a VM not already seated by amigration,verify, orlicense.
A seat is deduped per VM fingerprint across the billable kinds, so a migration and a later verify of the same VM claim one seat, not two.
The body may also carry host_hint: 64 lowercase hexadecimal characters, a one-way hash of the host's hardware id keyed with its machine-id (see Where your data goes). It is optional, and a body without it is recorded as before. Any other value is refused with 400 and nothing is recorded. The hint is stored with the event and counts the hosts that report under one fingerprint; it changes no seat, gate or charge.
POST /v1/enroll is a third way to claim that same seat, on its own. It records a license event — also a billable seat kind — from just a fingerprint (plus the host's OS for the statement), with no translation or attestation. It is the first-class licensing verb: seating a VM no longer has to be a byproduct of a minutes-long migrate/verify. It is capacity-gated exactly like migration/verify (402 quota when the account is out of seats), never meters an overage, and is idempotent — re-enrolling a seated VM claims nothing new, and a later migration/verify of an enrolled VM does not claim a second seat (same fingerprint dedup). Request body is { "vm_fingerprint": "...", "source": { "distro": "...", "version": "..." } }; the response is { "seated": true, "already_licensed": <bool> }.
Capacity and terms
GET /v1/account/capacity (aliases /v1/account/entitlements and /v1/account/terms) reports what the account holds and through when. It returns both a pooled summary and a per-term breakdown:
{
"account_id": 42,
"licensed_seats": 500,
"license_valid_from": "2026-01-15",
"license_valid_until": "2029-01-14",
"active_pov_seats": 20,
"pov_ends_at": "2026-12-31",
"free_quota": 10,
"billable_vms_used": 214,
"terms": [
{ "kind": "license", "source": "subscription", "seats": 480, "valid_from": "2026-01-15", "valid_until": "2029-01-14", "note": null },
{ "kind": "license", "source": "manual", "seats": 20, "valid_from": "2026-06-01", "valid_until": "2027-05-31", "note": "PO reference" },
{ "kind": "pov", "source": null, "seats": 20, "valid_from": "2026-10-02", "valid_until": "2026-12-31", "note": null }
]
}
The pooled fields (licensed_seats, license_valid_from, license_valid_until, active_pov_seats, pov_ends_at) collapse every active grant into one figure: licensed_seats is the total active licence seats and license_valid_until is the latest end across them. For an account whose seats run through different dates that single term is the longest one, which is why the breakdown exists.
free_quota is the account's free allowance and billable_vms_used is the count of billable VMs (the set /v1/account/vms enumerates), so free_quota + licensed_seats + active_pov_seats is total provisioned capacity and billable_vms_used is consumption. Both are account-level pool figures, never a per-VM assignment.
terms un-collapses that. Each entry is one active term:
kindislicenseorpov.sourceis the licence grant source (subscription,manual, orpromoted-from-pov) for a licence entry, andnullfor a POV (kindalready names it).seatsis the seat count for that term. Licence terms group active grants by (start, end, source, note); a POV term is a single POV.valid_from/valid_untilare the inclusive start and the human last-active day (YYYY-MM-DD). The stored upper bound is exclusive, sovalid_untilis the day before it.noteis the grant's commercial context (a PO number, an amount — shape only), ornull.
Only currently-active capacity appears — the same set the 402 seat gate counts. A revoked, expired, not-yet-started, or converted-POV grant is excluded from both the pooled totals and terms. The array is empty when the account holds no active capacity.
Per-VM visibility and usage history
GET /v1/account/vms
Every VM that has done a billable operation on the account — the per-host view of what is licensed. Paginated with limit (default 50, max 1000) and offset; page until has_more is false.
{
"vms": [
{
"vm": "a3f9c21b4e07",
"first_billable_at": "2026-02-04 16:22:08+00",
"licensed": true,
"last_seen_at": "2026-09-30 08:11:52+00",
"kinds": ["cve-scan", "migration"]
}
],
"limit": 50,
"offset": 0,
"count": 1,
"has_more": false
}
vmis the first 12 characters of the VM fingerprint — a stable per-VM key within the account.first_billable_atis when the VM first did a billable operation: its "licensed since" date.licensedis alwaystruefor a listed VM — membership in this list is being licensed, and it is permanent (re-running work on the VM is never blocked and never claims a new seat).last_seen_atis the most recent operation of any kind on the VM.kindsis the distinct operation kinds the VM has run, sorted.
This reports, per VM, whether it is licensed and since when. It does not label a VM with a seat type (paid / free / POV): seats and POV are account-level pools with no per-VM assignment, so there is no per-VM seat kind to report. Use /v1/account/capacity for the pool totals.
GET /v1/account/usage
The account's billable-operation events, newest first (at descending, id the stable tiebreak). Paginated with limit (default 50, max 1000) and offset; page until has_more is false to read the entire history.
{
"events": [
{
"id": 48213,
"kind": "migration",
"vm": "a3f9c21b4e07",
"source": "CentOS Linux 7",
"target": "Rocky Linux 9",
"at": "2026-09-30 08:11:52+00"
}
],
"limit": 50,
"offset": 0,
"count": 1,
"has_more": false
}
limit and offset echo the effective (clamped) values used; count is the number of events returned; has_more indicates a further page exists; id is a stable per-event key for deduping across pages.
Errors
Errors return a JSON {"error": "..."} with a standard status code:
| Status | Meaning |
|---|---|
400 | Malformed request (empty batch, over the 1000-item limit, bad kind). |
401 | Missing, invalid, or disabled token. |
402 | Seat quota reached (a billable migration / verify / license, or an auto-enrolling cve_scan with no capacity left), or UNLICENSED_VM for a drift on a VM with no seat — see Accounts, quota & billing. |
404 | Unknown release, artifact, or token id. |