Organizations
List My Organizations
Lists the organizations the caller is a member of.
List My Organizations › Responses
created_atidnameslugURL-safe identifier, unique platform-wide. Console routes are /{slug}/...
billing_country_codeTwo-letter country the organization is taxed under. Null until set; a purchase in a shared organization needs it (PUT /organizations/{id}/billing-country).
icon_asset_idThe organization's uploaded icon (ZC-1305), addressed through
/api/v2/assets/{id}. None when no icon has been uploaded. An
asset id, not a URL -- the /v2 convention every other asset-backed
field on this surface already follows (see
OrganizationMemberResponse::avatar_asset_id's own doc comment): the
id itself changes on every replace, so a consumer building the URL
from it never has to append a cache-busting query parameter to avoid
serving a stale image.
The caller's role in this organization, when resolved.
Create Organization
Creates a new organization owned by the caller. The caller is enrolled as its Owner.
Create Organization › Request Body
nameslugURL-safe identifier, unique platform-wide (lowercase ascii letters, digits, and single internal hyphens).
Create Organization › Responses
One organization as seen by its own member, on the /v2 console
surface. /v2 DTOs are always named *Request / *Response (design
doc §43.4) -- there is no OrganizationView here on purpose.
created_atidnameslugURL-safe identifier, unique platform-wide. Console routes are /{slug}/...
billing_country_codeTwo-letter country the organization is taxed under. Null until set; a purchase in a shared organization needs it (PUT /organizations/{id}/billing-country).
icon_asset_idThe organization's uploaded icon (ZC-1305), addressed through
/api/v2/assets/{id}. None when no icon has been uploaded. An
asset id, not a URL -- the /v2 convention every other asset-backed
field on this surface already follows (see
OrganizationMemberResponse::avatar_asset_id's own doc comment): the
id itself changes on every replace, so a consumer building the URL
from it never has to append a cache-busting query parameter to avoid
serving a stale image.
The caller's role in this organization, when resolved.
Get Organization
Gets one organization the caller belongs to.
path Parameters
idGet Organization › Responses
One organization as seen by its own member, on the /v2 console
surface. /v2 DTOs are always named *Request / *Response (design
doc §43.4) -- there is no OrganizationView here on purpose.
created_atidnameslugURL-safe identifier, unique platform-wide. Console routes are /{slug}/...
billing_country_codeTwo-letter country the organization is taxed under. Null until set; a purchase in a shared organization needs it (PUT /organizations/{id}/billing-country).
icon_asset_idThe organization's uploaded icon (ZC-1305), addressed through
/api/v2/assets/{id}. None when no icon has been uploaded. An
asset id, not a URL -- the /v2 convention every other asset-backed
field on this surface already follows (see
OrganizationMemberResponse::avatar_asset_id's own doc comment): the
id itself changes on every replace, so a consumer building the URL
from it never has to append a cache-busting query parameter to avoid
serving a stale image.
The caller's role in this organization, when resolved.
Delete Organization
Deletes an organization the caller owns. Only the organization's Owner may delete it. Refuses to delete a personal organization, refuses if the organization still holds any project -- delete every project first -- and refuses while its wallet still holds a balance.
path Parameters
idDelete Organization › Responses
Deleted
Update Organization
Renames and/or re-slugs an organization the caller owns or administers. Omitting a field leaves it unchanged. A personal organization's name and slug cannot be changed.
path Parameters
idUpdate Organization › Request Body
nameslugUpdate Organization › Responses
One organization as seen by its own member, on the /v2 console
surface. /v2 DTOs are always named *Request / *Response (design
doc §43.4) -- there is no OrganizationView here on purpose.
created_atidnameslugURL-safe identifier, unique platform-wide. Console routes are /{slug}/...
billing_country_codeTwo-letter country the organization is taxed under. Null until set; a purchase in a shared organization needs it (PUT /organizations/{id}/billing-country).
icon_asset_idThe organization's uploaded icon (ZC-1305), addressed through
/api/v2/assets/{id}. None when no icon has been uploaded. An
asset id, not a URL -- the /v2 convention every other asset-backed
field on this surface already follows (see
OrganizationMemberResponse::avatar_asset_id's own doc comment): the
id itself changes on every replace, so a consumer building the URL
from it never has to append a cache-busting query parameter to avoid
serving a stale image.
The caller's role in this organization, when resolved.
Set Organization Billing Country
Records the country this organization is taxed under, which a purchase inside a shared organization needs (a quote answers billing-country-required until it is set). Owners and admins only. Set once: repeating the same country succeeds, a different one is refused with billing-country-already-set.
path Parameters
idSet Organization Billing Country › Request Body
country_codeTwo-letter country code, e.g. ES. Case-insensitive.
Set Organization Billing Country › Responses
One organization as seen by its own member, on the /v2 console
surface. /v2 DTOs are always named *Request / *Response (design
doc §43.4) -- there is no OrganizationView here on purpose.
created_atidnameslugURL-safe identifier, unique platform-wide. Console routes are /{slug}/...
billing_country_codeTwo-letter country the organization is taxed under. Null until set; a purchase in a shared organization needs it (PUT /organizations/{id}/billing-country).
icon_asset_idThe organization's uploaded icon (ZC-1305), addressed through
/api/v2/assets/{id}. None when no icon has been uploaded. An
asset id, not a URL -- the /v2 convention every other asset-backed
field on this surface already follows (see
OrganizationMemberResponse::avatar_asset_id's own doc comment): the
id itself changes on every replace, so a consumer building the URL
from it never has to append a cache-busting query parameter to avoid
serving a stale image.
The caller's role in this organization, when resolved.
Get Organization By Slug
Gets one organization the caller belongs to, by its slug.
path Parameters
slugGet Organization By Slug › Responses
One organization as seen by its own member, on the /v2 console
surface. /v2 DTOs are always named *Request / *Response (design
doc §43.4) -- there is no OrganizationView here on purpose.
created_atidnameslugURL-safe identifier, unique platform-wide. Console routes are /{slug}/...
billing_country_codeTwo-letter country the organization is taxed under. Null until set; a purchase in a shared organization needs it (PUT /organizations/{id}/billing-country).
icon_asset_idThe organization's uploaded icon (ZC-1305), addressed through
/api/v2/assets/{id}. None when no icon has been uploaded. An
asset id, not a URL -- the /v2 convention every other asset-backed
field on this surface already follows (see
OrganizationMemberResponse::avatar_asset_id's own doc comment): the
id itself changes on every replace, so a consumer building the URL
from it never has to append a cache-busting query parameter to avoid
serving a stale image.
The caller's role in this organization, when resolved.
Upload Organization Icon
Uploads an organization's icon. Send as multipart/form-data with the image in a part named file: JPEG, PNG, WebP or static GIF (first frame), identified by magic bytes; anything else, including SVG, is refused. Re-encoded server-side to a 512x512 lossy WebP with EXIF orientation applied and all other metadata stripped; the original bytes are never stored. Every upload -- including a replace -- gets a FRESH asset id, so the returned icon_asset_id always changes and a client caching by that id never serves a stale image. The previous icon is released asynchronously. Requires ManageOrganizationPolicy (the same bar PATCH uses). Rate-limited per caller.
path Parameters
idUpload Organization Icon › Responses
The organization, with its new icon_asset_id
created_atidnameslugURL-safe identifier, unique platform-wide. Console routes are /{slug}/...
billing_country_codeTwo-letter country the organization is taxed under. Null until set; a purchase in a shared organization needs it (PUT /organizations/{id}/billing-country).
icon_asset_idThe organization's uploaded icon (ZC-1305), addressed through
/api/v2/assets/{id}. None when no icon has been uploaded. An
asset id, not a URL -- the /v2 convention every other asset-backed
field on this surface already follows (see
OrganizationMemberResponse::avatar_asset_id's own doc comment): the
id itself changes on every replace, so a consumer building the URL
from it never has to append a cache-busting query parameter to avoid
serving a stale image.
The caller's role in this organization, when resolved.
List Organization Members
Lists the members of an organization the caller belongs to, each with their name, email address and profile picture. Returns not found rather than forbidden for an organization the caller may not read, so an organization that exists and one that never did are indistinguishable to a non-member.
path Parameters
idList Organization Members › Responses
Members
created_atnameThe member's display name.
organization_idroleuser_idavatar_asset_idThe member's profile picture, addressed through /api/v2/assets/{id}. None when they never set one.
emailNone when the account has no reachable address.
Add Organization Member
Adds a user to an organization the caller owns or administers. Re-adding an existing member returns that membership unchanged. A personal organization takes no new members (422 member-personal-organization).
path Parameters
idAdd Organization Member › Request Body
user_idThe user to add to this organization.
Defaults to member, the least-privileged role: an omitted role must
never widen access further than the caller asked for.
Add Organization Member › Responses
One membership row, carrying enough of the member's identity that the person reading it can tell who it is.
This used to be organization_id, user_id, role and created_at
alone, which meant console's members table could only render a raw uuid
-- an organization owner literally could not tell who their own
collaborators were (console/components/members/member-cell.tsx, and
console/openapi/PENDING.md, both said so). Owner ruling 2026-09-09
item 8: name, email and profile picture, all three, visible to
fellow members.
Three shape decisions, each with a precedent in this workspace:
- Denormalized onto the membership row, not a separate identity
lookup. A lookup route would have to answer for a
user_idin isolation, which is a user-enumeration oracle by construction: it cannot know the caller shares an organization with the subject without being handed the organization, at which point it is this route with extra steps. Resolving at response time is whatkernel_http_v2_admin::contracts::organizationsalready does. emailisOption, followingUserSummaryResponse: an account with no reachable address is a real state (seeUserEmailService), not a failed lookup, and a member list must not 500 because one member signed up through a provider that returned no address.avatar_asset_id, not anavatar_url. The/v2rule is asset ids on the wire, addressed through/api/v2/assets/{id}by the consumer (kernel_http_v2_reseller::contracts::reseller,kernel_http_v2_catalog::contracts::products'sicon_asset_id)./v1baked/api/v1/assets/{id}?v={updated_at}into responses and pinned the wire to a path/v2deletes. It also matters here for a second reason: an id is an opaque kernel uuid, so no part of this field is derived from the member's email address the way a Gravatar-style hash or an OAuth provider's CDN URL would be. The provider's avatar is downloaded into our own object storage at login (AvatarImportService), so a member's picture never sends a viewer to a third party.
created_atnameThe member's display name.
organization_idroleuser_idavatar_asset_idThe member's profile picture, addressed through /api/v2/assets/{id}. None when they never set one.
emailNone when the account has no reachable address.
Remove Organization Member
Removes a member from an organization. A caller may always remove themselves. Removing anyone else needs Owner or Admin. Refuses to remove the organization's personal owner or its last remaining Owner, and refuses to remove a member whose role is above the caller's own (403 member-outranks-caller).
path Parameters
iduser_idRemove Organization Member › Responses
Removed
Update Organization Member
Changes the role of a member of an organization the caller owns or administers. Refuses to change the organization's personal owner's role, refuses to demote the organization's last remaining Owner, and refuses to change a member whose role is above the caller's own (403 member-outranks-caller).
path Parameters
iduser_idUpdate Organization Member › Request Body
roleDistinct from OrganizationRoleResponse because request and response
enums are separate types on /v2 (design doc §43.4), even where they
currently carry the same variants.
Update Organization Member › Responses
One membership row, carrying enough of the member's identity that the person reading it can tell who it is.
This used to be organization_id, user_id, role and created_at
alone, which meant console's members table could only render a raw uuid
-- an organization owner literally could not tell who their own
collaborators were (console/components/members/member-cell.tsx, and
console/openapi/PENDING.md, both said so). Owner ruling 2026-09-09
item 8: name, email and profile picture, all three, visible to
fellow members.
Three shape decisions, each with a precedent in this workspace:
- Denormalized onto the membership row, not a separate identity
lookup. A lookup route would have to answer for a
user_idin isolation, which is a user-enumeration oracle by construction: it cannot know the caller shares an organization with the subject without being handed the organization, at which point it is this route with extra steps. Resolving at response time is whatkernel_http_v2_admin::contracts::organizationsalready does. emailisOption, followingUserSummaryResponse: an account with no reachable address is a real state (seeUserEmailService), not a failed lookup, and a member list must not 500 because one member signed up through a provider that returned no address.avatar_asset_id, not anavatar_url. The/v2rule is asset ids on the wire, addressed through/api/v2/assets/{id}by the consumer (kernel_http_v2_reseller::contracts::reseller,kernel_http_v2_catalog::contracts::products'sicon_asset_id)./v1baked/api/v1/assets/{id}?v={updated_at}into responses and pinned the wire to a path/v2deletes. It also matters here for a second reason: an id is an opaque kernel uuid, so no part of this field is derived from the member's email address the way a Gravatar-style hash or an OAuth provider's CDN URL would be. The provider's avatar is downloaded into our own object storage at login (AvatarImportService), so a member's picture never sends a viewer to a third party.
created_atnameThe member's display name.
organization_idroleuser_idavatar_asset_idThe member's profile picture, addressed through /api/v2/assets/{id}. None when they never set one.
emailNone when the account has no reachable address.
List Organization Audit Log
Lists the organization's activity, most recent first. Only entries a caller explicitly scoped to a customer organization appear here: platform-internal events are excluded structurally, not by filtering. Narrow to one project with project_id, or to one action name with action.
path Parameters
idquery Parameters
actionExact match on the action name, e.g. subscription.activated.
Exact match on the action name, e.g. subscription.activated.
pagePage number (1-indexed). Defaults to 1.
Page number (1-indexed). Defaults to 1.
page_sizeNumber of items per page. Defaults to 20, max 100.
Number of items per page. Defaults to 20, max 100.
project_idNarrows to one project inside the organization. A project always implies its organization, so this never widens what the caller sees.
Narrows to one project inside the organization. A project always implies its organization, so this never widens what the caller sees.
service_instance_idNarrow to one service instance: what happened to this server
(ZC-1277). Composes with project_id rather than replacing it, and
both only ever narrow inside the organization the path already scoped.
Narrow to one service instance: what happened to this server
(ZC-1277). Composes with project_id rather than replacing it, and
both only ever narrow inside the organization the path already scoped.
List Organization Audit Log › Responses
Audit entries
Pagination metadata included in paginated responses
List Organization Domains
Lists every domain the organization has claimed, proven or not. Each row carries the TXT challenge to publish and the outcome of the most recent check.
path Parameters
idList Organization Domains › Responses
Domain verifications
domainrecord_namerecord_typeAlways TXT. Present so a client never has to hardcode the record
type it renders next to record_name/record_value.
record_valuestatuspending, verified, lost, or failed.
first_verified_atThe first time this row was ever proven, set once and never moved
again even through a later Lost/Verified cycle. What a "Verified on
1 March" message (design doc state 4/5) reads from; distinct from
verified_at, which keeps advancing on every successful recheck.
grandfathered_untilA dated exemption for one of the handful of production domains that
were serving before this feature existed (design doc, "The four live
production domains"). Some only for those rows, and only until the
date itself: after it, an unproven domain in this state stops being
served. Absent for every row created through the ordinary start flow.
last_checked_atlast_errorKernel's own sentence for the most recent refusal, verbatim, per the design doc's "what kernel returns" (kernel writes the sentence, console renders it as-is).
lost_sinceWhen a Verified row's proof was most recently found gone or
replaced. Some only while status is lost. Combined with the
domain's fourteen-day grace period, this is what lets a client compute
the exact date state 6 names ("will stop being served on
lost_since + 14 days").
verified_atAdd Organization Domain
Starts proving the organization controls a domain, or re-reads the challenge if it already started one. Publish a TXT record at the returned name with the returned value, then call the check route (or wait for the automatic recheck).
path Parameters
idAdd Organization Domain › Request Body
domainThe hostname the organization is proving it controls, for example
shop.acme.com. Normalized (lowercased, trailing root dot stripped)
and validated server-side; see the domain's own normalize_domain.
Add Organization Domain › Responses
The challenge to publish
domainrecord_namerecord_typeAlways TXT. Present so a client never has to hardcode the record
type it renders next to record_name/record_value.
record_valuestatuspending, verified, lost, or failed.
first_verified_atThe first time this row was ever proven, set once and never moved
again even through a later Lost/Verified cycle. What a "Verified on
1 March" message (design doc state 4/5) reads from; distinct from
verified_at, which keeps advancing on every successful recheck.
grandfathered_untilA dated exemption for one of the handful of production domains that
were serving before this feature existed (design doc, "The four live
production domains"). Some only for those rows, and only until the
date itself: after it, an unproven domain in this state stops being
served. Absent for every row created through the ordinary start flow.
last_checked_atlast_errorKernel's own sentence for the most recent refusal, verbatim, per the design doc's "what kernel returns" (kernel writes the sentence, console renders it as-is).
lost_sinceWhen a Verified row's proof was most recently found gone or
replaced. Some only while status is lost. Combined with the
domain's fourteen-day grace period, this is what lets a client compute
the exact date state 6 names ("will stop being served on
lost_since + 14 days").
verified_atCheck Organization Domain
Runs the TXT challenge for one of the organization's domains right now and persists the outcome, instead of waiting for the automatic recheck. Tightly rate-limited: this performs a real DNS lookup for the named hostname.
path Parameters
domainidCheck Organization Domain › Responses
The verification after running the check
domainrecord_namerecord_typeAlways TXT. Present so a client never has to hardcode the record
type it renders next to record_name/record_value.
record_valuestatuspending, verified, lost, or failed.
first_verified_atThe first time this row was ever proven, set once and never moved
again even through a later Lost/Verified cycle. What a "Verified on
1 March" message (design doc state 4/5) reads from; distinct from
verified_at, which keeps advancing on every successful recheck.
grandfathered_untilA dated exemption for one of the handful of production domains that
were serving before this feature existed (design doc, "The four live
production domains"). Some only for those rows, and only until the
date itself: after it, an unproven domain in this state stops being
served. Absent for every row created through the ordinary start flow.
last_checked_atlast_errorKernel's own sentence for the most recent refusal, verbatim, per the design doc's "what kernel returns" (kernel writes the sentence, console renders it as-is).
lost_sinceWhen a Verified row's proof was most recently found gone or
replaced. Some only while status is lost. Combined with the
domain's fourteen-day grace period, this is what lets a client compute
the exact date state 6 names ("will stop being served on
lost_since + 14 days").
verified_atRemove Organization Domain
Removes the organization's own claim on a domain, freeing the name for anyone (including this organization) to claim again. Refused while any of the organization's own service instances is currently exposed on this domain -- detach those first; the error response names them.
path Parameters
domainidRemove Organization Domain › Responses
Removed
List Organization API Tokens
Lists the live API tokens this organization owns, newest first, with each token's lifetime shape, capability set and last use. Revoked tokens are omitted: this is a credential inventory, not an audit log. No response here ever carries a secret, and the secret shown once by the create call cannot be recovered from this route or any other. Requires an Owner or Admin membership, or being the organization's personal owner: a credential inventory says which automation exists and what it may do, which is not the same question as who belongs to the organization.
path Parameters
idList Organization API Tokens › Responses
The organization's live API tokens
Create Organization API Token
Mints an API token owned by this organization and returns the secret exactly once, in the form zaroz_pat_{tokenId}_{secret}. Only a digest of the secret half is stored, so a caller that loses this response mints a replacement rather than recovering this one; no list or detail route can return it again.
lifetime chooses which of the two shapes the token has, and OMITTING IT YIELDS THE SAFER ONE. membership_tied (the default) is revoked automatically when the creator's membership of this organization ends. organization_owned belongs to the organization and keeps working afterwards, which is the point of an organization token and also the reason it is never what a caller gets by accident.
capabilities may be empty, and an empty set authorizes nothing: for an organization credential, absence denies rather than permits. Every capability is granted scoped to this organization, never globally, and a staff-only capability is refused outright no matter who is asking. Nobody can grant a capability they do not currently hold themselves.
Requires an Owner or Admin membership, or being the organization's personal owner: minting a credential that acts for the organization is the same authority as adding a member to it.
path Parameters
idCreate Organization API Token › Request Body
nameWhat the token may do, scoped to this organization. An empty list mints a token that authorizes nothing: for an organization credential, absence denies rather than permits.
expires_atOmit for a token that never expires on its own. Must be in the future.
Omitting this yields membership_tied, the safer shape: the token is revoked when the creator's membership of this organization ends. Pass organization_owned only when the credential is meant to outlive whoever set it up.
Create Organization API Token › Responses
The token, and its secret, shown once
secretThe full credential, shown exactly once. Only a digest of its secret half is stored, so no later request can return this value again.
One organization API token. Never carries the secret. The secret exists
in exactly one response, [CreatedOrganizationApiTokenResponse], and only
its digest is stored, so nothing can put it back here later.
List Grantable Organization API Token Capabilities
The capabilities the caller may put in an API token of this organization: every capability they currently hold themselves, minus the staff-only ones, which no organization token may carry. Create refuses exactly the rest, by the same domain rule, so a token screen renders from this list rather than a hardcoded copy. max_lifetime_days is the longest lifetime kernel accepts, or null when there is no maximum. Same access as the other organization token routes: an Owner or Admin membership, or being the organization's personal owner.
path Parameters
idList Grantable Organization API Token Capabilities › Responses
The capabilities this caller may grant on this organization
max_lifetime_daysLongest lifetime kernel accepts for an organization token, in days.
null means kernel enforces no maximum: only that expires_at is in
the future, and omitting it means the token never expires.
Revoke Organization API Token
Revokes one of this organization's API tokens immediately. Every other token it holds keeps working, which is what makes rotation an overlap rather than an outage: mint the replacement, deploy it, then revoke this one.
A token id that belongs to another organization, to a person, or to nothing at all all answer the same not found, so this route cannot be used to discover which token ids exist. Revoking an already-revoked token is that same not found. Requires the same Owner or Admin membership that minting does.
path Parameters
idtoken_idRevoke Organization API Token › Responses
Token revoked
List Organization Invitations
Lists an organization's currently-pending, unexpired invitations, for the Members tab's pending-invites section. Requires the same bar as inviting a member.
path Parameters
idList Organization Invitations › Responses
Pending invitations
created_atemailexpires_atidinvited_by_nameorganization_idorganization_nameDenormalized onto every row (also present, redundantly, on an organization-scoped listing) so GET /me/invitations does not need a second round trip per organization to render a usable list.
rolestatusThe invitation's effective status: a stale pending or declined row past its own expiry reads as expired here even before any sweep has rewritten the stored column. See OrganizationInvitation::effective_status.
invited_by_avatar_asset_idorganization_icon_asset_idresponded_atCreate Organization Invitation
Invites an email address to join an organization the caller owns or administers. Answers the same 201 shape whether or not the address belongs to an existing account (no enumeration oracle). Refuses with 429 and a machine-readable code when the sender's email is unverified, or any ZC-1346 rate limit or re-invite cool-down applies.
path Parameters
idCreate Organization Invitation › Request Body
emailroleDistinct from OrganizationRoleResponse because request and response
enums are separate types on /v2 (design doc §43.4), even where they
currently carry the same variants.
Create Organization Invitation › Responses
The created (or, if one was already pending, existing) invitation
created_atemailexpires_atidinvited_by_nameorganization_idorganization_nameDenormalized onto every row (also present, redundantly, on an organization-scoped listing) so GET /me/invitations does not need a second round trip per organization to render a usable list.
rolestatusThe invitation's effective status: a stale pending or declined row past its own expiry reads as expired here even before any sweep has rewritten the stored column. See OrganizationInvitation::effective_status.
invited_by_avatar_asset_idorganization_icon_asset_idresponded_atRevoke Organization Invitation
Withdraws a pending invitation. Requires the same bar as sending one. Refuses if the invitation has already been answered or has expired.
path Parameters
idinvitation_idRevoke Organization Invitation › Responses
Revoked
List My Invitations
Lists organization invitations addressed to the caller's own VERIFIED email, filtered by status (pending or declined). A caller with no verified email always gets an empty list.
query Parameters
statusOnly pending and declined are listable this way, matching the design's route table:
accepted, revoked and expired invitations do not belong in a recipient's actionable inbox.
List My Invitations › Responses
The caller's invitations
created_atemailexpires_atidinvited_by_nameorganization_idorganization_nameDenormalized onto every row (also present, redundantly, on an organization-scoped listing) so GET /me/invitations does not need a second round trip per organization to render a usable list.
rolestatusThe invitation's effective status: a stale pending or declined row past its own expiry reads as expired here even before any sweep has rewritten the stored column. See OrganizationInvitation::effective_status.
invited_by_avatar_asset_idorganization_icon_asset_idresponded_atGet Organization Invitation
The review page a recipient opens: organization name and icon, inviter name and avatar, role and expiry. Requires the caller's VERIFIED email to match the invitation's address; a signed-in caller with a different verified email gets a 403 carrying only their own masked address, nothing about the organization.
path Parameters
idGet Organization Invitation › Responses
The invitation, ready to review
created_atexpires_atidinviter_nameorganization_idWhere Accept navigates once it succeeds.
organization_namerolestatusThe invitation's effective status, not necessarily the raw stored column.
inviter_avatar_asset_idorganization_icon_asset_idAccept Organization Invitation
Accepts the invitation and inserts the organization_members row in the same transaction. Never changes an existing member's role: a caller who already belongs to the organization is refused with 409 and the invitation is cancelled. Idempotent on an already-accepted invitation, which answers success and writes nothing. Allowed on a declined, unexpired invitation too (the decline-undo). Requires the caller's VERIFIED email to match; a mismatch gets the same masked-email 403 the review route does.
path Parameters
idAccept Organization Invitation › Responses
The now-accepted invitation
created_atexpires_atidinviter_nameorganization_idWhere Accept navigates once it succeeds.
organization_namerolestatusThe invitation's effective status, not necessarily the raw stored column.
inviter_avatar_asset_idorganization_icon_asset_idDecline Organization Invitation
Declines the invitation. Starts the ZC-1346 30-day re-invite cool-down for this organization+address, which the recipient can lift early with allow-reinvite, or undo entirely by accepting anyway before the invitation's own expiry. Requires the caller's VERIFIED email to match.
path Parameters
idDecline Organization Invitation › Responses
The now-declined invitation
created_atexpires_atidinviter_nameorganization_idWhere Accept navigates once it succeeds.
organization_namerolestatusThe invitation's effective status, not necessarily the raw stored column.
inviter_avatar_asset_idorganization_icon_asset_idAllow Organization Reinvite
Lifts the ZC-1346 30-day re-invite cool-down early, so the organization may send a fresh invitation to this address right away. Only meaningful on a declined invitation. Requires the caller's VERIFIED email to match.
path Parameters
idAllow Organization Reinvite › Responses
Cool-down lifted
List My Projects
Lists the caller's projects, across every organization they belong to. Every row carries its own organization_id, so the listing can be grouped by owner; for one organization's projects alone, read /organizations/{id}/projects instead.
List My Projects › Responses
created_atidnameorganization_idslugURL-safe identifier, unique within the owning organization. Console routes are /{org}/{slug}.
updated_atdescriptionList Organization Projects
Lists one organization's projects. Answers 404 for an organization the caller does not belong to, the same way reading that organization directly would, so an organization the caller may not see is indistinguishable from one that does not exist. This can only ever return projects listMyProjects would also have returned.
path Parameters
idList Organization Projects › Responses
created_atidnameorganization_idslugURL-safe identifier, unique within the owning organization. Console routes are /{org}/{slug}.
updated_atdescriptionCreate Project
Creates a project in an organization the caller owns or administers. In a personal organization the slug games is reserved for the gaming dashboard's project: 422 project-slug-reserved.
path Parameters
idCreate Project › Request Body
nameslugURL-safe identifier, unique within the owning organization (lowercase ascii letters, digits, and single internal hyphens).
descriptionCreate Project › Responses
One project as seen by a member of its organization, on the /v2
console surface (design doc §34.1). /v2 DTOs are always named
*Request / *Response -- there is no ProjectView here on purpose.
created_atidnameorganization_idslugURL-safe identifier, unique within the owning organization. Console routes are /{org}/{slug}.
updated_atdescriptionList My Project Stats
The resource count and current-cycle spend for every project List My Projects returns, in one request rather than one per card. Same visibility as that operation: a project absent from it is absent here.
List My Project Stats › Responses
What the project is being charged for the service it is receiving right now, one entry per currency. Empty when nothing in the project is under a live service window -- which is the honest answer for a project with no subscriptions and for one whose subscriptions have all lapsed, never an error. In practice this holds exactly one entry: the platform transacts in EUR only.
project_idresource_countHow many live subscriptions this project holds. A "resource" here is a subscription -- design doc §34.1 puts Project directly above Subscription, so one purchase is one row -- not a resources row (which would count a single VPS as its VM plus each of its volumes) and not a service instance (which would drop a paid subscription still awaiting activation). "Live" is deleted_at IS NULL and nothing more, the same rule that decides whether the project can be deleted and the same rule GET /console/subscriptions?project_id= pages with, so this number is exactly how many rows the caller sees on clicking through.
Get Project By Slug
Gets one project the caller's organization owns, by its slug.
path Parameters
idslugGet Project By Slug › Responses
One project as seen by a member of its organization, on the /v2
console surface (design doc §34.1). /v2 DTOs are always named
*Request / *Response -- there is no ProjectView here on purpose.
created_atidnameorganization_idslugURL-safe identifier, unique within the owning organization. Console routes are /{org}/{slug}.
updated_atdescriptionGet Project
Gets one project the caller's organization owns.
path Parameters
idGet Project › Responses
One project as seen by a member of its organization, on the /v2
console surface (design doc §34.1). /v2 DTOs are always named
*Request / *Response -- there is no ProjectView here on purpose.
created_atidnameorganization_idslugURL-safe identifier, unique within the owning organization. Console routes are /{org}/{slug}.
updated_atdescriptionDelete Project
Deletes a project in an organization the caller owns or administers. Refuses if the project still holds any subscriptions, and for a personal organization's games project, which belongs to the gaming dashboard (403 project-gaming-managed).
path Parameters
idDelete Project › Responses
Deleted
Update Project
Renames, re-slugs, and/or re-describes a project in an organization the caller owns or administers. Omitted fields are left unchanged; description: null clears it. A personal organization's games project belongs to the gaming dashboard and cannot be changed here (403 project-gaming-managed), and no project in a personal organization may take the slug games (422 project-slug-reserved).
path Parameters
idUpdate Project › Request Body
descriptionnameslugUpdate Project › Responses
One project as seen by a member of its organization, on the /v2
console surface (design doc §34.1). /v2 DTOs are always named
*Request / *Response -- there is no ProjectView here on purpose.
created_atidnameorganization_idslugURL-safe identifier, unique within the owning organization. Console routes are /{org}/{slug}.
updated_atdescriptionGet Project Stats
The resource count and current-cycle spend for one project. A second read on purpose: these are aggregates over the project's subscriptions and invoice lines, so they are kept off ProjectResponse where they would block the project's own fields. Refused exactly as Get Project is -- a project the caller cannot read is 404, whether or not it exists.
path Parameters
idGet Project Stats › Responses
The aggregates behind console's project card badges and project overview tiles, for one project.
A separate operation, not fields on [ProjectResponse]. A field
cannot be streamed apart from the object carrying it: resource_count on
ProjectResponse would make GET /projects/{id} -- and every row of
GET /projects -- wait on an aggregation over subscriptions and
invoice_line_items before the project's own name could be rendered.
Split out, console paints the project immediately and fills these in
behind its own suspense boundary.
What the project is being charged for the service it is receiving right now, one entry per currency. Empty when nothing in the project is under a live service window -- which is the honest answer for a project with no subscriptions and for one whose subscriptions have all lapsed, never an error. In practice this holds exactly one entry: the platform transacts in EUR only.
project_idresource_countHow many live subscriptions this project holds. A "resource" here is a subscription -- design doc §34.1 puts Project directly above Subscription, so one purchase is one row -- not a resources row (which would count a single VPS as its VM plus each of its volumes) and not a service instance (which would drop a paid subscription still awaiting activation). "Live" is deleted_at IS NULL and nothing more, the same rule that decides whether the project can be deleted and the same rule GET /console/subscriptions?project_id= pages with, so this number is exactly how many rows the caller sees on clicking through.
Get Organization Standing
Reads one organization's current standing (ZC-1324 org trust): whether it is restricted, and why. Reachable by any member (Q121) -- the organization's own owner sees the full numeric breakdown (balance, reservations, floor, credit limit, open receivables) in detail; every other member sees only message, the same fixed sentence regardless of the actual reason. restricted is always present. An organization the caller does not belong to answers 404, identical to an invented id.
path Parameters
organization_idGet Organization Standing › Responses
The organization's current standing, at the caller's own disclosure level
