Skip to content

Overview

Public surface of the Ermeon control plane after the subscription cutover (13 §5.5, 14 §6). Deliberate breaking release of `public-v1`.

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 stable code.
  • 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-Match and 304 Not Modified.
  • Lists use cursor pagination: limit 1–200 and an opaque next_cursor.
  • Foreign identifiers are opaque: cross-tenant access returns 404, never 403.

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.

Information

  • OpenAPI version: 3.1.0

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