Endpoints

All routes are under /v1. Every route except health requires a bearer token.

Method & pathAuthPurpose
GET /v1/healthpublicLiveness check.
POST /v1/resolvebearerNative package names → canonical slugs for a release.
POST /v1/translatebearerCanonicals → target packages (+ confidence, caveats, conditionals).
POST /v1/config-pathsbearerCanonicals → config-path relocations for a release pair.
POST /v1/usagebearerRecord a depersonalized VM usage event (preflight / readiness / cve_scan / drift / migration / verify).
POST /v1/authorizebearerPre-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/enrollbearerLicense (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-scanbearerMatch a VM's packages against advisories (license-gated; never metered; auto-claims a seat when the account has capacity).
GET /v1/accountbearerThe account's billing/usage summary.
GET /v1/account/usagebearerThe account's billable-operation events, newest first. Paginated (limit default 50, max 1000; offset).
GET /v1/account/vmsbearerEvery 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/statementbearerThe account's billing statement (instances, rate, annual total).
GET /v1/account/capacitybearerProvisioned licence capacity and the active term (seats granted, valid-from / valid-until), plus a per-term terms breakdown.
GET /v1/account/entitlementsbearerAlias of /v1/account/capacity — same payload.
GET /v1/account/termsbearerAlias of /v1/account/capacity — same payload.
POST /v1/findingsbearerFile a finding (bug / coverage / measurement). Hostnames, domains and paths are stripped. Not billable.
GET /v1/findingsbearerThis account's findings, newest first (default 50, max 100).
GET /v1/findings/:idbearerOne finding belonging to this account. 404 if missing or not yours.
POST /v1/findings/:id/resolvebearer, plan internalMark fixed. Body is { "channel": "stable" | "beta" } only — the tag is the newest staged release on that channel, not a caller string.
POST /v1/questionsbearerFile a question. Hostnames, domains and paths are stripped. Not billable.
GET /v1/questionsbearerThis account's questions, newest first (default 50, max 100).
GET /v1/questions/:idbearerOne question belonging to this account. 404 if missing or not yours.
POST /v1/questions/:id/answerbearer, plan internalWrite 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 lightweight license seat when the account has capacity (VRTMV-620), and is refused with 402 only when capacity is genuinely exhausted. The response carries seat_claimed: true on the call that claimed the seat and false otherwise;
  • drift — ongoing monitoring; license-gated and never metered, but unlike cve_scan it requires a pre-existing license — refused with 402 UNLICENSED_VM on a VM not already seated by a migration, verify, or license.

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:

  • kind is license or pov.
  • source is the licence grant source (subscription, manual, or promoted-from-pov) for a licence entry, and null for a POV (kind already names it).
  • seats is 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_until are the inclusive start and the human last-active day (YYYY-MM-DD). The stored upper bound is exclusive, so valid_until is the day before it.
  • note is the grant's commercial context (a PO number, an amount — shape only), or null.

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
}
  • vm is the first 12 characters of the VM fingerprint — a stable per-VM key within the account.
  • first_billable_at is when the VM first did a billable operation: its "licensed since" date.
  • licensed is always true for 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_at is the most recent operation of any kind on the VM.
  • kinds is 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:

StatusMeaning
400Malformed request (empty batch, over the 1000-item limit, bad kind).
401Missing, invalid, or disabled token.
402Seat 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.
404Unknown release, artifact, or token id.