{"openapi":"3.1.0","info":{"title":"platform-api","description":"Platform API: infrastructure, operational, and customer tiers.","version":"0.12.1"},"paths":{"/v1/me/access":{"get":{"tags":["authz"],"summary":"Get My Access","description":"Return the caller's access level per related tenant. Call this to check\nwhat the current caller is authorized to do before attempting an action\nthat requires a specific level, or to discover a pending invitation or\naccess request.\n\nOne entry per tenant the subject relates to via an active assignment, a\npending invitation to their verified email, or a pending access request\n(ADR 0016 decision 6); a subject with no relation gets an empty list.\n`subscription` is the tenant's `{state, expires_at, grace_ends_at}` (UC-096);\na standard tenant reports `active` with null dates.","operationId":"get_my_access","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TenantAccessOut"},"type":"array","title":"Response Get My Access"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/tenant/members":{"get":{"tags":["authz"],"summary":"List Members","description":"List tenant members with their effective role x scope assignments.\n\nMembers are subjects with at least one active assignment at the caller's\ntenant scope. Only their assignments at that same tenant scope are\nincluded — a subject's assignments on another tenant are never disclosed\nto a viewer of this one (no cross-tenant existence leak).","operationId":"list_members","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembersListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tenant/members/{sub}/assignments":{"post":{"tags":["authz"],"summary":"Grant Assignment","description":"Grant a role x scope assignment to a tenant member.\n\nEnforces the grantability policy (app/authz/grants.py): no privilege\nescalation, no cross-tenant grants, member-only. An `admin` may grant up\nto `admin`; only an `owner` may grant `owner` (ADR 0016 decision 3).\nEvery grant is recorded in the tamper-evident audit trail (UC-039) with\nthe token's `azp`/`act`/`tier` (ADR 0016 decision 5).","operationId":"grant_assignment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sub","in":"path","required":true,"schema":{"type":"string","title":"Sub"}},{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignmentIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":false,"x-idempotent":false}},"/v1/tenant/members/{sub}/assignments/{id}":{"delete":{"tags":["authz"],"summary":"Revoke Assignment","description":"Revoke a role x scope assignment from a tenant member.\n\nTenant-scoped (ADR 0016 code fix): an assignment id belonging to a\ndifferent tenant answers `404 assignment.not_found`, indistinguishable\nfrom an unknown id — no cross-tenant existence leak. An `admin` may\nrevoke up to `admin`; only an `owner` may revoke an `owner` assignment.\nThe last `owner` of a tenant cannot be revoked (self-lockout guard,\nre-pointed from the old \"last tenant admin\" rule). Every revocation is\nrecorded in the tamper-evident audit trail (UC-039) with the token's\n`azp`/`act`/`tier`.","operationId":"revoke_assignment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sub","in":"path","required":true,"schema":{"type":"string","title":"Sub"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Id"}},{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":true,"x-idempotent":true}},"/infra/nodes":{"get":{"tags":["infrastructure"],"summary":"List Nodes","description":"Return the infrastructure nodes known to the platform. Call this when an\noperator needs an inventory of nodes before acting on one.","operationId":"list_nodes","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array","title":"Response List Nodes"}}}}},"security":[{"HTTPBearer":[]}]}},"/infra/whoami":{"get":{"tags":["infrastructure"],"summary":"Whoami","description":"Return the caller's own infra-admin roles. Call this to confirm which\nprivileges the current token carries before attempting a privileged action.","operationId":"whoami","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Response Whoami"}}}}},"security":[{"HTTPBearer":[]}]}},"/infra/kill-switches":{"get":{"tags":["infrastructure"],"summary":"List Kill Switches","description":"List all registered kill switches with their env default, override, and effective value.\nCall this before flipping a switch, to see its current state.","operationId":"list_kill_switches","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/KillSwitchOut"},"type":"array","title":"Response List Kill Switches"}}}}},"security":[{"HTTPBearer":[]}]}},"/infra/kill-switches/{name}":{"patch":{"tags":["infrastructure"],"summary":"Set Kill Switch","description":"Set or clear *name*'s override and return the switch's new effective state.\nCall this to flip a glass-break kill switch during an incident, or to clear an\noverride once resolved; audited before commit, 404 on unknown name.","operationId":"set_kill_switch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KillSwitchPatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KillSwitchOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/catalog/templates":{"get":{"tags":["catalog"],"summary":"List Catalog Templates Infra","description":"List all catalog templates, including disabled ones. Call this when an infra\nadmin needs the full catalog before creating, updating, or disabling a template.","operationId":"list_all_catalog_templates","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TemplateOut"},"type":"array","title":"Response List All Catalog Templates"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["catalog"],"summary":"Create Catalog Template","description":"Create a new ApplicationTemplate and return the completed operation. Call this\nwhen an infra admin wants to add a template to the customer-facing catalog.","operationId":"create_catalog_template","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateCreateIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/infra/catalog/templates/{code}":{"patch":{"tags":["catalog"],"summary":"Update Catalog Template","description":"Update an existing ApplicationTemplate's fields and return the completed\noperation. Call this when an infra admin needs to correct or refresh a\ntemplate's catalog metadata.","operationId":"update_catalog_template","security":[{"HTTPBearer":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateUpdateIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/catalog/templates/{code}/disable":{"post":{"tags":["catalog"],"summary":"Disable Catalog Template","description":"Retire an ApplicationTemplate from the customer-facing catalog and return the\ncompleted operation. Call this when an infra admin wants to stop a template from\nbeing offered to new trials; disable is the only retirement path (no DELETE).","operationId":"disable_catalog_template","security":[{"HTTPBearer":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/operations/":{"get":{"tags":["infra-operations"],"summary":"List Ops","description":"List async operations across every tier, optionally filtered by status, type,\ntenant, or actor. Call this when an operator needs to triage the infra operations\nqueue, e.g. to find stuck or failed operations to retry or cancel.","operationId":"list_infra_operations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/OperationStatus"},{"type":"null"}],"title":"Status"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"tier","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/OperationTier"},{"type":"null"}],"title":"Tier"}},{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"}},{"name":"actor_sub","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Sub"}},{"name":"created_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created Before"}},{"name":"created_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created After"}},{"name":"stuck","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Stuck"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/operations/{operation_id}":{"get":{"tags":["infra-operations"],"summary":"Get Op","description":"Return one operation's full status, result, and error detail. Call this to\ninspect a specific operation before deciding whether to retry or cancel it.","operationId":"get_infra_operation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Operation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationInfraOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/operations/{operation_id}/retry":{"post":{"tags":["infra-operations"],"summary":"Retry Op","description":"Reset a failed operation to pending so the worker picks it up again and return\nits new status. Call this when an operator has fixed the underlying cause of a\nfailed operation and wants it re-attempted.","operationId":"retry_operation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Operation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationInfraOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/operations/{operation_id}/cancel":{"post":{"tags":["infra-operations"],"summary":"Cancel Op","description":"Cancel a pending or running operation and return its new status. Call this\nwhen an operator wants to stop an operation that should no longer proceed.","operationId":"cancel_operation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Operation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationInfraOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/tenants":{"post":{"tags":["tenants"],"summary":"Create Tenant Endpoint","description":"Provision a new tenant asynchronously and return the operation envelope. Call\nthis when an infra admin needs to create a tenant outside the self-service\ntrial flow. Returns 202 + Location: /infra/operations/{id}.","operationId":"create_tenant","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantCreateIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["tenants"],"summary":"List Tenants Endpoint","description":"List tenants with optional status / domain filters (paginated). Call this\nwhen an infra admin needs to find a tenant before inspecting or acting on it.","operationId":"list_tenants","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/TenantStatus"},{"type":"null"}],"title":"Status"}},{"name":"domain","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/tenants/{tenant_id}":{"get":{"tags":["tenants"],"summary":"Get Tenant Endpoint","description":"Retrieve a single tenant's full detail by UUID. Call this when an infra admin\nneeds a tenant's current status and metadata before acting on it.","operationId":"get_tenant","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/trials":{"post":{"tags":["trials"],"summary":"Create Trial Endpoint","description":"Provision a trial tenant (async). Returns 202 + Location: /infra/operations/{id}.\n\nBlocked when TRIALS_PROVISIONING_ENABLED=false and caller is TENANT_PROVISIONER\n(UC-045 kill-switch halts the prospect funnel without affecting infra-admin).\n\nA domain that already had its trial answers 409 `trial.exists_for_domain`;\n`override_trial_once` with `override_reason` (ops-realm `sales` or\n`infra-admin` only) lets it through and is audited (UC-099).","operationId":"create_trial","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrialCreateIn"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/infra/trials/{trial_id}":{"get":{"tags":["trials"],"summary":"Get Trial Endpoint","description":"Retrieve a trial's full detail by UUID. Call this when an operator needs a\ntrial's current status before extending it, loading data, or tearing it down.","operationId":"get_trial","security":[{"HTTPBearer":[]}],"parameters":[{"name":"trial_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Trial Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrialOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["trials"],"summary":"Teardown Trial Endpoint","description":"Tear down a trial (erases tenant + data). Idempotent: 200 TrialOut if torn down.","operationId":"teardown_trial","security":[{"HTTPBearer":[]}],"parameters":[{"name":"trial_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Trial Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Teardown Trial"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/trials/{trial_id}:extend":{"post":{"tags":["trials"],"summary":"Extend Trial Endpoint","description":"Extend a trial once by 1 to 30 days with a written reason (ops-realm\n`sales` or `infra-admin`; the provisioner credential is refused). Also\nworks during the grace period, when it restarts the trial. Returns the\nupdated trial.","operationId":"extend_trial","security":[{"HTTPBearer":[]}],"parameters":[{"name":"trial_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Trial Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrialExtendIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrialOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/trials/{trial_id}:load-data":{"post":{"tags":["trials"],"summary":"Load Data Endpoint","description":"Import a prospect's IUCLID export into the trial instance (UC-046).\n\nReturns 202 + Location: /infra/operations/{id}. Preconditions:\ntrial must be ready, have a deployed IUCLID template, and the export\nmust be within the trial quota.","operationId":"load_trial_data","security":[{"HTTPBearer":[]}],"parameters":[{"name":"trial_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Trial Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrialLoadDataIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/infra/trials/{trial_id}:erase-data":{"post":{"tags":["trials"],"summary":"Erase Data Endpoint","description":"Erase imported data from the trial instance without full teardown (UC-046).\n\nThe trial remains open and ready (empty) after erasure.\nReturns 202 + Location: /infra/operations/{id}.","operationId":"erase_trial_data","security":[{"HTTPBearer":[]}],"parameters":[{"name":"trial_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Trial Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ops/notifications/email":{"post":{"tags":["operations"],"summary":"Send Email","description":"Queue a plain-text email for delivery and return the operation envelope. Call\nthis from a service that needs to notify a user with a one-off, pre-composed\nmessage; supply an Idempotency-Key to avoid sending duplicates on retry.","operationId":"send_email","security":[{"HTTPBearer":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailNotification"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ops/notifications/email/templated":{"post":{"tags":["operations"],"summary":"Send Templated Email","description":"Render a named template with the given context and queue the resulting email\nfor delivery, returning the operation envelope. Call this from a service that\nneeds to send a standard notification (e.g. invite, welcome) rather than a\none-off message.","operationId":"send_templated_email","security":[{"HTTPBearer":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplatedEmailNotification"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ops/operations/{operation_id}":{"get":{"tags":["operations"],"summary":"Get Operation","description":"Return one operation's status, result, and error detail for the calling\nservice. Call this to poll a 202-accepted operation to completion.","operationId":"get_operation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Operation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/operations/{operation_id}":{"get":{"tags":["operations"],"summary":"Get My Operation","description":"Return the status, result and error of an operation the caller started, such as\nthe `Location` of a `202` from `complete_billing`. Call this to poll it to completion.","operationId":"get_my_operation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Operation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tenant/billing":{"get":{"tags":["billing"],"summary":"Get Billing","description":"Return the tenant's stored billing details: VAT id, invoice address, invoice\nemail and PO reference. Call this to check whether billing is complete; only\nthe owner and billing contacts may read it (`403 rbac.insufficient_level`).","operationId":"get_billing","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["billing"],"summary":"Complete Billing","description":"Complete the tenant's billing details: legal-entity name and type, VAT id,\ninvoice address, invoice email and optional PO reference. Call this when the\nowner or a billing contact has the details. EU VAT ids are validated against\nVIES by the worker; a failed operation with `billing.vat_check_unavailable`\ncan be retried by submitting again. Returns `202` with an operation to poll.","operationId":"complete_billing","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":false,"x-idempotent":true,"x-open-world":true}},"/v1/tenant/services":{"get":{"tags":["customer"],"summary":"List Tenant Services","description":"Return the caller's tenant's active service subscriptions. Call this before\noffering to add a service, to check what is already subscribed.\n\nLike every tenant-scoped operation (ADR 0016 decision 6), accepts an\noptional `tenant_id`; omitted, the tenant comes from the token's\n`organization` claim when the subject relates to exactly one tenant, else\n`422 tenant.ambiguous`. Requires at least `viewer` — gated on the\nDB-derived level, not merely the token's `organization` claim, so a\nmember whose assignment was revoked cannot read a stale tenant's\nsubscriptions even with a token that still carries the claim.","operationId":"list_tenant_services","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{"type":"string"}},"title":"Response List Tenant Services"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["customer"],"summary":"Add Tenant Service","description":"Request a new service subscription for the caller's tenant and return its\nstatus. Call this when a tenant admin wants to add a service (ADR 0016\ncode fix: requires `admin`, was `customer` realm role only).","operationId":"add_tenant_service","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceSubscription"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Add Tenant Service"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":false,"x-idempotent":false}},"/v1/catalog/templates":{"get":{"tags":["catalog"],"summary":"Get Catalog Templates","description":"Return the enabled ApplicationTemplate catalog, ordered by name. Call this to\ndiscuss trial and template options with a prospect or customer before starting a\ntrial. If this doesn't answer your question, call `start_inquiry` to ask\n4chems directly or request a call.","operationId":"get_catalog_templates","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TemplateV1Out"},"type":"array","title":"Response Get Catalog Templates"}}}}},"security":[],"x-anonymous":true}},"/v1/consents":{"post":{"tags":["consents"],"summary":"Record Consent","description":"Record acceptance of a legal document version and return the entry; call this\nonly after showing the user the document URL and version and receiving an\nexplicit affirmative answer.\n\nWithout `tenant_id` it records the caller's own consent; with `tenant_id` it\nrecords on behalf of that tenant and needs level owner. Idempotent for an\nalready-recorded document+version pair, which returns the existing record with\na 200 instead of creating a duplicate.","operationId":"record_consent","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":false,"x-idempotent":true,"x-mcp-body-fields":[{"name":"document_code","required":true},{"name":"document_version","required":true},{"name":"tenant_id","required":false}]},"get":{"tags":["consents"],"summary":"List Consents","description":"Return the caller's own recorded consents plus the tenant-scope consents of\nevery tenant where the caller is at least a viewer. Call this to check which legal\ndocuments have already been accepted before asking again. Pass `tenant_id` to\nlist one tenant's consents only (a caller of several tenants must).","operationId":"list_consents","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConsentOut"},"title":"Response List Consents"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/reference/legal-entity-types":{"get":{"tags":["reference"],"summary":"Get Legal Entity Types","description":"Return the legal-entity types available for *country*, ordered alphabetically.\nIf this doesn't answer your question, call `start_inquiry` to ask 4chems directly\nor request a call.\n\n- **200** — list of types; OTHER is always last.\n- **404** — country is not a supported market (`reference.country_not_supported`).\n- **422** — *country* is malformed (not two uppercase letters).\n- **401** — invalid or expired bearer token (no silent anonymous fallback).","operationId":"get_legal_entity_types","security":[],"parameters":[{"name":"country","in":"query","required":true,"schema":{"type":"string","pattern":"^[A-Z]{2}$","description":"ISO 3166-1 alpha-2 country code","title":"Country"},"description":"ISO 3166-1 alpha-2 country code"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LegalEntityTypeOut"},"title":"Response Get Legal Entity Types"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-anonymous":true}},"/v1/tenant/invitations":{"post":{"tags":["invitations"],"summary":"Create Invitation","description":"Invite an email address to the caller's tenant at the specified access\nlevel and return the invitation, including the raw single-use accept\ntoken. Call this when a tenant admin wants to add a new member; the\ninvitee accepts via `accept_invitation`.","operationId":"create_invitation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationCreatedOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":false,"x-idempotent":false},"get":{"tags":["invitations"],"summary":"List Invitations","description":"List all invitations for the caller's tenant, most recent first. Call this to\nshow a tenant admin which invitations are pending, accepted, or revoked; the\nraw token is never included.","operationId":"list_invitations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InvitationOut"},"title":"Response List Invitations"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tenant/invitations/{invitation_id}":{"delete":{"tags":["invitations"],"summary":"Revoke Invitation","description":"Revoke a pending invitation so its token can no longer be accepted. Call this\nwhen a tenant admin wants to withdraw an invite sent by mistake or no longer\nneeded; accepted invitations cannot be revoked.","operationId":"revoke_invitation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Invitation Id"}},{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":true,"x-idempotent":true}},"/v1/invitations/{token}/accept":{"post":{"tags":["invitations"],"summary":"Accept Invitation","description":"Accept a pending invitation and grant the invitee's first role assignment.\nCall this when an invitee has a valid invite link and wants to join the\ntenant; single-use, consuming the token marks it accepted.\n\nThe caller must be authenticated (CUSTOMER role) so their KC subject is\nrecorded in the new role_assignment.  No scope pre-condition is required.","operationId":"accept_invitation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":false,"x-idempotent":false}},"/v1/onboarding/route":{"get":{"tags":["onboarding"],"summary":"Get Onboarding Route","description":"Return the next onboarding step. If this doesn't answer your question, call\n`start_inquiry` to ask 4chems directly or request a call.\n\nAnonymous call (no token): returns action=register with no URL and a\n`next_step` naming `start_account_registration` and `start_device_login`\n(UC-091).\nA presented but invalid/expired token returns 401 (no silent fallback).\n\nAuthenticated call: existing routing logic (email_verified gate, domain\nlookup, etc.) — behaviour is unchanged from pre-UC-053 (UC-014).\nThe email domain is derived from the token's ``email`` claim only.\nZero DB reads are performed before the email_verified gate (NFR-006 pt 3).","operationId":"get_onboarding_route","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingOut"}}}}},"security":[],"x-anonymous":true}},"/v1/onboarding/access-requests":{"post":{"tags":["onboarding"],"summary":"Create Access Request Endpoint","description":"Request access to the tenant that owns the caller's verified email domain.\n\nIdempotent: a repeat POST while a pending request exists returns the same\noperation envelope without creating a duplicate row or notification.\nA dismissed request allows a fresh re-ask (rate-limited to 3/day, NFR-002).","operationId":"create_access_request","responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}}},"security":[{"HTTPBearer":[]}],"x-destructive":false,"x-idempotent":true}},"/v1/onboarding/access-requests/{access_request_id}:approve":{"post":{"tags":["onboarding"],"summary":"Approve Access Request Endpoint","description":"Approve a pending access request: issue a `member` invitation to the\nrequester and resolve the request (level `admin` or `owner` of the\nrequest's tenant). The response carries the single-use invitation token\nto hand to the requester, who accepts via `accept_invitation`. 409\n`access_request.not_pending` unless the request is pending.","operationId":"approve_access_request","security":[{"HTTPBearer":[]}],"parameters":[{"name":"access_request_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Access Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestApprovedOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":false,"x-idempotent":false}},"/v1/onboarding/access-requests/{access_request_id}:dismiss":{"post":{"tags":["onboarding"],"summary":"Dismiss Access Request Endpoint","description":"Dismiss a pending access request (level `admin` or `owner` of the\nrequest's tenant). The requester is mailed — naming the tenant, never the\ndismissing admin — and may ask again. 409 `access_request.not_pending`\nunless the request is pending.","operationId":"dismiss_access_request","security":[{"HTTPBearer":[]}],"parameters":[{"name":"access_request_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Access Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":true,"x-idempotent":false}},"/v1/onboarding/access-requests/{access_request_id}:withdraw":{"post":{"tags":["onboarding"],"summary":"Withdraw Access Request Endpoint","description":"Withdraw the caller's own pending access request; no notification is\nsent. Any other caller gets 404 `access_request.not_found`, the same as\nfor an unknown id. 409 `access_request.not_pending` unless pending.","operationId":"withdraw_access_request","security":[{"HTTPBearer":[]}],"parameters":[{"name":"access_request_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Access Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-destructive":true,"x-idempotent":false}},"/v1/onboarding/tenants":{"post":{"tags":["onboarding"],"summary":"Create Self Service Tenant","description":"Create a 7-day trial tenant for the caller's business email domain (UC-052).\nNeeds a verified email on a business domain and `attestation: true`; answers\n`auth.email_not_verified`, `tenant.attestation_required`,\n`tenant.free_mail_domain` (free-mail address), `tenant.domain_exists`\n(a live trial tenant on the domain) or `trial.exists_for_domain` (the\ndomain already had its one trial; call `start_inquiry`). The trial lasts\nexactly 7 days; `ttl` / `expires_at` in the body are ignored.\n\nDomain is derived from the verified token email claim (NFR-006 pt 4).\nThe caller becomes trial admin; the tenant is trial by construction\n(UC-044 flavour, ≤7-day expiry, guaranteed teardown) and cannot be\nmade permanent via this endpoint.\n\nGuard order (nothing persisted on any failure):\n  1. email_verified gate (NFR-006 pt 3)\n  2. attestation=true (NFR-006 §8a)\n  3. per-principal rate limit (NFR-002 pt 2, ≤10/min)\n  4. free-mail domain block (NFR-006 pt 5)\n  5. live-tenant guard, then trial-once (UC-099) — no re-trial after teardown","operationId":"create_self_service_tenant","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelfServiceTenantCreateIn"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}],"x-destructive":false,"x-idempotent":false}},"/v1/onboarding/entry":{"post":{"tags":["onboarding"],"summary":"Check Entry","description":"Retired (UC-091): always answers 410 ``onboarding.entry_retired``.\n\nThe entry check revealed whether an address has an account (UC-085 D2).\nNo Keycloak request is made, with or without a token. Removed in the next\nminor release.","operationId":"check_entry","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryCheckIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"deprecated":true,"security":[],"x-anonymous":true,"x-no-mcp":true}},"/v1/onboarding/enrichment":{"post":{"tags":["onboarding"],"summary":"Enrich Domain Endpoint","description":"Fetch the caller's domain imprint and suggest legal-entity details (UC-057).\n\nEmpty body (domain comes from the token email claim only — NFR-006 pt 4).\nSuggest-only: always 200, never errors the wizard. All-null on any failure.\nFree-mail domains return all-null without any outbound fetch (AC4).\nPer-principal rate limit: RATE_LIMIT_ENRICHMENT_PER_MINUTE (default 5/min).","operationId":"enrich_domain","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentOut"}}}}},"security":[{"HTTPBearer":[]}],"x-destructive":false,"x-idempotent":true,"x-open-world":true}},"/v1/onboarding/login/device":{"post":{"tags":["onboarding"],"summary":"Start Device Login","description":"Sign in to an existing account by device login (UC-091, RFC 8628). Anonymous,\nempty body; the account must exist (start_account_registration, then set the\npassword through the emailed link). Show `verification_uri` (or\n`verification_uri_complete`) and `user_code` to the human, who signs in there,\nthen call `verify_device_login` with `challenge_id`, waiting `interval` seconds\nbetween calls. The response never contains the Keycloak device_code or the\nagent client secret.","operationId":"start_device_login","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceChallengeOut"}}}}},"security":[],"x-destructive":false,"x-idempotent":false,"x-anonymous":true}},"/v1/onboarding/login/device/verify":{"post":{"tags":["onboarding"],"summary":"Verify Device Login","description":"Poll a device login to token issuance (UC-091, RFC 8628). Anonymous. While\nthe human has not signed in yet it answers `{\"status\": \"pending\",\n\"retry_after\": <seconds>}`: wait `retry_after` seconds and call again with the\nsame `challenge_id`. On approval it returns Keycloak's token set\n(`access_token`, `refresh_token`, `expires_in`, ...) exactly once; pass the\n`access_token` as `authorization` ('Bearer <token>') to every other tool.","operationId":"verify_device_login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceVerifyIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[],"x-destructive":false,"x-idempotent":false,"x-anonymous":true}},"/v1/onboarding/registrations/device":{"post":{"tags":["onboarding"],"summary":"Start Registration","description":"Deprecated — use start_device_login (same behaviour).\n\nStart a Keycloak device-authorization grant (UC-075, RFC 8628).\n\nAnonymous, empty body. Show the returned `verification_uri` (or\n`verification_uri_complete`) and `user_code` to a human on any device;\nthey register (self-registration) or log in there. Then call\n`verify_registration` with `challenge_id`, sleeping `interval` seconds\nbetween calls while it answers 202. The response never contains the\nKeycloak device_code or the agent client secret.","operationId":"start_registration","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceChallengeOut"}}}}},"deprecated":true,"security":[],"x-destructive":false,"x-idempotent":false,"x-replaced-by":"start_device_login","x-anonymous":true}},"/v1/onboarding/registrations/device/verify":{"post":{"tags":["onboarding"],"summary":"Verify Registration","description":"Deprecated — use verify_device_login (same behaviour).\n\nPoll a device-authorization challenge to token issuance (UC-075, RFC 8628).\n\nAnonymous. While the human has not yet acted, answers 202\n`{\"status\": \"pending\", \"retry_after\": <seconds>}` — sleep that many\nseconds and call again with the same `challenge_id`. On approval,\nanswers 200 with Keycloak's token set (`access_token`, `refresh_token`,\n`expires_in`, ...) handed over exactly once; every existing token-gated\ntool then works unchanged with that `access_token`.","operationId":"verify_registration","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceVerifyIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"deprecated":true,"security":[],"x-destructive":false,"x-idempotent":false,"x-replaced-by":"verify_device_login","x-anonymous":true}},"/v1/onboarding/registrations/account":{"post":{"tags":["onboarding"],"summary":"Start Account Registration","description":"Start registering a 4chems account with a business email address (`email`,\n`name`, optional `company`). Anonymous. Emails a 6-digit code to the address;\nno account exists yet. Ask the user for the code, then call\nverify_account_registration with the returned challenge_id and code. Free-mail\naddresses (gmail.com, ...) are refused: use the company address, or call\nstart_inquiry. The answer is the same whether or not the address already has\nan account.","operationId":"start_account_registration","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountRegistrationIn"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountChallengeOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[],"x-destructive":false,"x-idempotent":false,"x-anonymous":true}},"/v1/onboarding/registrations/account/verify":{"post":{"tags":["onboarding"],"summary":"Verify Account Registration","description":"Verify the emailed 6-digit code (`challenge_id`, `code`) and create the\n4chems account. Never guess or retry codes, and never ask the user for a\npassword.\n\nAnonymous. On a matching code, creates the user (email already verified)\nand Keycloak mails a link where the user sets their own password; no\npassword passes through this API or the agent. Then call\nstart_device_login to sign in. Answers 409 registration.exists when the\naddress already has an account, and 503 registration.idp_unavailable on\na Keycloak failure — retry with the same challenge_id and code.","operationId":"verify_account_registration","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountVerifyIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountCreatedOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[],"x-destructive":false,"x-idempotent":false,"x-anonymous":true}},"/v1/inquiries":{"post":{"tags":["inquiries"],"summary":"Start Inquiry","description":"Store an anonymous inquiry draft and email a 6-digit verification code.\nAsk the user for the code, then call verify_inquiry with the returned\nchallenge_id and code — the inquiry reaches 4chems only after that\nverification succeeds.","operationId":"start_inquiry","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InquiryIn"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InquiryChallengeOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[],"x-destructive":false,"x-idempotent":false,"x-anonymous":true}},"/v1/inquiries/verify":{"post":{"tags":["inquiries"],"summary":"Verify Inquiry","description":"Verify the 6-digit code and deliver the inquiry to the helpdesk inbox.","operationId":"verify_inquiry","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InquiryVerifyIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InquiryReceivedOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[],"x-destructive":false,"x-idempotent":false,"x-anonymous":true}}},"components":{"schemas":{"AccessRequestApprovedOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"invitation_id":{"type":"string","format":"uuid","title":"Invitation Id"},"token":{"type":"string","title":"Token"}},"type":"object","required":["id","tenant_id","status","created_at","updated_at","invitation_id","token"],"title":"AccessRequestApprovedOut","description":"`approve_access_request` response: the issued invitation, with its\nsingle-use accept token for the admin to hand to the requester."},"AccessRequestOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","tenant_id","status","created_at","updated_at"],"title":"AccessRequestOut","description":"An access request after approve / dismiss / withdraw (UC-094)."},"AccountChallengeOut":{"properties":{"challenge_id":{"type":"string","format":"uuid","title":"Challenge Id"},"expires_in_seconds":{"type":"integer","title":"Expires In Seconds"},"next_step":{"type":"string","title":"Next Step"}},"type":"object","required":["challenge_id","expires_in_seconds","next_step"],"title":"AccountChallengeOut"},"AccountCreatedOut":{"properties":{"account":{"type":"string","const":"created","title":"Account"},"next_step":{"type":"string","title":"Next Step"}},"type":"object","required":["account","next_step"],"title":"AccountCreatedOut"},"AccountRegistrationIn":{"properties":{"email":{"type":"string","title":"Email"},"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"company":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Company"}},"type":"object","required":["email","name"],"title":"AccountRegistrationIn","description":"Request body for POST /v1/onboarding/registrations/account (UC-088)."},"AccountVerifyIn":{"properties":{"challenge_id":{"type":"string","format":"uuid","title":"Challenge Id"},"code":{"type":"string","maxLength":32,"minLength":1,"title":"Code"}},"type":"object","required":["challenge_id","code"],"title":"AccountVerifyIn","description":"Request body for POST /v1/onboarding/registrations/account/verify (UC-089)."},"AssignmentIn":{"properties":{"role":{"type":"string","title":"Role"},"scope_type":{"type":"string","title":"Scope Type"},"scope_id":{"type":"string","title":"Scope Id"}},"type":"object","required":["role","scope_type","scope_id"],"title":"AssignmentIn"},"AssignmentOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"subject_type":{"type":"string","title":"Subject Type"},"subject":{"type":"string","title":"Subject"},"role":{"type":"string","title":"Role"},"scope_type":{"type":"string","title":"Scope Type"},"scope_id":{"type":"string","title":"Scope Id"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"granted_by":{"type":"string","title":"Granted By"},"granted_at":{"type":"string","format":"date-time","title":"Granted At"}},"type":"object","required":["id","subject_type","subject","role","scope_type","scope_id","expires_at","granted_by","granted_at"],"title":"AssignmentOut"},"AttestationIn":{"properties":{"attested":{"type":"boolean","title":"Attested"},"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["attested","email"],"title":"AttestationIn","description":"Attestation checkbox (NFR-006 pt 8). Stored with timestamp on the tenant row."},"BillingIn":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"legal_entity_type":{"anyOf":[{"type":"string","pattern":"^[A-Z]{2,3}_[A-Z0-9][A-Z0-9_]*$"},{"type":"null"}],"title":"Legal Entity Type"},"other_label":{"anyOf":[{"type":"string","maxLength":80,"minLength":2},{"type":"null"}],"title":"Other Label"},"vat_id":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"title":"Vat Id"},"address_line1":{"type":"string","maxLength":200,"minLength":1,"title":"Address Line1"},"address_line2":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Address Line2"},"postal_code":{"type":"string","maxLength":20,"minLength":1,"title":"Postal Code"},"city":{"type":"string","maxLength":100,"minLength":1,"title":"City"},"invoice_email":{"type":"string","format":"email","title":"Invoice Email"},"po_reference":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Po Reference"}},"type":"object","required":["address_line1","postal_code","city","invoice_email"],"title":"BillingIn","description":"`complete_billing` body. `vat_id` is checked against the tenant's country in the service."},"BillingOut":{"properties":{"name":{"type":"string","title":"Name"},"country":{"type":"string","title":"Country"},"legal_entity_type":{"type":"string","title":"Legal Entity Type"},"other_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Other Label"},"vat_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vat Id"},"address_line1":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Line1"},"address_line2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Line2"},"postal_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postal Code"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"invoice_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Email"},"po_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Po Reference"},"vat_validated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Vat Validated At"},"billing_completed":{"type":"boolean","title":"Billing Completed"},"billing_completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Billing Completed At"}},"type":"object","required":["name","country","legal_entity_type","billing_completed"],"title":"BillingOut","description":"The stored billing data, `get_billing` (owner and billing contacts only)."},"BotSignalsIn":{"properties":{"honeypot":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Honeypot"},"submit_timing_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Submit Timing Ms"}},"type":"object","title":"BotSignalsIn","description":"Advisory bot-detection signals from the onboarding wizard (UC-052).\n\nRecorded and fed to scoring; never used as a hard gate."},"ConsentIn":{"properties":{"document_code":{"type":"string","title":"Document Code"},"document_version":{"type":"string","title":"Document Version"},"tenant_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Tenant Id","description":"Record on behalf of this tenant (needs level owner). Omit to record the caller's own consent."}},"type":"object","required":["document_code","document_version"],"title":"ConsentIn"},"ConsentOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"subject_sub":{"type":"string","title":"Subject Sub"},"tenant_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Tenant Id"},"document_code":{"type":"string","title":"Document Code"},"document_version":{"type":"string","title":"Document Version"},"granted_at":{"type":"string","format":"date-time","title":"Granted At"},"source":{"type":"string","title":"Source"}},"type":"object","required":["id","subject_sub","document_code","document_version","granted_at","source"],"title":"ConsentOut"},"DeviceChallengeOut":{"properties":{"challenge_id":{"type":"string","format":"uuid","title":"Challenge Id"},"verification_uri":{"type":"string","title":"Verification Uri"},"verification_uri_complete":{"type":"string","title":"Verification Uri Complete"},"user_code":{"type":"string","title":"User Code"},"expires_in":{"type":"integer","title":"Expires In"},"interval":{"type":"integer","title":"Interval"}},"type":"object","required":["challenge_id","verification_uri","verification_uri_complete","user_code","expires_in","interval"],"title":"DeviceChallengeOut","description":"Response for POST /v1/onboarding/registrations/device (UC-075).\n\nNever carries the Keycloak device_code or the agent client secret."},"DeviceVerifyIn":{"properties":{"challenge_id":{"type":"string","format":"uuid","title":"Challenge Id"}},"type":"object","required":["challenge_id"],"title":"DeviceVerifyIn","description":"Request body for POST /v1/onboarding/registrations/device/verify (UC-075)."},"EmailNotification":{"properties":{"to":{"type":"string","format":"email","title":"To"},"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"}},"type":"object","required":["to","subject","body"],"title":"EmailNotification"},"EnrichmentOut":{"properties":{"legal_entity_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legal Entity Name"},"entity_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"confidence":{"type":"number","title":"Confidence","default":0.0},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"}},"type":"object","title":"EnrichmentOut","description":"Response for POST /v1/onboarding/enrichment (UC-057).\n\nSuggest-only: all fields are nullable; confidence=0 means no suggestion.\nThe UI must fall back to manual entry when confidence is low."},"EntryCheckIn":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"EntryCheckIn","description":"Request body for POST /v1/onboarding/entry (UC-055)."},"ErrorDetail":{"properties":{"code":{"type":"string","title":"Code"},"message":{"type":"string","title":"Message"}},"type":"object","required":["code","message"],"title":"ErrorDetail"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InquiryChallengeOut":{"properties":{"challenge_id":{"type":"string","format":"uuid","title":"Challenge Id"},"expires_in_seconds":{"type":"integer","title":"Expires In Seconds"},"next_step":{"type":"string","title":"Next Step"}},"type":"object","required":["challenge_id","expires_in_seconds","next_step"],"title":"InquiryChallengeOut"},"InquiryIn":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"email":{"type":"string","format":"email","title":"Email"},"company":{"type":"string","maxLength":120,"title":"Company","default":""},"topic":{"$ref":"#/components/schemas/InquiryTopic"},"message":{"type":"string","maxLength":4000,"minLength":10,"title":"Message"},"website":{"type":"string","title":"Website","default":""}},"type":"object","required":["name","email","topic","message"],"title":"InquiryIn"},"InquiryReceivedOut":{"properties":{"inquiry_id":{"type":"string","format":"uuid","title":"Inquiry Id"},"reference":{"type":"string","title":"Reference"},"status":{"type":"string","title":"Status"}},"type":"object","required":["inquiry_id","reference","status"],"title":"InquiryReceivedOut"},"InquiryTopic":{"type":"string","enum":["migration","hosting","iuclid","chesar","qsar_toolbox","other"],"title":"InquiryTopic"},"InquiryVerifyIn":{"properties":{"challenge_id":{"type":"string","format":"uuid","title":"Challenge Id"},"code":{"type":"string","maxLength":6,"minLength":6,"title":"Code"}},"type":"object","required":["challenge_id","code"],"title":"InquiryVerifyIn"},"InvitationCreatedOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"tenant_id":{"type":"string","title":"Tenant Id"},"email":{"type":"string","title":"Email"},"level":{"type":"string","title":"Level"},"billing_contact":{"type":"boolean","title":"Billing Contact","default":false},"status":{"type":"string","title":"Status"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"invited_by":{"type":"string","title":"Invited By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accepted At"},"token":{"type":"string","title":"Token"}},"type":"object","required":["id","tenant_id","email","level","status","expires_at","invited_by","created_at","token"],"title":"InvitationCreatedOut","description":"Returned only on POST — includes the raw token for sharing the invite link."},"InvitationIn":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"level":{"$ref":"#/components/schemas/Role","default":"viewer"},"billing_contact":{"type":"boolean","title":"Billing Contact","default":false}},"type":"object","required":["email"],"title":"InvitationIn"},"InvitationOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"tenant_id":{"type":"string","title":"Tenant Id"},"email":{"type":"string","title":"Email"},"level":{"type":"string","title":"Level"},"billing_contact":{"type":"boolean","title":"Billing Contact","default":false},"status":{"type":"string","title":"Status"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"invited_by":{"type":"string","title":"Invited By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accepted At"}},"type":"object","required":["id","tenant_id","email","level","status","expires_at","invited_by","created_at"],"title":"InvitationOut"},"KillSwitchOut":{"properties":{"name":{"type":"string","title":"Name"},"env_default":{"type":"boolean","title":"Env Default"},"override":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Override"},"effective":{"type":"boolean","title":"Effective"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["name","env_default","override","effective","updated_by","updated_at"],"title":"KillSwitchOut"},"KillSwitchPatchIn":{"properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"}},"type":"object","required":["enabled"],"title":"KillSwitchPatchIn"},"LegalEntityIn":{"properties":{"country":{"type":"string","pattern":"^[A-Z]{2}$","title":"Country"},"legal_entity_type":{"type":"string","pattern":"^[A-Z]{2,3}_[A-Z0-9][A-Z0-9_]*$","title":"Legal Entity Type"},"other_label":{"anyOf":[{"type":"string","maxLength":80,"minLength":2},{"type":"null"}],"title":"Other Label"},"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name"}},"type":"object","required":["country","legal_entity_type","name"],"title":"LegalEntityIn","description":"Composite legal-entity input validated against the country catalog.\n\nRules:\n- *country* must be a supported market (validated at field level via SupportedCountry,\n  caught in the model validator against the loaded catalog).\n- *legal_entity_type* must appear in that country's catalog file.\n- When *legal_entity_type* is ``<CC>_OTHER``, *other_label* is required (2-80 chars).\n- For any other type, *other_label* must be absent."},"LegalEntityTypeOut":{"properties":{"code":{"type":"string","title":"Code"},"abbreviation":{"type":"string","title":"Abbreviation"},"name":{"type":"string","title":"Name"}},"type":"object","required":["code","abbreviation","name"],"title":"LegalEntityTypeOut"},"Level":{"type":"string","enum":["none","requester","viewer","member","admin","owner"],"title":"Level"},"MemberOut":{"properties":{"subject":{"type":"string","title":"Subject"},"assignments":{"items":{"$ref":"#/components/schemas/AssignmentOut"},"type":"array","title":"Assignments"}},"type":"object","required":["subject","assignments"],"title":"MemberOut"},"MembersListOut":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"members":{"items":{"$ref":"#/components/schemas/MemberOut"},"type":"array","title":"Members"}},"type":"object","required":["tenant_id","members"],"title":"MembersListOut"},"NextStepOut":{"properties":{"operation":{"type":"string","title":"Operation"},"documents":{"items":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":"object"},"type":"array","title":"Documents"}},"type":"object","required":["operation","documents"],"title":"NextStepOut","description":"What a `member` must do next: record consents to the listed documents (UC-093)."},"OperationInfraOut":{"properties":{"operation_id":{"type":"string","format":"uuid","title":"Operation Id"},"status":{"type":"string","title":"Status"},"type":{"type":"string","title":"Type"},"tier":{"type":"string","title":"Tier"},"tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"},"actor_sub":{"type":"string","title":"Actor Sub"},"target_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Type"},"target_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Id"},"result":{"anyOf":[{},{"type":"null"}],"title":"Result"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"attempts":{"type":"integer","title":"Attempts"},"max_attempts":{"type":"integer","title":"Max Attempts"},"cancel_requested":{"type":"boolean","title":"Cancel Requested"},"retried_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Retried By"},"cancelled_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cancelled By"},"locked_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Locked Until"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"}},"type":"object","required":["operation_id","status","type","tier","actor_sub","attempts","max_attempts","cancel_requested","created_at"],"title":"OperationInfraOut","description":"Full detail schema for the /infra operations console (UC-022).\n\nIntentionally excludes idempotency_key — it is a caller secret."},"OperationListOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/OperationInfraOut"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"offset":{"type":"integer","title":"Offset"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["items","total","offset","limit"],"title":"OperationListOut"},"OperationOut":{"properties":{"operation_id":{"type":"string","format":"uuid","title":"Operation Id"},"status":{"type":"string","title":"Status"},"type":{"type":"string","title":"Type"},"target_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Type"},"target_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Id"},"result":{"anyOf":[{},{"type":"null"}],"title":"Result"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"}},"type":"object","required":["operation_id","status","type","created_at"],"title":"OperationOut"},"OperationStatus":{"type":"string","enum":["pending","running","succeeded","failed","cancelled"],"title":"OperationStatus"},"OperationTier":{"type":"string","enum":["ops","infra","v1"],"title":"OperationTier"},"Role":{"type":"string","enum":["viewer","member","admin","owner"],"title":"Role","description":"The grantable tenant access levels (ADR 0016 decision 3: role collapse).\n\nScoped department/instance roles (``team_admin``, ``env_admin``,\n``app_admin``, ``application_owner``, ``application_supervisor``,\n``application_user``) and the platform roles (``platform_admin``,\n``node_admin``) are no longer grantable; the migration\n(``uc087accesslevelscore_0001``) maps every existing row onto one of\nthese four values."},"RoutingAction":{"type":"string","enum":["wizard","wizard_no_tenant_domain","request_access","trial_used","member","register"],"title":"RoutingAction"},"RoutingOut":{"properties":{"action":{"type":"string","title":"Action","x-extensible-enum":["wizard","wizard_no_tenant_domain","request_access","trial_used","member","register"]},"next_step":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/NextStepOut"},{"type":"null"}],"title":"Next Step"},"tenant_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Name"},"registration_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registration Url"},"registration_methods":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Registration Methods"},"subscription":{"anyOf":[{"$ref":"#/components/schemas/SubscriptionOut"},{"type":"null"}]}},"type":"object","required":["action"],"title":"RoutingOut"},"SelfServiceTenantCreateIn":{"properties":{"legal_entity":{"$ref":"#/components/schemas/LegalEntityIn"},"attestation":{"type":"boolean","title":"Attestation"},"bot_signals":{"anyOf":[{"$ref":"#/components/schemas/BotSignalsIn"},{"type":"null"}]}},"type":"object","required":["legal_entity","attestation"],"title":"SelfServiceTenantCreateIn","description":"Request body for POST /v1/onboarding/tenants (UC-052).\n\nThe domain is derived from the caller's verified token email claim\n(NFR-006 pt 4) — it is not a request parameter.\nattestation must be true; the endpoint raises 422 if false (NFR-006 §8a)."},"ServiceSubscription":{"properties":{"service":{"type":"string","title":"Service"}},"type":"object","required":["service"],"title":"ServiceSubscription"},"SubscriptionOut":{"properties":{"state":{"$ref":"#/components/schemas/SubscriptionState"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"grace_ends_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Grace Ends At"}},"type":"object","required":["state","expires_at","grace_ends_at"],"title":"SubscriptionOut"},"SubscriptionState":{"type":"string","enum":["trialing","expired","erased","active"],"title":"SubscriptionState","description":"Commercial state (UC-096, UC-086 D7); `TrialStatus` is provisioning only."},"TemplateCreateIn":{"properties":{"code":{"type":"string","maxLength":63,"minLength":1,"pattern":"^[a-z0-9][a-z0-9-]*$","title":"Code"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"repository_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repository Url"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"kind":{"type":"string","enum":["deployed","platform_feature"],"title":"Kind"},"legacy":{"type":"boolean","title":"Legacy","default":false},"legacy_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legacy Note"},"includes":{"items":{"type":"string"},"type":"array","title":"Includes","default":[]},"options":{"items":{},"type":"array","title":"Options","default":[]}},"type":"object","required":["code","name","kind"],"title":"TemplateCreateIn"},"TemplateOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"repository_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repository Url"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"kind":{"type":"string","title":"Kind"},"legacy":{"type":"boolean","title":"Legacy"},"legacy_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legacy Note"},"includes":{"title":"Includes"},"options":{"title":"Options"},"enabled":{"type":"boolean","title":"Enabled"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","code","name","description","image_url","repository_url","version","kind","legacy","legacy_note","includes","options","enabled","created_at","updated_at"],"title":"TemplateOut"},"TemplateUpdateIn":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"repository_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repository Url"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"kind":{"anyOf":[{"type":"string","enum":["deployed","platform_feature"]},{"type":"null"}],"title":"Kind"},"legacy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Legacy"},"legacy_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legacy Note"},"includes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Includes"},"options":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Options"}},"type":"object","title":"TemplateUpdateIn"},"TemplateV1Out":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"kind":{"type":"string","title":"Kind"},"legacy":{"type":"boolean","title":"Legacy"},"legacy_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legacy Note"},"includes":{"title":"Includes"},"options":{"title":"Options"},"enabled":{"type":"boolean","title":"Enabled"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","code","name","description","kind","legacy","legacy_note","includes","options","enabled","created_at","updated_at"],"title":"TemplateV1Out","description":"Customer-facing (/v1) catalog item (UC-072).\n\nDeliberately narrower than ``TemplateOut``: ``version``, ``image_url`` and\n``repository_url`` are dropped — no data source populates them and they were\npermanently null in the public response. The `/infra` catalog admin schema\n(``TemplateOut``) and the database columns are unchanged."},"TemplatedEmailNotification":{"properties":{"to":{"type":"string","format":"email","title":"To"},"template_name":{"type":"string","title":"Template Name"},"context":{"additionalProperties":true,"type":"object","title":"Context"}},"type":"object","required":["to","template_name","context"],"title":"TemplatedEmailNotification"},"TenantAccessOut":{"properties":{"tenant":{"type":"string","title":"Tenant"},"level":{"$ref":"#/components/schemas/Level"},"via":{"type":"string","title":"Via"},"subscription":{"$ref":"#/components/schemas/SubscriptionOut"}},"type":"object","required":["tenant","level","via","subscription"],"title":"TenantAccessOut"},"TenantCreateIn":{"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,30}$","title":"Slug"},"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name"},"initial_admin_email":{"type":"string","format":"email","title":"Initial Admin Email"},"attestation":{"$ref":"#/components/schemas/AttestationIn"},"legal_entity":{"$ref":"#/components/schemas/LegalEntityIn"},"allow_duplicate_domain":{"type":"boolean","title":"Allow Duplicate Domain","default":false}},"type":"object","required":["slug","name","initial_admin_email","attestation","legal_entity"],"title":"TenantCreateIn"},"TenantListOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TenantOut"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"offset":{"type":"integer","title":"Offset"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["items","total","offset","limit"],"title":"TenantListOut"},"TenantOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"domain":{"type":"string","title":"Domain"},"status":{"type":"string","title":"Status"},"flavour":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flavour"},"created_by_email":{"type":"string","title":"Created By Email"},"attested_at":{"type":"string","format":"date-time","title":"Attested At"},"attested_by_email":{"type":"string","title":"Attested By Email"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","slug","name","domain","status","flavour","created_by_email","attested_at","attested_by_email","created_at","updated_at"],"title":"TenantOut"},"TenantStatus":{"type":"string","enum":["provisioning","pending_activation","active","disabled"],"title":"TenantStatus"},"TrialCreateIn":{"properties":{"legal_entity":{"$ref":"#/components/schemas/LegalEntityIn"},"initial_admin_email":{"type":"string","format":"email","title":"Initial Admin Email"},"products":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Products"},"seed":{"$ref":"#/components/schemas/TrialSeed","default":"empty"},"ttl":{"type":"integer","maximum":14.0,"minimum":1.0,"title":"Ttl","default":14},"override_trial_once":{"type":"boolean","title":"Override Trial Once","default":false},"override_reason":{"anyOf":[{"type":"string","maxLength":500,"minLength":1},{"type":"null"}],"title":"Override Reason"}},"type":"object","required":["legal_entity","initial_admin_email","products"],"title":"TrialCreateIn"},"TrialExtendIn":{"properties":{"days":{"type":"integer","minimum":1.0,"title":"Days"},"reason":{"type":"string","maxLength":500,"minLength":1,"title":"Reason"}},"additionalProperties":false,"type":"object","required":["days","reason"],"title":"TrialExtendIn","description":"Operator extension (UC-098); the upper bound is a 409 (`trials_extension_max_days`)."},"TrialLoadDataIn":{"properties":{"upload_ref":{"type":"string","maxLength":2048,"minLength":1,"title":"Upload Ref"}},"type":"object","required":["upload_ref"],"title":"TrialLoadDataIn"},"TrialOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id"},"email_domain":{"type":"string","title":"Email Domain"},"seed":{"type":"string","title":"Seed"},"status":{"type":"string","title":"Status"},"subscription_state":{"type":"string","title":"Subscription State"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"products":{"items":{"type":"string"},"type":"array","title":"Products"},"extensions_count":{"type":"integer","title":"Extensions Count"},"erasure_attestation":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Erasure Attestation"},"import_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Ref"},"data_loaded":{"type":"boolean","title":"Data Loaded"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","tenant_id","email_domain","seed","status","subscription_state","expires_at","products","extensions_count","erasure_attestation","import_ref","data_loaded","created_at","updated_at"],"title":"TrialOut"},"TrialSeed":{"type":"string","enum":["empty","demo","with_data"],"title":"TrialSeed"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}