openapi: 3.1.0 info: title: portal API version: 1.0.0 description: | The portal HTTP API. Every route chi serves under /api/v1 (internal/server/server.go) is described here; the schemas mirror the handler-layer *Response types in internal/handler (the wire boundary — repository rows never serialize directly). web/src/api/types.ts is generated from this file with openapi-typescript (`npm run generate:api` in web/); openapi-fetch consumes the generated `paths` interface. Edit this spec, regenerate, and the frontend contract follows — CI fails on drift. Two surfaces sit outside the JSON request/response model: - GET /workspaces/{workspaceId}/runs/{runId}/logs/ws upgrades to a WebSocket (documented on the operation; the stream itself is newline-delimited log text, authenticated via the `bearer` + token subprotocol pair). - GET /auth/github and /auth/github/callback are browser redirect hops of the OAuth flow, not XHR endpoints. Errors use one envelope everywhere (ErrorResponse) via respond.Error / respond.FromError; operations declare it as their default response. servers: - url: /api/v1 paths: /health: get: operationId: getHealth summary: App-level health surface the UI reads description: > Per-service status plus the dev_login flag in development. Returns 503 with the same body shape when degraded. (Liveness/readiness probes live at /healthz and /readyz outside /api/v1.) security: [] responses: "200": description: Healthy content: application/json: schema: { $ref: "#/components/schemas/HealthStatus" } "503": description: Degraded content: application/json: schema: { $ref: "#/components/schemas/HealthStatus" } /auth/github: get: operationId: githubLogin summary: Start the GitHub OAuth flow (browser redirect) security: [] responses: "307": description: Redirect to GitHub's authorize URL; sets the signed oauth_state cookie. default: { $ref: "#/components/responses/Error" } /auth/github/callback: get: operationId: githubCallback summary: GitHub OAuth callback (browser redirect) description: > Exchanges the code, provisions the user, sets the short-lived HttpOnly handoff cookie (scoped to /auth/handoff), and redirects to the SPA callback page, which completes login via POST /auth/handoff. security: [] parameters: - name: code in: query schema: { type: string } - name: state in: query schema: { type: string } responses: "307": description: Redirect to the SPA /auth/callback page. default: { $ref: "#/components/responses/Error" } /auth/handoff: post: operationId: authHandoff summary: Exchange the HttpOnly login-handoff cookie for the JWT (one-time) description: > Completes the login handoff. The OAuth callback (and dev login) set a short-lived HttpOnly cookie scoped to this path; this endpoint returns the JWT once and expires the cookie in the same response (delete-on-read), so a second call gets 401. POST keeps the exchange non-cacheable, and SameSite=Lax blocks cross-site POSTs from carrying the cookie. Unauthenticated by design — the caller has no bearer token yet. security: [] responses: "200": description: The JWT, exactly once; the handoff cookie is expired in the same response. content: application/json: schema: type: object properties: token: { type: string } required: [token] "401": description: Missing, expired, or malformed handoff cookie content: application/json: schema: { $ref: "#/components/schemas/ErrorResponse" } default: { $ref: "#/components/responses/Error" } /auth/me: get: operationId: getMe summary: The authenticated user responses: "200": description: Current user content: application/json: schema: { $ref: "#/components/schemas/User" } "401": description: Not authenticated content: application/json: schema: { $ref: "#/components/schemas/ErrorResponse" } default: { $ref: "#/components/responses/Error" } /webhooks/github: post: operationId: githubWebhook summary: GitHub push webhook (public, HMAC-verified) security: [] requestBody: required: true content: application/json: schema: type: object additionalProperties: true description: GitHub webhook payload (ping or push event). responses: "200": description: Webhook handled (pong / ignored / runs triggered). content: application/json: schema: type: object additionalProperties: true properties: status: { type: string } triggered: { type: integer } required: [status] default: { $ref: "#/components/responses/Error" } /users: get: operationId: listUsers summary: List org users (admin) responses: "200": description: Users content: application/json: schema: { $ref: "#/components/schemas/UserList" } default: { $ref: "#/components/responses/Error" } /users/{userId}/role: put: operationId: updateUserRole summary: Change a user's role (owner) parameters: - { name: userId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UpdateRoleRequest" } responses: "200": description: Updated user content: application/json: schema: { $ref: "#/components/schemas/User" } default: { $ref: "#/components/responses/Error" } /audit-logs: get: operationId: listAuditLogs summary: List audit logs (admin) parameters: - { name: page, in: query, schema: { type: integer } } - { name: per_page, in: query, schema: { type: integer } } responses: "200": description: Audit logs content: application/json: schema: { $ref: "#/components/schemas/AuditLogList" } default: { $ref: "#/components/responses/Error" } /variables: get: operationId: listOrgVariables summary: List org variables responses: "200": description: Org variables (sensitive values redacted) content: application/json: schema: { $ref: "#/components/schemas/OrgVariableList" } default: { $ref: "#/components/responses/Error" } post: operationId: createOrgVariable summary: Create an org variable (admin) requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateVariableRequest" } responses: "201": description: Created variable content: application/json: schema: { $ref: "#/components/schemas/OrgVariable" } default: { $ref: "#/components/responses/Error" } /variables/{variableId}: put: operationId: updateOrgVariable summary: Update an org variable (admin) parameters: - { name: variableId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateVariableRequest" } responses: "200": description: Updated variable content: application/json: schema: { $ref: "#/components/schemas/OrgVariable" } default: { $ref: "#/components/responses/Error" } delete: operationId: deleteOrgVariable summary: Delete an org variable (admin) parameters: - { name: variableId, in: path, required: true, schema: { type: string } } responses: "204": { description: Deleted } default: { $ref: "#/components/responses/Error" } /variables/{variableId}/value: get: operationId: revealOrgVariableValue summary: Reveal an org variable's decrypted value (admin) parameters: - { name: variableId, in: path, required: true, schema: { type: string } } responses: "200": description: Decrypted value content: application/json: schema: { $ref: "#/components/schemas/VariableValue" } default: { $ref: "#/components/responses/Error" } /teams: get: operationId: listTeams summary: List teams parameters: - name: member_of in: query description: "`me` scopes the result to teams the caller belongs to." schema: { type: string, enum: [me] } responses: "200": description: Teams content: application/json: schema: { $ref: "#/components/schemas/TeamList" } default: { $ref: "#/components/responses/Error" } post: operationId: createTeam summary: Create a team (admin) requestBody: required: true content: application/json: schema: type: object properties: name: { type: string } required: [name] responses: "201": description: Created team content: application/json: schema: { $ref: "#/components/schemas/Team" } default: { $ref: "#/components/responses/Error" } /teams/{teamId}: get: operationId: getTeam summary: Get a team parameters: - { name: teamId, in: path, required: true, schema: { type: string } } responses: "200": description: Team content: application/json: schema: { $ref: "#/components/schemas/Team" } default: { $ref: "#/components/responses/Error" } delete: operationId: deleteTeam summary: Delete a team (admin) parameters: - { name: teamId, in: path, required: true, schema: { type: string } } responses: "204": { description: Deleted } default: { $ref: "#/components/responses/Error" } /teams/{teamId}/members: get: operationId: listTeamMembers summary: List team members parameters: - { name: teamId, in: path, required: true, schema: { type: string } } responses: "200": description: Members content: application/json: schema: { $ref: "#/components/schemas/TeamMemberList" } default: { $ref: "#/components/responses/Error" } post: operationId: addTeamMember summary: Add a team member (admin) parameters: - { name: teamId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object properties: user_id: { type: string } role: { type: string } cloud_identity: { type: string } required: [user_id, role] responses: "201": description: Added member content: application/json: schema: { $ref: "#/components/schemas/TeamMember" } default: { $ref: "#/components/responses/Error" } /teams/{teamId}/members/{userId}: put: operationId: updateTeamMember summary: Update a team member (admin) parameters: - { name: teamId, in: path, required: true, schema: { type: string } } - { name: userId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object properties: role: { type: string } cloud_identity: { type: string } required: [role] responses: "200": description: Updated member content: application/json: schema: { $ref: "#/components/schemas/TeamMember" } default: { $ref: "#/components/responses/Error" } delete: operationId: removeTeamMember summary: Remove a team member (admin) parameters: - { name: teamId, in: path, required: true, schema: { type: string } } - { name: userId, in: path, required: true, schema: { type: string } } responses: "204": { description: Removed } default: { $ref: "#/components/responses/Error" } /accounts: get: operationId: listAccounts summary: List AWS accounts (admin) parameters: - { name: page, in: query, schema: { type: integer } } - { name: per_page, in: query, schema: { type: integer } } responses: "200": description: Accounts content: application/json: schema: { $ref: "#/components/schemas/AccountList" } default: { $ref: "#/components/responses/Error" } post: operationId: createAccount summary: Register an AWS account (admin) requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateAccountRequest" } responses: "201": description: Created account content: application/json: schema: { $ref: "#/components/schemas/Account" } default: { $ref: "#/components/responses/Error" } /accounts/{accountId}: get: operationId: getAccount summary: Get an account (admin) parameters: - { name: accountId, in: path, required: true, schema: { type: string } } responses: "200": description: Account content: application/json: schema: { $ref: "#/components/schemas/Account" } default: { $ref: "#/components/responses/Error" } put: operationId: updateAccount summary: Update an account (admin) parameters: - { name: accountId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UpdateAccountRequest" } responses: "200": description: Updated account content: application/json: schema: { $ref: "#/components/schemas/Account" } default: { $ref: "#/components/responses/Error" } delete: operationId: deleteAccount summary: Delete an account (admin) parameters: - { name: accountId, in: path, required: true, schema: { type: string } } responses: "204": { description: Deleted } default: { $ref: "#/components/responses/Error" } /clusters: get: operationId: listClusters summary: List clusters parameters: - { name: page, in: query, schema: { type: integer } } - { name: per_page, in: query, schema: { type: integer } } - { name: account_id, in: query, schema: { type: string } } responses: "200": description: Clusters content: application/json: schema: { $ref: "#/components/schemas/ClusterList" } default: { $ref: "#/components/responses/Error" } post: operationId: createCluster summary: Register a cluster (admin) requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateClusterRequest" } responses: "201": description: Created cluster content: application/json: schema: { $ref: "#/components/schemas/Cluster" } default: { $ref: "#/components/responses/Error" } /clusters/{clusterId}: get: operationId: getCluster summary: Get a cluster parameters: - { name: clusterId, in: path, required: true, schema: { type: string } } responses: "200": description: Cluster content: application/json: schema: { $ref: "#/components/schemas/Cluster" } default: { $ref: "#/components/responses/Error" } put: operationId: updateCluster summary: Update a cluster (admin) parameters: - { name: clusterId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UpdateClusterRequest" } responses: "200": description: Updated cluster content: application/json: schema: { $ref: "#/components/schemas/Cluster" } default: { $ref: "#/components/responses/Error" } delete: operationId: deleteCluster summary: Delete a cluster (admin) parameters: - { name: clusterId, in: path, required: true, schema: { type: string } } responses: "204": { description: Deleted } default: { $ref: "#/components/responses/Error" } /clusters/{clusterId}/test-connection: post: operationId: testClusterConnection summary: Re-run the async connection test (admin) parameters: - { name: clusterId, in: path, required: true, schema: { type: string } } responses: "202": description: Connection test enqueued content: application/json: schema: type: object properties: status: { type: string } required: [status] default: { $ref: "#/components/responses/Error" } /cluster-orders: get: operationId: listClusterOrders summary: Recent cluster operations across the org (admin) responses: "200": description: Cluster operations, newest first content: application/json: schema: { $ref: "#/components/schemas/ClusterOperationList" } default: { $ref: "#/components/responses/Error" } post: operationId: provisionCluster summary: Order a cluster vend (admin) description: > Templates an eks-fleet Cluster CR from the input and commits it to the clusters GitOps repo; the vended cluster auto-registers when it comes up. requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/ClusterOrderInput" } responses: "202": description: Provision enqueued content: application/json: schema: { $ref: "#/components/schemas/ClusterOperation" } default: { $ref: "#/components/responses/Error" } /cluster-orders/{environment}/{name}: delete: operationId: deprovisionCluster summary: Order a cluster teardown (admin) parameters: - { name: environment, in: path, required: true, schema: { type: string } } - { name: name, in: path, required: true, schema: { type: string } } - { name: team, in: query, required: true, schema: { type: string } } responses: "202": description: Deprovision enqueued content: application/json: schema: { $ref: "#/components/schemas/ClusterOperation" } default: { $ref: "#/components/responses/Error" } /cluster-orders/{environment}/{name}/operations: get: operationId: listClusterOrderOperations summary: Vend log for one cluster, newest first (admin) parameters: - { name: environment, in: path, required: true, schema: { type: string } } - { name: name, in: path, required: true, schema: { type: string } } responses: "200": description: Cluster operations content: application/json: schema: { $ref: "#/components/schemas/ClusterOperationList" } default: { $ref: "#/components/responses/Error" } /cluster-orders/{environment}/{name}/unwedge: post: operationId: unwedgeCluster summary: Break-glass teardown for a wedged spoke (owner) description: > Tears the spoke's tagged AWS resources down directly and drops the Workspace finalizers. No-op unless the Workspace is genuinely wedged and already marked for deletion. parameters: - { name: environment, in: path, required: true, schema: { type: string } } - { name: name, in: path, required: true, schema: { type: string } } - { name: team, in: query, required: true, schema: { type: string } } responses: "202": description: Unwedge enqueued content: application/json: schema: { $ref: "#/components/schemas/ClusterOperation" } default: { $ref: "#/components/responses/Error" } /ops/feed: get: operationId: getOpsFeed summary: Org-wide operations feed (admin) description: Cluster vends and tenant deploys merged into one activity stream, newest first. responses: "200": description: Feed items content: application/json: schema: { $ref: "#/components/schemas/OpsFeedItemList" } default: { $ref: "#/components/responses/Error" } /templates: get: operationId: listTemplates summary: List tenant templates responses: "200": description: Templates (team-filtered for non-admins) content: application/json: schema: { $ref: "#/components/schemas/TemplateList" } default: { $ref: "#/components/responses/Error" } post: operationId: createTemplate summary: Create a template (admin) requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateTemplateRequest" } responses: "201": description: Created template content: application/json: schema: { $ref: "#/components/schemas/Template" } default: { $ref: "#/components/responses/Error" } /templates/{templateID}: get: operationId: getTemplate summary: Get a template parameters: - { name: templateID, in: path, required: true, schema: { type: string } } responses: "200": description: Template content: application/json: schema: { $ref: "#/components/schemas/Template" } default: { $ref: "#/components/responses/Error" } put: operationId: updateTemplate summary: Update a template (admin) parameters: - { name: templateID, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UpdateTemplateRequest" } responses: "200": description: Updated template content: application/json: schema: { $ref: "#/components/schemas/Template" } default: { $ref: "#/components/responses/Error" } delete: operationId: deleteTemplate summary: Delete a template (admin) parameters: - { name: templateID, in: path, required: true, schema: { type: string } } responses: "204": { description: Deleted } default: { $ref: "#/components/responses/Error" } /templates/{templateID}/access: get: operationId: listTemplateAccess summary: List team-access grants on a template parameters: - { name: templateID, in: path, required: true, schema: { type: string } } responses: "200": description: Grants content: application/json: schema: { $ref: "#/components/schemas/TemplateTeamAccessList" } default: { $ref: "#/components/responses/Error" } post: operationId: grantTemplateAccess summary: Grant a team access to a template (admin) parameters: - { name: templateID, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object properties: team_id: { type: string } required: [team_id] responses: "201": description: Grant content: application/json: schema: { $ref: "#/components/schemas/TemplateTeamAccess" } default: { $ref: "#/components/responses/Error" } /templates/{templateID}/access/{teamId}: delete: operationId: revokeTemplateAccess summary: Revoke a team's template access (admin) parameters: - { name: templateID, in: path, required: true, schema: { type: string } } - { name: teamId, in: path, required: true, schema: { type: string } } responses: "204": { description: Revoked } default: { $ref: "#/components/responses/Error" } /tenants: get: operationId: listTenants summary: List tenants (watcher-observed inventory) parameters: - { name: page, in: query, schema: { type: integer } } - { name: per_page, in: query, schema: { type: integer } } - { name: cluster_id, in: query, schema: { type: string } } responses: "200": description: Tenants (team-filtered for non-admins) content: application/json: schema: { $ref: "#/components/schemas/TenantList" } default: { $ref: "#/components/responses/Error" } post: operationId: createTenant summary: Request a tenant deploy (operator) description: | Enqueues a tenant_operation of kind=create; the tenant row appears once ArgoCD applies the commit and the watcher observes the CR. `values` are overrides against the referenced template, which supplies the governance bounds: the budget ceiling, the model-family intersection, the datastore-kind narrowing and the required-compliance flags. Omitting `template_id` applies none of them and renders `values` as given, so it requires admin. An operator omitting it is refused. requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateTenantRequest" } responses: "202": description: Create enqueued content: application/json: schema: { $ref: "#/components/schemas/TenantOperation" } default: { $ref: "#/components/responses/Error" } /tenants/{tenantId}: get: operationId: getTenant summary: Get a tenant parameters: - { name: tenantId, in: path, required: true, schema: { type: string } } responses: "200": description: Tenant content: application/json: schema: { $ref: "#/components/schemas/Tenant" } default: { $ref: "#/components/responses/Error" } delete: operationId: deleteTenant summary: Request a tenant delete (operator) parameters: - { name: tenantId, in: path, required: true, schema: { type: string } } responses: "202": description: Delete enqueued content: application/json: schema: { $ref: "#/components/schemas/TenantOperation" } default: { $ref: "#/components/responses/Error" } /tenants/{tenantId}/operations: get: operationId: listTenantOperations summary: Per-tenant operation log parameters: - { name: tenantId, in: path, required: true, schema: { type: string } } responses: "200": description: Operations content: application/json: schema: { $ref: "#/components/schemas/TenantOperationList" } default: { $ref: "#/components/responses/Error" } /tenants/{tenantId}/access: get: operationId: listTenantAccess summary: List team-access grants on a tenant parameters: - { name: tenantId, in: path, required: true, schema: { type: string } } responses: "200": description: Grants content: application/json: schema: { $ref: "#/components/schemas/TenantTeamAccessList" } default: { $ref: "#/components/responses/Error" } post: operationId: grantTenantAccess summary: Grant a team access to a tenant (admin) parameters: - { name: tenantId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object properties: team_id: { type: string } required: [team_id] responses: "201": description: Grant content: application/json: schema: { $ref: "#/components/schemas/TenantTeamAccess" } default: { $ref: "#/components/responses/Error" } /tenants/{tenantId}/access/{teamId}: delete: operationId: revokeTenantAccess summary: Revoke a team's tenant access (admin) parameters: - { name: tenantId, in: path, required: true, schema: { type: string } } - { name: teamId, in: path, required: true, schema: { type: string } } responses: "204": { description: Revoked } default: { $ref: "#/components/responses/Error" } /pipelines: get: operationId: listPipelines summary: List pipelines responses: "200": description: Pipelines content: application/json: schema: { $ref: "#/components/schemas/PipelineList" } default: { $ref: "#/components/responses/Error" } post: operationId: createPipeline summary: Create a pipeline (operator; a stage auto_apply on a gated workspace needs admin) requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreatePipelineRequest" } responses: "201": description: Created pipeline content: application/json: schema: { $ref: "#/components/schemas/Pipeline" } default: { $ref: "#/components/responses/Error" } /pipelines/{pipelineId}: get: operationId: getPipeline summary: Get a pipeline with its stages parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } responses: "200": description: Pipeline detail content: application/json: schema: { $ref: "#/components/schemas/PipelineDetailResponse" } default: { $ref: "#/components/responses/Error" } put: operationId: updatePipeline summary: Update a pipeline (operator; a stage auto_apply on a gated workspace needs admin) parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UpdatePipelineRequest" } responses: "200": description: Updated pipeline content: application/json: schema: { $ref: "#/components/schemas/Pipeline" } default: { $ref: "#/components/responses/Error" } delete: operationId: deletePipeline summary: Delete a pipeline (admin) parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } responses: "204": { description: Deleted } default: { $ref: "#/components/responses/Error" } /pipelines/{pipelineId}/runs: get: operationId: listPipelineRuns summary: List pipeline runs parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } - { name: page, in: query, schema: { type: integer } } - { name: per_page, in: query, schema: { type: integer } } responses: "200": description: Pipeline runs content: application/json: schema: { $ref: "#/components/schemas/PipelineRunList" } default: { $ref: "#/components/responses/Error" } post: operationId: startPipelineRun summary: Start a pipeline run (operator) parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } responses: "201": description: Started run content: application/json: schema: { $ref: "#/components/schemas/PipelineRun" } default: { $ref: "#/components/responses/Error" } /pipelines/{pipelineId}/runs/{runId}: get: operationId: getPipelineRun summary: Get a pipeline run with its stage progress parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } - { name: runId, in: path, required: true, schema: { type: string } } responses: "200": description: Pipeline run detail content: application/json: schema: { $ref: "#/components/schemas/PipelineRunDetailResponse" } default: { $ref: "#/components/responses/Error" } /pipelines/{pipelineId}/runs/{runId}/cancel: post: operationId: cancelPipelineRun summary: Cancel a pipeline run (operator) parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } - { name: runId, in: path, required: true, schema: { type: string } } responses: "200": description: Cancelled run content: application/json: schema: { $ref: "#/components/schemas/PipelineRun" } default: { $ref: "#/components/responses/Error" } /pipelines/{pipelineId}/variables: get: operationId: listPipelineVariables summary: List pipeline variables parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } responses: "200": description: Pipeline variables (sensitive values redacted) content: application/json: schema: { $ref: "#/components/schemas/PipelineVariableList" } default: { $ref: "#/components/responses/Error" } post: operationId: createPipelineVariable summary: Create a pipeline variable (admin) parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateVariableRequest" } responses: "201": description: Created variable content: application/json: schema: { $ref: "#/components/schemas/PipelineVariable" } default: { $ref: "#/components/responses/Error" } /pipelines/{pipelineId}/variables/{variableId}: put: operationId: updatePipelineVariable summary: Update a pipeline variable (admin) parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } - { name: variableId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateVariableRequest" } responses: "200": description: Updated variable content: application/json: schema: { $ref: "#/components/schemas/PipelineVariable" } default: { $ref: "#/components/responses/Error" } delete: operationId: deletePipelineVariable summary: Delete a pipeline variable (admin) parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } - { name: variableId, in: path, required: true, schema: { type: string } } responses: "204": { description: Deleted } default: { $ref: "#/components/responses/Error" } /pipelines/{pipelineId}/variables/{variableId}/value: get: operationId: revealPipelineVariableValue summary: Reveal a pipeline variable's decrypted value (admin) parameters: - { name: pipelineId, in: path, required: true, schema: { type: string } } - { name: variableId, in: path, required: true, schema: { type: string } } responses: "200": description: Decrypted value content: application/json: schema: { $ref: "#/components/schemas/VariableValue" } default: { $ref: "#/components/responses/Error" } /workspaces: get: operationId: listWorkspaces summary: List workspaces parameters: - { name: page, in: query, schema: { type: integer } } - { name: per_page, in: query, schema: { type: integer } } - { name: search, in: query, schema: { type: string } } - { name: environment, in: query, schema: { type: string } } responses: "200": description: Workspaces with last-run rollups content: application/json: schema: { $ref: "#/components/schemas/WorkspaceList" } default: { $ref: "#/components/responses/Error" } post: operationId: createWorkspace summary: Create a workspace (operator; auto_apply needs admin) requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateWorkspaceRequest" } responses: "201": description: Created workspace content: application/json: schema: { $ref: "#/components/schemas/Workspace" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}: get: operationId: getWorkspace summary: Get a workspace parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "200": description: Workspace content: application/json: schema: { $ref: "#/components/schemas/Workspace" } "404": description: Not found content: application/json: schema: { $ref: "#/components/schemas/ErrorResponse" } default: { $ref: "#/components/responses/Error" } put: operationId: updateWorkspace summary: Update a workspace (operator; turning auto_apply on or requires_approval off needs admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UpdateWorkspaceRequest" } responses: "200": description: Updated workspace content: application/json: schema: { $ref: "#/components/schemas/Workspace" } default: { $ref: "#/components/responses/Error" } delete: operationId: deleteWorkspace summary: Delete a workspace (admin on the workspace; deleting the last workspace requiring approval on a repository + working directory needs org admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "204": { description: Deleted } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/upload: post: operationId: uploadWorkspaceConfig summary: Upload a configuration archive (operator) description: > Multipart upload (50 MB limit) for source=upload workspaces. Archives can be large, so the caller sets a wider deadline than the client's default on this one request. parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary required: [file] responses: "200": description: Workspace with the new config version content: application/json: schema: { $ref: "#/components/schemas/Workspace" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/clone: post: operationId: cloneWorkspace summary: Clone a workspace with its variables (operator; an auto-applying source needs admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CloneWorkspaceRequest" } responses: "201": description: Cloned workspace content: application/json: schema: { $ref: "#/components/schemas/Workspace" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/lock: post: operationId: lockWorkspace summary: Lock a workspace (operator) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "200": description: Locked workspace content: application/json: schema: { $ref: "#/components/schemas/Workspace" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/unlock: post: operationId: unlockWorkspace summary: Unlock a workspace (operator) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "200": description: Unlocked workspace content: application/json: schema: { $ref: "#/components/schemas/Workspace" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/variables: get: operationId: listWorkspaceVariables summary: List workspace variables parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "200": description: Variables (sensitive values redacted) content: application/json: schema: { $ref: "#/components/schemas/WorkspaceVariableList" } default: { $ref: "#/components/responses/Error" } post: operationId: createWorkspaceVariable summary: Create a workspace variable (admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateVariableRequest" } responses: "201": description: Created variable content: application/json: schema: { $ref: "#/components/schemas/WorkspaceVariable" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/variables/effective: get: operationId: listEffectiveVariables summary: Merged org → pipeline → workspace variable view parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: pipeline_id, in: query, schema: { type: string } } responses: "200": description: Effective variables content: application/json: schema: type: array items: { $ref: "#/components/schemas/EffectiveVariable" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/variables/discover: post: operationId: discoverWorkspaceVariables summary: Discover the config's variable surface (viewer) description: > Parses the staged config (terragrunt render + variables.tf, or plain tofu) and returns every module variable with its configured state. The `default` field carries the value the config resolves — a terragrunt input or a module default — so it is only populated for callers who clear the variable-management bar (admin, or an admin team grant on this workspace). Below that bar the response is names, types, descriptions and provenance with no values, and the terragrunt render that would resolve them is skipped. Synchronous; intentionally not list-enveloped. parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "200": description: Discovered variables content: application/json: schema: type: array items: { $ref: "#/components/schemas/DiscoveredVariable" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/variables/bulk: post: operationId: bulkCreateWorkspaceVariables summary: Create up to 50 variables in one call (admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object properties: variables: type: array items: { $ref: "#/components/schemas/CreateVariableRequest" } required: [variables] responses: "201": description: Created variables content: application/json: schema: type: array items: { $ref: "#/components/schemas/WorkspaceVariable" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/variables/import-outputs: post: operationId: importWorkspaceOutputs summary: Import another workspace's outputs as variables (admin on both workspaces) description: > Upserts each of the source workspace's state outputs as a terraform-category variable on this one. Outputs the source state marks sensitive are skipped: state redacts their values, so there is nothing to carry across. A source whose outputs are all sensitive answers 400 saying so. parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object properties: source_workspace_id: { type: string } required: [source_workspace_id] responses: "201": description: Imported variables content: application/json: schema: type: array items: { $ref: "#/components/schemas/WorkspaceVariable" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/variables/copy: post: operationId: copyWorkspaceVariables summary: Copy another workspace's variables into this one (admin on both workspaces) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object properties: source_workspace_id: { type: string } required: [source_workspace_id] responses: "201": description: Copied variables content: application/json: schema: type: array items: { $ref: "#/components/schemas/WorkspaceVariable" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/variables/{variableId}: put: operationId: updateWorkspaceVariable summary: Update a workspace variable (admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: variableId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateVariableRequest" } responses: "200": description: Updated variable content: application/json: schema: { $ref: "#/components/schemas/WorkspaceVariable" } default: { $ref: "#/components/responses/Error" } delete: operationId: deleteWorkspaceVariable summary: Delete a workspace variable (admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: variableId, in: path, required: true, schema: { type: string } } responses: "204": { description: Deleted } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/variables/{variableId}/value: get: operationId: revealWorkspaceVariableValue summary: Reveal a workspace variable's decrypted value (admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: variableId, in: path, required: true, schema: { type: string } } responses: "200": description: Decrypted value content: application/json: schema: { $ref: "#/components/schemas/VariableValue" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/state: get: operationId: listStateVersions summary: List state versions parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: page, in: query, schema: { type: integer } } - { name: per_page, in: query, schema: { type: integer } } responses: "200": description: State versions content: application/json: schema: { $ref: "#/components/schemas/StateVersionList" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/state/current: get: operationId: getCurrentStateVersion summary: The latest state version parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "200": description: Latest state version content: application/json: schema: { $ref: "#/components/schemas/StateVersion" } "404": description: No state yet content: application/json: schema: { $ref: "#/components/schemas/ErrorResponse" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/state/current/resources: get: operationId: listCurrentStateResources summary: Parsed resources from the latest state parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "200": description: Resources content: application/json: schema: type: array items: { $ref: "#/components/schemas/StateResource" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/state/current/outputs: get: operationId: listCurrentStateOutputs summary: Parsed outputs from the latest state parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "200": description: Outputs content: application/json: schema: type: array items: { $ref: "#/components/schemas/StateOutput" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/state/diff: get: operationId: diffStateVersions summary: Diff two state versions by serial parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: from, in: query, required: true, schema: { type: integer } } - { name: to, in: query, required: true, schema: { type: integer } } responses: "200": description: State diff content: application/json: schema: { $ref: "#/components/schemas/StateDiff" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/state/{stateId}: get: operationId: getStateVersion summary: Get a state version parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: stateId, in: path, required: true, schema: { type: string } } responses: "200": description: State version content: application/json: schema: { $ref: "#/components/schemas/StateVersion" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/state/{stateId}/download: get: operationId: downloadStateVersion summary: Download the raw tfstate blob (admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: stateId, in: path, required: true, schema: { type: string } } responses: "200": description: The tfstate document (attachment). content: application/json: schema: type: object additionalProperties: true default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/state/serial/{serial}: delete: operationId: deleteStateVersion summary: Drop one state version + its S3 objects (admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: serial, in: path, required: true, schema: { type: integer } } responses: "204": { description: Deleted (row + storage) } "200": description: Row deleted, storage cleanup failed (detail attached) content: application/json: schema: type: object properties: deleted: { $ref: "#/components/schemas/StateVersion" } storage_error: { type: string } required: [deleted, storage_error] default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/access: get: operationId: listWorkspaceAccess summary: List team-access grants on a workspace parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } responses: "200": description: Grants content: application/json: schema: { $ref: "#/components/schemas/WorkspaceTeamAccessList" } default: { $ref: "#/components/responses/Error" } post: operationId: setWorkspaceAccess summary: Grant/replace a team's workspace access (admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object properties: team_id: { type: string } role: { type: string } required: [team_id, role] responses: "201": description: Grant content: application/json: schema: { $ref: "#/components/schemas/WorkspaceTeamAccess" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/access/{teamId}: delete: operationId: removeWorkspaceAccess summary: Revoke a team's workspace access (admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: teamId, in: path, required: true, schema: { type: string } } responses: "204": { description: Revoked } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/runs: get: operationId: listRuns summary: List a workspace's runs parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: page, in: query, schema: { type: integer } } - { name: per_page, in: query, schema: { type: integer } } responses: "200": description: Runs content: application/json: schema: { $ref: "#/components/schemas/RunList" } default: { $ref: "#/components/responses/Error" } post: operationId: createRun summary: Create a run (role elevates per operation; apply/destroy on a workspace that requires approval needs admin) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CreateRunRequest" } responses: "201": description: Created run content: application/json: schema: { $ref: "#/components/schemas/Run" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/runs/{runId}: get: operationId: getRun summary: Get a run parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: runId, in: path, required: true, schema: { type: string } } responses: "200": description: Run content: application/json: schema: { $ref: "#/components/schemas/Run" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/runs/{runId}/plan-json: get: operationId: getRunPlanJSON summary: The run's machine-readable plan description: >- The resource changes the plan makes, with values the plan marks sensitive withheld from callers who may not manage this workspace's state. parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: runId, in: path, required: true, schema: { type: string } } responses: "200": description: The projected plan content: application/json: schema: { $ref: "#/components/schemas/TofuPlanJSON" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/runs/{runId}/logs/ws: get: operationId: streamRunLogs summary: Live run log stream (WebSocket upgrade) description: > Not a JSON operation: the connection upgrades to a WebSocket after the org-scope check. Clients authenticate by requesting the subprotocol pair ["bearer", ]; messages are text log lines. Documented here for completeness — the typed client does not call it. parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: runId, in: path, required: true, schema: { type: string } } responses: "101": { description: Switching to the WebSocket protocol. } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/runs/{runId}/cancel: post: operationId: cancelRun summary: Cancel a run (operator) parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: runId, in: path, required: true, schema: { type: string } } responses: "200": description: Cancelled run content: application/json: schema: { $ref: "#/components/schemas/Run" } default: { $ref: "#/components/responses/Error" } /workspaces/{workspaceId}/runs/{runId}/approvals: get: operationId: listApprovals summary: List a run's approvals parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: runId, in: path, required: true, schema: { type: string } } responses: "200": description: Approvals content: application/json: schema: { $ref: "#/components/schemas/ApprovalList" } default: { $ref: "#/components/responses/Error" } post: operationId: createApproval summary: Approve or reject a gated apply parameters: - { name: workspaceId, in: path, required: true, schema: { type: string } } - { name: runId, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/ApprovalRequest" } responses: "201": description: Recorded decision content: application/json: schema: { $ref: "#/components/schemas/Approval" } default: { $ref: "#/components/responses/Error" } components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT responses: Error: description: The standard error envelope (respond.Error / respond.FromError). content: application/json: schema: { $ref: "#/components/schemas/ErrorResponse" } schemas: ErrorResponse: type: object properties: error: { type: string, description: "HTTP status text." } message: { type: string } request_id: { type: string, description: "Set on 5xx responses so logs can be correlated." } required: [error] HealthStatus: type: object properties: status: { type: string } services: type: object additionalProperties: { type: string } dev_login: type: boolean description: Present (true) only in development, where the dev-login route exists. required: [status] VariableValue: type: object properties: value: { type: string } required: [value] User: type: object properties: id: { type: string } org_id: { type: string } email: { type: string } name: { type: string } avatar_url: { type: string } github_id: type: [integer, "null"] description: GitHub account id for OAuth-provisioned users; null for dev users. role: { type: string, enum: [owner, admin, operator, viewer] } last_login_at: { type: string, format: date-time } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, org_id, email, name, role, created_at, updated_at] UpdateRoleRequest: type: object properties: role: { type: string, enum: [owner, admin, operator, viewer] } required: [role] Workspace: type: object properties: id: { type: string } org_id: { type: string } name: { type: string } description: { type: string } source: { type: string, enum: [vcs, upload] } repo_url: { type: string } repo_branch: { type: string } working_dir: { type: string } tofu_version: { type: string } environment: { type: string, enum: [development, staging, production] } auto_apply: { type: boolean } requires_approval: { type: boolean } vcs_trigger_enabled: { type: boolean } locked: { type: boolean } locked_by: { type: [string, "null"] } current_run_id: { type: [string, "null"] } current_config_version_id: { type: string } created_by: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } last_run_status: { type: [string, "null"], description: "List-view rollup." } last_run_at: { type: [string, "null"], format: date-time, description: "List-view rollup." } resource_count: { type: integer, description: "List-view rollup." } effective_role: type: string enum: [viewer, operator, admin, owner] description: >- The caller's role on this workspace: their org role, or a higher role one of their teams holds through a workspace team grant. Single-workspace reads only. required: [id, org_id, name, source, repo_url, repo_branch, working_dir, tofu_version, environment, auto_apply, requires_approval, vcs_trigger_enabled, locked, created_by, created_at, updated_at] CreateWorkspaceRequest: type: object properties: name: { type: string } description: { type: string } source: { type: string, enum: [vcs, upload] } repo_url: { type: string } repo_branch: { type: string } working_dir: type: string description: >- Repo-relative path to the leaf portal runs in. Stored canonical: "./envs/production", "envs//production", "envs/./production" and "envs/production/" all name one directory to the executor, so they are all stored as "envs/production". Omit on update to keep the stored value. tofu_version: { type: string } environment: { type: string, enum: [development, staging, production] } auto_apply: { type: boolean } requires_approval: { type: boolean } vcs_trigger_enabled: { type: boolean } required: [name] UpdateWorkspaceRequest: type: object properties: name: { type: string } description: { type: string } repo_url: { type: string } repo_branch: { type: string } working_dir: type: string description: >- Repo-relative path to the leaf portal runs in. Stored canonical: "./envs/production", "envs//production", "envs/./production" and "envs/production/" all name one directory to the executor, so they are all stored as "envs/production". Omit on update to keep the stored value. tofu_version: { type: string } environment: { type: string, enum: [development, staging, production] } auto_apply: { type: boolean } requires_approval: { type: boolean } vcs_trigger_enabled: { type: boolean } CloneWorkspaceRequest: type: object properties: name: { type: string } description: { type: string } environment: { type: string } requires_approval: type: boolean description: >- Approval gate for the clone. Omit to inherit the source's. Raising it is an operator action — it is how a clone of a config another workspace already gates gets created without an admin. Clearing a gate the source holds needs admin. required: [name] RunStatus: type: string enum: - pending - queued - planning - planned - awaiting_approval - applying - applied - errored - cancelled - discarded RunOperation: type: string enum: [plan, apply, destroy, import, test] Run: type: object properties: id: { type: string } workspace_id: { type: string } org_id: { type: string } operation: { $ref: "#/components/schemas/RunOperation" } status: { $ref: "#/components/schemas/RunStatus" } plan_output: { type: string } plan_log_url: { type: string } apply_log_url: { type: string } resources_added: { type: integer } resources_changed: { type: integer } resources_deleted: { type: integer } error_message: { type: string } commit_sha: { type: string } plan_json_url: { type: string } created_by: { type: string } started_at: { type: [string, "null"], format: date-time } finished_at: { type: [string, "null"], format: date-time } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, workspace_id, org_id, operation, status, created_by, created_at, updated_at] CreateRunRequest: type: object properties: operation: { $ref: "#/components/schemas/RunOperation" } imports: type: array items: type: object properties: address: { type: string } id: { type: string } required: [address, id] TofuResourceChange: type: object properties: address: { type: string } module_address: { type: string } mode: { type: string } type: { type: string } name: { type: string } provider_name: { type: string } change: type: object properties: actions: type: array items: { type: string } before: type: [object, "null"] additionalProperties: true description: >- The attributes that differ, at their prior values — not the resource's whole attribute map, and inside an attribute whose value is a map, not the members that stayed put. Every key present is one that changed, so a caller reads the keys rather than comparing the two maps: a withheld value reads the same on both sides. A leaf reading "(sensitive value)" was withheld — the string matches what `tofu show` prints and is not reserved, so an attribute whose real value is that string is indistinguishable. Null means the resource did not exist. after: type: [object, "null"] additionalProperties: true description: >- The attributes that differ, at their planned values. Null means the resource is being destroyed. required: [actions, before, after] required: [address, mode, type, name, provider_name, change] TofuPlanJSON: type: object description: >- The run's plan, projected. Not the stored `tofu show -json` artifact: that document carries the root variable values, a complete prior-state representation, the configuration's literals, and the cleartext of every value the rendered plan prints as "(sensitive value)". This carries the resource changes and nothing else, so a section a later tofu format adds cannot begin disclosing on its own. properties: format_version: { type: string } resource_changes: type: array description: >- One entry per resource the plan is acting on. Resources it is not touching are omitted. items: { $ref: "#/components/schemas/TofuResourceChange" } sensitive_values_redacted: type: boolean description: >- Values the plan marks sensitive were withheld and read "(sensitive value)". Unmarked values ARE present in cleartext — this names a subset, unlike attributes_redacted on the state surface, where every value is gone. Absent for callers who may manage this workspace's state, which is the bar the raw state download sits at — a plan's before and after are state attributes under another name. required: [format_version, resource_changes] StateVersion: type: object properties: id: { type: string } workspace_id: { type: string } org_id: { type: string } run_id: { type: string } serial: { type: integer } state_url: { type: string } resource_count: { type: integer } resource_summary: { type: string } created_at: { type: string, format: date-time } required: [id, workspace_id, org_id, run_id, serial, state_url, resource_count, resource_summary, created_at] StateResource: type: object properties: type: { type: string } name: { type: string } module: { type: string } provider: { type: string } mode: { type: string } attributes: type: object additionalProperties: true description: >- Attribute names the instance carries. Values are present only for callers who may manage this workspace's state; below that bar every value is null and attributes_redacted is true, because tofu writes provider secrets into state attributes in cleartext. attributes_redacted: type: boolean description: Attribute values were withheld; the names are complete. required: [type, name, module, provider, mode, attributes] StateOutput: type: object properties: name: { type: string } value: {} type: { type: string } sensitive: { type: boolean } required: [name, value, type, sensitive] ResourceDiff: type: object properties: type: { type: string } name: { type: string } module: { type: string } action: { type: string, enum: [added, removed, changed, unchanged] } before: type: object additionalProperties: true after: type: object additionalProperties: true changed_keys: type: array items: { type: string } attributes_redacted: type: boolean description: >- before/after values were withheld; the counts and changed_keys are the same ones a state manager sees. required: [type, name, module, action] StateDiff: type: object properties: added: { type: integer } removed: { type: integer } changed: { type: integer } unchanged: { type: integer } diffs: type: array items: { $ref: "#/components/schemas/ResourceDiff" } required: [added, removed, changed, unchanged, diffs] VariableCategory: type: string enum: [terraform, env] WorkspaceVariable: type: object properties: id: { type: string } workspace_id: { type: string } org_id: { type: string } key: { type: string } value: { type: string } sensitive: { type: boolean } category: { $ref: "#/components/schemas/VariableCategory" } description: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, workspace_id, org_id, key, value, sensitive, category, description, created_at, updated_at] OrgVariable: type: object properties: id: { type: string } org_id: { type: string } key: { type: string } value: { type: string } sensitive: { type: boolean } category: { $ref: "#/components/schemas/VariableCategory" } description: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, org_id, key, value, sensitive, category, description, created_at, updated_at] PipelineVariable: type: object properties: id: { type: string } pipeline_id: { type: string } org_id: { type: string } key: { type: string } value: { type: string } sensitive: { type: boolean } category: { $ref: "#/components/schemas/VariableCategory" } description: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, pipeline_id, org_id, key, value, sensitive, category, description, created_at, updated_at] CreateVariableRequest: type: object properties: key: { type: string } value: { type: string } sensitive: { type: boolean } category: { $ref: "#/components/schemas/VariableCategory" } description: { type: string } required: [key, value, sensitive, category] EffectiveVariable: type: object properties: key: { type: string } value: { type: string } sensitive: { type: boolean } category: { $ref: "#/components/schemas/VariableCategory" } description: { type: string } source: { type: string, enum: [org, pipeline, workspace] } source_id: { type: string } required: [key, value, sensitive, category, description, source, source_id] DiscoveredVariable: type: object properties: name: { type: string } type: { type: string } description: { type: string } default: { type: string } required: { type: boolean } configured: { type: boolean } configured_by: { type: string, enum: [terragrunt, portal] } required: [name, required, configured] Team: type: object properties: id: { type: string } org_id: { type: string } name: { type: string } slug: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, org_id, name, slug, created_at, updated_at] TeamMember: type: object properties: id: { type: string } team_id: { type: string } user_id: { type: string } role: { type: string } cloud_identity: { type: string } created_at: { type: string, format: date-time } email: { type: string } user_name: { type: string } avatar_url: { type: string } required: [id, team_id, user_id, role, cloud_identity, created_at, email, user_name, avatar_url] WorkspaceTeamAccess: type: object properties: id: { type: string } workspace_id: { type: string } team_id: { type: string } role: { type: string } created_at: { type: string, format: date-time } team_name: { type: string } team_slug: { type: string } required: [id, workspace_id, team_id, role, created_at, team_name, team_slug] Approval: type: object properties: id: { type: string } run_id: { type: string } org_id: { type: string } user_id: { type: string } status: { type: string, enum: [approved, rejected] } comment: { type: string } created_at: { type: string, format: date-time } user_name: { type: string } avatar_url: { type: string } required: [id, run_id, org_id, user_id, status, comment, created_at] ApprovalRequest: type: object properties: status: { type: string, enum: [approved, rejected] } comment: { type: string } required: [status] AuditLog: type: object properties: id: { type: string } org_id: { type: string } user_id: { type: string } action: { type: string } entity_type: { type: string } entity_id: { type: string } before_data: {} after_data: {} ip_address: { type: string } user_agent: { type: string } created_at: { type: string, format: date-time } required: [id, org_id, user_id, action, entity_type, entity_id, before_data, after_data, ip_address, user_agent, created_at] Pipeline: type: object properties: id: { type: string } org_id: { type: string } name: { type: string } description: { type: string } created_by: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, org_id, name, description, created_by, created_at, updated_at] OnFailure: type: string enum: [stop, continue] PipelineStage: type: object properties: id: { type: string } pipeline_id: { type: string } workspace_id: { type: string } stage_order: { type: integer } auto_apply: { type: boolean } on_failure: { $ref: "#/components/schemas/OnFailure" } created_at: { type: string, format: date-time } workspace_name: { type: string } required: [id, pipeline_id, workspace_id, stage_order, auto_apply, on_failure, created_at, workspace_name] PipelineRunStatus: type: string enum: [idle, running, completed, errored, cancelled] PipelineRun: type: object properties: id: { type: string } pipeline_id: { type: string } org_id: { type: string } status: { $ref: "#/components/schemas/PipelineRunStatus" } current_stage: { type: integer } total_stages: { type: integer } created_by: { type: string } started_at: { type: string, format: date-time } finished_at: { type: [string, "null"], format: date-time } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, pipeline_id, org_id, status, current_stage, total_stages, created_by, started_at, created_at, updated_at] PipelineStageStatus: type: string enum: - pending - importing_outputs - running - awaiting_approval - completed - errored - skipped - cancelled PipelineRunStage: type: object properties: id: { type: string } pipeline_run_id: { type: string } stage_id: { type: string } workspace_id: { type: string } run_id: { type: [string, "null"] } stage_order: { type: integer } status: { $ref: "#/components/schemas/PipelineStageStatus" } auto_apply: { type: boolean } on_failure: { $ref: "#/components/schemas/OnFailure" } started_at: { type: [string, "null"], format: date-time } finished_at: { type: [string, "null"], format: date-time } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } workspace_name: { type: string } required: [id, pipeline_run_id, stage_id, workspace_id, stage_order, status, auto_apply, on_failure, created_at, updated_at, workspace_name] CreatePipelineStageInput: type: object properties: workspace_id: { type: string } auto_apply: { type: boolean } on_failure: { $ref: "#/components/schemas/OnFailure" } required: [workspace_id, auto_apply, on_failure] CreatePipelineRequest: type: object properties: name: { type: string } description: { type: string } stages: type: array items: { $ref: "#/components/schemas/CreatePipelineStageInput" } required: [name, stages] UpdatePipelineRequest: type: object properties: name: { type: string } description: { type: string } stages: type: array items: { $ref: "#/components/schemas/CreatePipelineStageInput" } PipelineDetailResponse: type: object properties: pipeline: { $ref: "#/components/schemas/Pipeline" } stages: type: array items: { $ref: "#/components/schemas/PipelineStage" } required: [pipeline, stages] PipelineRunDetailResponse: type: object properties: pipeline_run: { $ref: "#/components/schemas/PipelineRun" } stages: type: array items: { $ref: "#/components/schemas/PipelineRunStage" } required: [pipeline_run, stages] Account: type: object properties: id: { type: string } org_id: { type: string } name: { type: string } description: { type: string } aws_account_id: { type: string } assume_role_arn: { type: string } default_region: { type: string } created_by: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } external_id_set: type: boolean description: Whether an assume-role external id is configured; the value itself never leaves the server. vend_role_arn: type: string description: >- The fleet-vend role a cross-account vend into this account assumes. Published to SSM by landing-zone; the vend does not create it. Empty means ungated (same-account vending). Requires both permissions boundaries — an order carrying the role without them is refused. data_kms_key_arn: type: string description: >- Customer-managed KMS key for this account's data at rest. Published to SSM by landing-zone. Empty means the XRD default. cluster_permissions_boundary_arn: type: string description: >- IAM permissions boundary capping the roles a cross-account vend mints for the cluster. Published to SSM at /eks-fleet//fleet-vend/vend_permissions_boundary_arn. operator_permissions_boundary_arn: type: string description: >- Permissions boundary capping the roles the agent-platform operator mints on this account. Same requirement and SSM source as cluster_permissions_boundary_arn. required: [id, org_id, name, description, aws_account_id, assume_role_arn, default_region, created_by, created_at, updated_at, external_id_set, vend_role_arn, data_kms_key_arn, cluster_permissions_boundary_arn, operator_permissions_boundary_arn] CreateAccountRequest: type: object properties: name: { type: string } description: { type: string } aws_account_id: { type: string } assume_role_arn: { type: string } external_id: { type: string } default_region: { type: string } vend_role_arn: type: string description: >- The fleet-vend role a cross-account vend into this account assumes. Published to SSM by landing-zone; the vend does not create it. Empty means ungated (same-account vending). Requires both permissions boundaries — an order carrying the role without them is refused. data_kms_key_arn: type: string description: >- Customer-managed KMS key for this account's data at rest. Published to SSM by landing-zone. Empty means the XRD default. cluster_permissions_boundary_arn: type: string description: >- IAM permissions boundary capping the roles a cross-account vend mints for the cluster. Published to SSM at /eks-fleet//fleet-vend/vend_permissions_boundary_arn. operator_permissions_boundary_arn: type: string description: >- Permissions boundary capping the roles the agent-platform operator mints on this account. Same requirement and SSM source as cluster_permissions_boundary_arn. required: [name, aws_account_id, assume_role_arn, default_region] UpdateAccountRequest: type: object properties: name: { type: string } description: { type: string } assume_role_arn: { type: string } external_id: { type: string } default_region: { type: string } vend_role_arn: type: string description: >- The fleet-vend role a cross-account vend into this account assumes. Published to SSM by landing-zone. Omit the key to leave the stored value alone; send an empty string to clear it. Requires both permissions boundaries whenever it is set. data_kms_key_arn: type: string description: >- Customer-managed KMS key for this account's data at rest. Published to SSM by landing-zone. Empty means the XRD default. cluster_permissions_boundary_arn: type: string description: >- IAM permissions boundary capping the roles a cross-account vend mints for the cluster. Published to SSM at /eks-fleet//fleet-vend/vend_permissions_boundary_arn. operator_permissions_boundary_arn: type: string description: >- Permissions boundary capping the roles the agent-platform operator mints on this account. Same requirement and SSM source as cluster_permissions_boundary_arn. ClusterConnectionStatus: type: string enum: [pending, connecting, connected, failed] Cluster: type: object properties: id: { type: string } org_id: { type: string } account_id: { type: string } name: { type: string } description: { type: string } environment: { type: string, enum: [development, staging, production] } api_endpoint: { type: string } region: { type: string } auth_mode: type: string description: sa_token (stored bearer token) or eks_iam (minted from the account role). eks_cluster_name: { type: string } connection_status: { $ref: "#/components/schemas/ClusterConnectionStatus" } last_connected_at: { type: [string, "null"], format: date-time } connection_error: { type: string } node_count: { type: integer } k8s_version: { type: string } argocd_sync_status: type: string description: Synced | OutOfSync | Unknown | "" (not yet observed). argocd_health_status: type: string description: Healthy | Progressing | Degraded | Missing | ... | "" (not yet observed). control_plane_status: type: string description: ACTIVE | UPDATING | CREATING | DEGRADED | ... | "" (not yet observed). platform_version: type: string description: EKS platform version, e.g. eks.7. last_health_observed_at: { type: [string, "null"], format: date-time } credentials_set: type: boolean description: Whether CA bundle + SA token are stored; the ciphertext never leaves the server. created_by: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, org_id, account_id, name, description, environment, api_endpoint, region, connection_status, last_connected_at, connection_error, node_count, k8s_version, argocd_sync_status, argocd_health_status, control_plane_status, platform_version, last_health_observed_at, credentials_set, created_by, created_at, updated_at] CreateClusterRequest: type: object properties: account_id: { type: string } name: { type: string } description: { type: string } environment: { type: string, enum: [development, staging, production] } api_endpoint: { type: string } ca_bundle: { type: string } sa_token: { type: string } region: { type: string } auth_mode: type: string enum: [sa_token, eks_iam] description: Defaults to sa_token. eks_iam requires eks_cluster_name and an account assume-role. eks_cluster_name: { type: string } required: [account_id, name, api_endpoint, ca_bundle, sa_token] UpdateClusterRequest: type: object properties: # name and environment round-trip so a read-modify-write PUT can send the # whole object back, but they are fixed once the cluster exists: together # they address its manifest in the clusters repo, its ArgoCD Application # and its tenant directory, none of which an edit to this row moves. A # value that differs from the stored one is a 409. name: { type: string, description: "Immutable. Sending a different value is a conflict." } description: { type: string } environment: { type: string, description: "Immutable. Sending a different value is a conflict." } api_endpoint: { type: string } ca_bundle: { type: string } sa_token: { type: string } region: { type: string } ClusterOrderInput: type: object description: > The vend form: portal templates an eks-fleet Cluster CR from it and commits it to the clusters GitOps repo. account is the 12-digit AWS account number (not a portal account id); the eks-fleet defaults cover everything optional. properties: name: { type: string } account: { type: string } region: { type: string } team: { type: string } environment: { type: string, enum: [development, staging, production] } cluster_version: { type: string } system_nodes: { $ref: "#/components/schemas/ClusterOrderSystemNodes" } network: { $ref: "#/components/schemas/ClusterOrderNetwork" } endpoint_public_access: type: boolean description: > Clusters vend with a private API endpoint (the eks-fleet default). Setting this true requires a non-empty endpoint_public_access_cidrs allowlist — orders that opt into public access without one are rejected with 400 (mirroring the Cluster XRD's CEL rule). endpoint_public_access_cidrs: type: array items: { type: string } description: > CIDR allowlist scoping who can reach the public endpoint (e.g. 203.0.113.0/24). Required (non-empty) when endpoint_public_access is true; each entry must parse as a CIDR. observability_tier: type: string enum: [floor, full] description: > floor (the default) is CloudWatch alone; full adds the in-cluster LGTM stack and Amazon Managed Prometheus/Grafana, and requires the managed-monitoring substrate to exist for this cluster. Tenant workloads are identical either way — only the destinations differ. ttl_days: type: integer minimum: 0 description: > Days-to-live for an ephemeral cluster. 0 (the default) is persistent; above 0 the hub reaper tears the cluster down that many days after creation. vend_role_arn: type: string description: > The fleet-vend role for a cross-account vend. Setting it requires both permissions-boundary ARNs below — the vend mints IAM in the workload account, and the Cluster XRD refuses one that does not cap what it can mint. cluster_permissions_boundary_arn: type: string description: > Boundary the cluster's IAM roles are minted with. Published to SSM by landing-zone at /eks-fleet//fleet-vend/vend_permissions_boundary_arn. operator_permissions_boundary_arn: type: string description: > Boundary the agent-platform operator role is minted with. Same value and SSM source as cluster_permissions_boundary_arn. data_kms_key_arn: type: string description: > The spoke's data CMK, used to encrypt the model-artifacts and eval-reports buckets at rest. A prerequisite referenced by ARN — the vend does not create it. required: [name, account, region, team] ClusterOrderSystemNodes: type: object description: > Sizing for the system node group, which hosts the cluster addons. Omitted fields take the eks-fleet defaults (m7g.xlarge + m6g.xlarge, min 2, max 6, desired 2, 100 GiB), and the sizes are checked as the group EKS is actually asked for: min_size 9 on its own is min 9 against the default max of 6, and is rejected with 400 rather than by the autoscaling API partway through the vend. properties: instance_types: type: array items: { type: string } min_size: type: integer minimum: 0 description: May be 0 — a group scaled to zero is a legitimate ask. max_size: type: integer minimum: 1 description: > Must be at least 1 (EKS refuses a managed node group that cannot run a node) and at least min_size. desired_size: type: integer minimum: 0 description: Must fall within [min_size, max_size]. disk_size: type: integer minimum: 1 description: Root volume size in GiB. ClusterOrderNetwork: type: object description: > The VPC the cluster lands in, discriminated on mode. Populate only the sub-object matching the mode — sending the other side is rejected rather than ignored, because an accepted-and-dropped adopt block reads as "the order took effect" while the stack builds a fresh VPC. properties: mode: type: string enum: [create, adopt] description: > create (the default) provisions a VPC the stack owns and disposes of with the cluster. adopt participates in a VPC provisioned elsewhere — a same-account shared VPC, or one shared cross-account over AWS RAM — which the owner runs and tags. create: { $ref: "#/components/schemas/ClusterOrderNetworkCreate" } adopt: { $ref: "#/components/schemas/ClusterOrderNetworkAdopt" } ClusterOrderNetworkCreate: type: object description: Create-mode levers. Used when mode is create. properties: vpc_cidr: type: string description: > Literal CIDR for the VPC (default 10.0.0.0/16). Mutually exclusive with ipam_pool_id. ipam_pool_id: type: string description: > IPAM pool the CIDR is drawn from instead of vpc_cidr. Cross-account this is the org IPAM environment sub-pool shared in over RAM. ipam_netmask_length: type: integer description: > Netmask length to allocate from ipam_pool_id (16-20). Subnets are carved 8 bits smaller than the VPC block, so a base longer than /20 would put them below the /28 AWS minimum. 0 means literal allocation. transit_gateway_id: type: string description: > Transit gateway the VPC attaches to. Requires an IPAM-allocated CIDR so attached prefixes stay non-overlapping. centralized_egress: type: boolean description: > Route private egress through the transit gateway instead of a local NAT gateway (zero NAT gateways). Requires transit_gateway_id. max_azs: { type: integer } nat_gateways: { type: integer } ClusterOrderNetworkAdopt: type: object description: > Adopt-mode references. Used when mode is adopt; the owner of the VPC runs it and its subnet tagging. properties: vpc_id: type: string description: VPC to adopt. Required when mode is adopt. subnet_ids: type: object properties: private: type: array items: { type: string } description: > Private subnets. Required and non-empty when mode is adopt — nodes land there. public: type: array items: { type: string } ClusterOperationKind: type: string enum: [provision, deprovision, unwedge] ClusterOperationStatus: type: string enum: [pending, committed, failed, active, deprovisioned] VendPhaseEntry: type: object properties: at: { type: string, format: date-time } detail: { type: string } required: [at] ClusterOperation: type: object properties: id: { type: string } org_id: { type: string } name: { type: string } environment: { type: string } team: { type: string } operation: { $ref: "#/components/schemas/ClusterOperationKind" } status: { $ref: "#/components/schemas/ClusterOperationStatus" } git_commit_sha: { type: string } error: { type: string } spec_json: type: object additionalProperties: true description: The clusterspec input the CR was templated from. cluster_id: { type: [string, "null"] } created_by: { type: string } created_at: { type: string, format: date-time } completed_at: { type: [string, "null"], format: date-time } vend_phases: type: object additionalProperties: { $ref: "#/components/schemas/VendPhaseEntry" } description: > Regressible map keyed by phase (committed, tofu_running, active, ...). Only portal-side phases advance locally; substrate phases need the in-cluster watcher. required: [id, org_id, name, environment, team, operation, status, git_commit_sha, error, spec_json, cluster_id, created_by, created_at, completed_at, vend_phases] Tenant: type: object properties: id: { type: string } org_id: { type: string } cluster_id: { type: string } name: { type: string } phase: { type: string } spec: type: object additionalProperties: true status: type: object additionalProperties: true last_observed_at: { type: string, format: date-time } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, org_id, cluster_id, name, phase, spec, status, last_observed_at, created_at, updated_at] CreateTenantRequest: type: object properties: cluster_id: { type: string } name: { type: string } values: type: object additionalProperties: true template_id: type: string description: When set, values are operator overrides merged with the template's defaults. owning_team_id: { type: string } required: [cluster_id, name, values] TenantOperationKind: type: string enum: [create, delete] TenantOperationStatus: type: string enum: [pending, committed, failed] TenantOperation: type: object properties: id: { type: string } org_id: { type: string } cluster_id: { type: string } tenant_name: { type: string } operation: { $ref: "#/components/schemas/TenantOperationKind" } status: { $ref: "#/components/schemas/TenantOperationStatus" } git_commit_sha: { type: string } error: { type: string } values_json: type: object additionalProperties: true template_id: { type: [string, "null"] } created_by: { type: string } created_at: { type: string, format: date-time } completed_at: { type: [string, "null"], format: date-time } required: [id, org_id, cluster_id, tenant_name, operation, status, git_commit_sha, error, values_json, template_id, created_by, created_at, completed_at] TenantTeamAccess: type: object properties: id: { type: string } org_id: { type: string } cluster_id: { type: string } tenant_name: { type: string } team_id: { type: string } granted_by: { type: string } granted_at: { type: string, format: date-time } required: [id, org_id, cluster_id, tenant_name, team_id, granted_by, granted_at] TemplateTeamAccess: type: object properties: id: { type: string } org_id: { type: string } template_id: { type: string } team_id: { type: string } granted_by: { type: string } granted_at: { type: string, format: date-time } required: [id, org_id, template_id, team_id, granted_by, granted_at] Template: type: object properties: id: { type: string } org_id: { type: string } name: { type: string } description: { type: string } persona: { type: string } default_values: type: object additionalProperties: true allowed_overrides: type: array items: { type: string } max_budget_usd: { type: integer } allowed_model_families: description: > Model families a tenant from this template may declare. Empty places no restriction. The values are the Platform CRD's own enum; a template holding anything else renders a manifest the apiserver refuses. type: array items: { $ref: "#/components/schemas/ModelFamily" } allowed_datastore_kinds: description: > Datastore kinds an operator may declare on a tenant from this template. Empty places no restriction. The override allowlist cannot express this on its own: `datastores` is one dotted path carrying the whole list, so allowing it allows every kind. type: array items: type: string enum: [relational, keyValue, objectStore, queue, cache, stream] required_compliance: type: array items: { type: string } created_by: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, org_id, name, description, persona, default_values, allowed_overrides, max_budget_usd, allowed_model_families, allowed_datastore_kinds, required_compliance, created_by, created_at, updated_at] ModelFamily: description: > A model family the Platform CRD admits. Mirrors Platform.spec.identity.allowedModelFamilies; internal/tenantmanifest asserts this list against the vendored schema. type: string enum: [anthropic, amazon-nova, amazon-titan, meta, mistral, cohere, stability] CreateTemplateRequest: type: object properties: name: { type: string } description: { type: string } persona: { type: string } default_values: type: object additionalProperties: true allowed_overrides: type: array items: { type: string } max_budget_usd: { type: integer } allowed_model_families: description: > Model families a tenant from this template may declare. Empty places no restriction. The values are the Platform CRD's own enum; a template holding anything else renders a manifest the apiserver refuses. type: array items: { $ref: "#/components/schemas/ModelFamily" } allowed_datastore_kinds: type: array items: type: string enum: [relational, keyValue, objectStore, queue, cache, stream] required_compliance: type: array items: { type: string } required: [name, persona] UpdateTemplateRequest: type: object properties: name: { type: string } description: { type: string } persona: { type: string } default_values: type: object additionalProperties: true allowed_overrides: type: array items: { type: string } max_budget_usd: { type: integer } allowed_model_families: description: > Model families a tenant from this template may declare. Empty places no restriction. The values are the Platform CRD's own enum; a template holding anything else renders a manifest the apiserver refuses. type: array items: { $ref: "#/components/schemas/ModelFamily" } allowed_datastore_kinds: type: array items: type: string enum: [relational, keyValue, objectStore, queue, cache, stream] required_compliance: type: array items: { type: string } OpsFeedItem: type: object description: > One entry in the org-wide operations feed: exactly one of cluster/tenant is set, discriminated by kind. `at` is the activity time the feed is sorted on (completion if finished, else creation). properties: kind: { type: string, enum: [cluster, tenant] } at: { type: string, format: date-time } cluster: { $ref: "#/components/schemas/ClusterOperation" } tenant: { $ref: "#/components/schemas/TenantOperation" } required: [kind, at] UserList: type: object properties: data: type: array items: { $ref: "#/components/schemas/User" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] AuditLogList: type: object properties: data: type: array items: { $ref: "#/components/schemas/AuditLog" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] OrgVariableList: type: object properties: data: type: array items: { $ref: "#/components/schemas/OrgVariable" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] PipelineVariableList: type: object properties: data: type: array items: { $ref: "#/components/schemas/PipelineVariable" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] WorkspaceVariableList: type: object properties: data: type: array items: { $ref: "#/components/schemas/WorkspaceVariable" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] TeamList: type: object properties: data: type: array items: { $ref: "#/components/schemas/Team" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] TeamMemberList: type: object properties: data: type: array items: { $ref: "#/components/schemas/TeamMember" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] WorkspaceTeamAccessList: type: object properties: data: type: array items: { $ref: "#/components/schemas/WorkspaceTeamAccess" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] AccountList: type: object properties: data: type: array items: { $ref: "#/components/schemas/Account" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] ClusterList: type: object properties: data: type: array items: { $ref: "#/components/schemas/Cluster" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] ClusterOperationList: type: object properties: data: type: array items: { $ref: "#/components/schemas/ClusterOperation" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] OpsFeedItemList: type: object properties: data: type: array items: { $ref: "#/components/schemas/OpsFeedItem" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] TemplateList: type: object properties: data: type: array items: { $ref: "#/components/schemas/Template" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] TemplateTeamAccessList: type: object properties: data: type: array items: { $ref: "#/components/schemas/TemplateTeamAccess" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] TenantList: type: object properties: data: type: array items: { $ref: "#/components/schemas/Tenant" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] TenantOperationList: type: object properties: data: type: array items: { $ref: "#/components/schemas/TenantOperation" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] TenantTeamAccessList: type: object properties: data: type: array items: { $ref: "#/components/schemas/TenantTeamAccess" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] PipelineList: type: object properties: data: type: array items: { $ref: "#/components/schemas/Pipeline" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] PipelineRunList: type: object properties: data: type: array items: { $ref: "#/components/schemas/PipelineRun" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] WorkspaceList: type: object properties: data: type: array items: { $ref: "#/components/schemas/Workspace" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] RunList: type: object properties: data: type: array items: { $ref: "#/components/schemas/Run" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] StateVersionList: type: object properties: data: type: array items: { $ref: "#/components/schemas/StateVersion" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] ApprovalList: type: object properties: data: type: array items: { $ref: "#/components/schemas/Approval" } total: { type: integer } page: { type: integer } per_page: { type: integer } required: [data, total, page, per_page] security: - bearerAuth: []