Skip to main content

Product plane (catalog & keys)

The shared product-face commerce plane (F2). Vagary is tenant #0. Every capability is sold through this one plane; per-capability edge modules (a later wave) plug in on top. Product->capability edges only.

  • Group: Sellable plane
  • Contract: contracts/product-plane/product/v1/openapi.yaml
  • Console: internal — operated by Vagary Labs; no customer console page for this capability.
  • Runbook: operational checklist for product-plane — internal (Vagary Labs ops; not part of this public site): docs/runbooks/capability-operations.md#product-plane
  • Public access: none — internal-only capability. It is NOT exposed on the public API gateway (https://api.vagarylabs.com); it is reachable only inside the fleet (container/tailnet) by first-party callers. There is no customer-facing endpoint to call.

Endpoints​

MethodPathSummary
GET/internal/voice/orders/{order_id}Read exact historical accepted terms through Billing's dedicated binding
POST/signupTenant self-onboarding — mint an org, provision the first user in identity, issue an org-scoped API key
POST/account/organizationCreate an organisation for the signed-in principal, who becomes its owner
GET/signup/contextWhat this organisation's signup established — read by the product's own onboarding
POST/onboardAlias of /signup (tenant self-onboarding + first key)
GET/api-keysList the org's API keys (metadata only — never the raw key or hash)
POST/api-keysIssue a new API key scoped to (org, product). Returns the raw key ONCE.
POST/api-keys/{prefix}/rotateRotate — mint a fresh raw key (preserving product/label/scopes), revoke the old prefix, chain rotated_to
POST/api-keys/{prefix}/suspendSuspend an active key (ACTIVE -> SUSPENDED)
POST/api-keys/{prefix}/reactivateReactivate a suspended key (SUSPENDED -> ACTIVE)
DELETE/api-keys/{prefix}Revoke a key permanently (terminal)
GET/api-keys/{prefix}/statsWindowed usage stats for a key
GET/projectsList the org's projects (creates the default pair if the org has none)
POST/projectsCreate a project
GET/projects/{project_id}/environmentsList a project's environments
POST/projects/{project_id}/environmentsCreate an environment inside a project
GET/bindingsList the org's capability bindings, optionally narrowed to one project/environment
POST/bindingsCreate a capability binding
POST/bindings/planProduce a server-derived lifecycle plan for a capability binding
POST/bindings/applyApply a previously planned binding lifecycle change
GET/bindings/{binding_id}Get one capability binding by id
POST/bindings/{binding_id}Unsupported on the binding id root
POST/bindings/{binding_id}/statusSet a binding lifecycle status directly
GET/bindings/{binding_id}/reconcileCompose the binding row with its current effective grant and plan visibility
GET/productsProduct catalog — each product's granted capabilities (the plane owns the catalog; the edge enforces cap ∈ product.caps). PUBLIC (no bearer required, for the pre-auth signup wizard); when a bearer IS presented, the caller's resolved org's bound catalog policy overlays its white-label display_name onto the same full list — see resolve_display_overrides. Not org-scope-FILTERING (that's GET /org-catalog); every product still appears.
GET/plansProduct-face priced plan catalog (I5 — named/priced plans over billing-metering's opaque tiers)
GET/voice/order-termsPreview server-owned Voice terms for the verified organization owner
POST/voice/ordersPersist an immutable owner acceptance of complete server-owned terms
GET/voice/orders/{order_id}Read immutable accepted terms for the verified organization owner
POST/checkoutCreate a subscription/checkout intent — delegates the charge to billing-metering (402 = charge-OFF gate)
GET/usageUsage summary (proxies billing-metering /v1/usage/summary for the resolved org)
GET/quota/checkQuota check (proxies billing-metering /v1/quota/check) — enforce before granting a metered action
GET/resale-pricingList the caller's own org's current resale prices (optionally filtered to one sku)
PUT/resale-pricing/{sku}Set (replacing any prior value) the caller's own org's current resale price for a SKU. Owner-gated.
DELETE/resale-pricing/{sku}Stop reselling a SKU at a stated price (does not affect catalog_policy exposure). Owner-gated.
GET/healthliveness
GET/metricsPrometheus text exposition

Schemas​

CreatedOrganization​

FieldTypeDescription
organization_idstring
namestring
realmstringtaken from the caller's verified token, never from the body
rolestringthe creator is always the owner
planstring
stepsobjectThe provisioning steps, reported rather than assumed. Reuses signup's own path so the org/owner/project/environment/entitlement relationship is the SAME one FA1 line 4 proves — a second implementation would be a second definition of correct.

SignupContext​

FieldTypeDescription
organization_idstring
foundbooleanfalse when this organisation has no signup transaction — normal, not an error. Onboarding falls back to asking rather than treating an empty body as a failed lookup.
contextobject

SignupRequest​

FieldTypeDescription
emailstring
passwordstring
namestring
realmstringidentity realm (per-brand); defaults to the plane's DEFAULT_REALM
organization_idstringoptional — omit to mint a fresh org (tenant self-onboarding)
productstringwhich capability/product the first key grants access to
planstringproduct-face plan id (free/pro/enterprise)

SignupResult​

FieldTypeDescription
organization_idstring
userobject
planstringthe requested/selected plan id — always echoed, whether or not it was applied (see plan_applied)
plan_appliedbooleanwhether plan is actually held by billing-metering right now. True only for a $0 plan whose grant round-tripped 200 (POST /v1/entitlement, ADR-120); false for a paid plan (which stays unapplied until POST /checkout settles a charge — this route never free-grants a paid tier) or a degraded billing call.
api_keyobject
signup_modestringdirect or invite — a caller cannot otherwise tell 'no key because this product mints none' from 'no key because you joined someone else's workspace', and those need different words on screen.
invited_rolestringthe role the INVITATION granted — identity's, never this plane's; null on a direct signup.
onboardingobjectWhere the customer goes next, and how that destination continues from this signup rather than starting over. PATHS, not URLs: the realm -> console-host mapping already has an owner, and an absolute URL here would make this a second authority for it — while a path lands the customer on the console they actually signed up on.

IssueKeyRequest​

FieldTypeDescription
productstring
labelstring
scopesarray
expires_atnumberepoch seconds; omit/null for a key that never expires. Must be strictly in the future — an already-past value is rejected with 422.
project_idstringScope the key to one of the org's projects. Omit to use the org's default project. A project the caller's organization does not own is 404 — the id is checked against identity, never trusted.
environment_idstringScope the key to one environment WITHIN project_id. Requires project_id (422 without it: an environment belongs to a project, and pairing a lone environment with the default project would attribute the key to a project the caller never named). An environment outside the named project is 404.

Project​

FieldTypeDescription
idstring
organization_idstring
namestring
slugstring
product_idstring

ProjectList​

FieldTypeDescription
projectsarray

CreateProjectRequest​

FieldTypeDescription
namestring
slugstringOptional; derived from name when omitted.

Generated by scripts/gen-capability-docs.py from contracts/product-plane/product/v1/openapi.yaml — the contract IS the source of truth; edit the contract, not this page. The Edge route table section is synced from the live edge catalog snapshot (v1) — re-sync with python3 scripts/sync-edge-catalog.py.