Flotera Platform
Overview
Public surface of the Ermeon control plane after the subscription cutover (13 §5.5, 14 §6). Deliberate breaking release of `public-v1`.
Flotera Public API 2.0.0
Section titled “Flotera Public API 2.0.0”Account-scoped public API consumed by the panel and by customer
automation. Two customer credentials exist: the browser session cookie
and the Personal Access Token (pat, Bearer).
Personal Access Tokens are a PLAN CAPABILITY, not a fixed part of the
surface: api.customer_tokens.enabled decides whether a tenant may issue
and list them. The /tokens operations are therefore present on every
plan and answer 403 feature_not_entitled where the capability is off —
never 404. This is a deliberate design choice, not a leak: an operation
that vanishes with the plan cannot be enabled for a single tenant without
a new contract release, and a client cannot distinguish “your plan does
not include this” from “this endpoint does not exist”. Revoking a token is
exempt from the gate — see /tokens/{token_id}/revoke.
Normative sources:
infrastructure/03-contracts.md— wire rules, error model, endpoint table (§3.2);infrastructure/13-product-update-plan.md§5.4–§5.6 — lifecycle enforcement, target operations, authorization order;infrastructure/14-wallet-billing-nowpayments.md§3, §6 — subscription transitions, top-up and billing projections;infrastructure/02-target-core-architecture.md§9.1 — read models and caches;infrastructure/04-storage.md— authoritative storage shapes;infrastructure/05-identity-security.md§5.2 — session scope vocabulary.
Wire conventions:
- Monetary values are decimal strings (
^-?[0-9]+(\.[0-9]{1,6})?$), never JSON numbers. - Timestamps are RFC 3339 UTC with microsecond precision.
- Errors use
application/problem+json(RFC 9457) with a stablecode. - User mutations accept
Idempotency-Key(1–128 ASCII, unique per(tenant_id, operation, key)for at least 24 hours). - Read-model reads support
ETag/If-None-Matchand304 Not Modified. - Lists use cursor pagination:
limit1–200 and an opaquenext_cursor. - Foreign identifiers are opaque: cross-tenant access returns
404, never403.
Authorization order (13 §5.6), enforced by the API in exactly this sequence: credential validity → tenant isolation → session scope → plan entitlement → account lifecycle → resource limit → domain preconditions.
Account lifecycle is declared per operation, never inferred from the HTTP
method: every operation carries x-ermeon-frozen: allow|deny, and a
frozen tenant calling a deny operation receives 403 account_frozen.
A missing annotation is treated as deny by the runtime and rejected by
contracts/ci/check.py, so a new route cannot become accidentally
reachable while an account is frozen.
Authentication
Section titled “Authentication”sessionCookie
Section titled “sessionCookie”Browser session cookie (Secure; HttpOnly; SameSite=Lax; Path=/).
Cookie-authenticated unsafe methods additionally require the
session-bound CSRF token and an Origin check (05 §4.1); the CSRF
header is enforced by the edge and intentionally not modeled per
operation here.
Security scheme type: apiKey
Cookie parameter name: __Host-erm_session
Personal Access Token (erm_pat_...). Effective permissions are the intersection of membership role, token scopes and caveats (server allowlist, expiry, source CIDR). Agent tokens are not accepted by the public API.
Issuing and listing PATs is a PLAN CAPABILITY (api.customer_tokens.enabled). The /tokens operations therefore always exist and answer 403 feature_not_entitled when the plan does not include them — deliberately NOT 404. A route that disappears with the plan cannot be turned on for one tenant without a new contract release, and the client cannot tell “your plan lacks this” from “this endpoint never existed”.
Security scheme type: http
Bearer format: erm_pat