Tenant Endpoints
Request a Tenant
Request a new tenant on the control plane. Returns 202 with a poll URL — the tenant does not exist yet, a flow that will create it does.
POST
Why 202 and not 201
The response is202 Accepted. Nothing has been created when it returns — an onboarding flow
has been opened in state requested, and that flow is still subject to approval or rejection.
Answering 201 with a Location header pointing at a tenant that a second operator may still
reject would be a lie the console renders as success. The 202 body hands back a poll_url
instead, and the tenant becomes real only after approval.
Base URL and authentication
operator; a viewer token is refused on every write route.
A missing token and a malformed token both return the same byte-pinned
403. There is no 401
on this surface, and no response body distinguishes “no credential” from “bad credential”.Request body
string
required
URL-safe tenant identifier, and the tenant’s permanent handle. Maximum 56 characters — the
physical database is named
tenant_<slug> and Postgres truncates identifiers at 63 bytes
(NAMEDATALEN), so a longer slug would silently collide with another tenant’s database.string
required
Human-readable organization name, shown on the approval card.
string
required
Email of the tenant’s owner. Must contain
@.string
required
Placement region. A cell in this region must exist and have spare capacity, or the flow
fails placement with
no_placeable_cell. Check placement before requesting.string
required
One of
core, team, enterprise.These three are the whole set — the value is enforced by a database CHECK constraint as well
as by the API, so an unrecognised tier is refused rather than stored. trial is not a
tier; a trial is a time window on a tenant, not a plan.string
required
Must be
shared. The schema also names dedicated and self-hosted, but only shared is
implemented today and anything else is refused with 422.string
default:"monitor"
monitor or enforce. Omitted means monitor — a new tenant observes before it blocks.integer
Expected developer endpoint count, for capacity planning. Recorded, not enforced.
integer
Expected server endpoint count. Recorded, not enforced.
string
Free-text context for the approver. Maximum 2000 characters.
Response
string
The slug you requested, echoed back.
string
Always
requested on a fresh request.string
Path to the detail route. Poll this for progress.
string
Path to the SSE event stream for the same flow.
Errors
After the 202
The202 is the start of a pipeline, not the end of a request. The states a tenant moves
through:
pending-approval waits for a second operator, and the
flow engine deliberately will not wake it, because nothing but a decision can move it.
paused holds a flow that exhausted its retries, and needs an explicit resume.
Poll GET /v1/tenants/{slug} for the current state, or stream
GET /v1/tenants/{slug}/events. The detail route’s presentation field is derived from the
flow definition rather than stored, and reports backing-off for a flow retrying on a timer —
which is why a state of failed is not terminal.
Approval is a separate call, by a different person
403 — the rule working, not a
misconfiguration.
The refusal code differs by surface. Self-approval on a tenant route returns
403; on
a deploy route it returns 409. A console that treats every 403 as “session expired”
will tell operators to log in again when the real answer is “ask a colleague to approve.”POST /v1/tenants/{slug}/reject with a reason — an empty or very short
reason is 422, because the reason is what an auditor reads later. cancel and resume take
the same path shape.
The full lifecycle
The token mint returns the raw token exactly once. It is never written to the flow journal,
the tenant event log, or the service logs — only a SHA-256 hash and a short prefix are stored.
If it is not captured from that one response body, mint another; there is no route that reveals
it again.
Deprovisioning requires a
justification of at least 20 characters, and is approved through
the same /approve route — the flow type is resolved from the slug, not from the path. While
a deprovision is live, reject, cancel, resume and the event stream all act on the offboard
flow rather than on the completed provisioning flow.