{"openapi":"3.1.0","info":{"title":"OutDo Data API","description":"The public REST API for the OutDo platform: authentication, the workspace model (workspaces, modules, rails, features, columns, views), row data, aggregates, and files.\n\n### Authentication\nSign in with `POST /v1/auth/token` to obtain a bearer token, then click **Authorize** and paste the `access_token`. Every request runs with the signed-in user's own permissions — the API enforces the platform's row-level security and never widens access.\n\nMachines authenticate with an organization API key (`outdo_sk_…`) in the same `Authorization: Bearer` header. A key belongs to one organization and carries scopes: `read` for safe methods, `write` for row writes, and `structure` for changing the model itself (features, columns, views, automations). `structure` is additive — it is only granted together with `write`.\n\n### Conventions\n- **Errors** follow RFC 7807 (`application/problem+json`); `422` responses carry an actionable `detail`. Every error also carries a stable `code` (e.g. `rate_limited`, `quota_exceeded`, `no_seats_available`); branch on `code`, not on the English `detail` text.\n- **Pagination** is cursor-based: pass `next_cursor` from the previous page back as `cursor`.\n- **Idempotency**: send an `Idempotency-Key` header on any POST to make retries safe. `POST /v1/auth/*` and `POST /v1/api-keys` are excluded: their responses carry credential material, which is never cached; retrying key creation mints a new key.\n- **Rate limits** apply per route and per client (and per API key when one is used); `429` responses carry a `Retry-After` header.\n- **Versioning**: this surface is `/v1`; breaking changes ship under a new prefix.\n\nMachine-readable structure: `GET /v1/schema` (the caller's visible model) and `GET /v1/schema/definitions` (what each entity's config accepts).\n\n### Documentation\nThe guides live at [docs.out-do.app](https://docs.out-do.app/reference/api/): the REST reference, [automations](https://docs.out-do.app/reference/workflows-v2) (the definition envelope, the validator, and the draft/apply cycle), the generated catalogs of [steps](https://docs.out-do.app/reference/passos/) and [triggers](https://docs.out-do.app/reference/gatilhos/), and the [MCP server](https://docs.out-do.app/reference/mcp/).","contact":{"name":"OutDo","url":"https://out-do.app/"},"version":"0.1.0"},"servers":[{"url":"https://api.out-do.app"},{"url":"https://api.dev.out-do.app"}],"paths":{"/v1/health":{"get":{"tags":["health"],"summary":"Liveness probe","description":"Unauthenticated. Touches no downstream service — safe for load-balancer checks.","operationId":"health","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthOut"}}}}}}},"/v1/auth/token":{"post":{"tags":["auth"],"summary":"Sign in with email and password","description":"Exchanges OutDo credentials for a bearer session. Authentication failures return a generic 401 (no cause disclosed) and the endpoint is strictly rate-limited.","operationId":"auth_token","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Authentication failed (generic; no cause disclosed)."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."}}}},"/v1/auth/refresh":{"post":{"tags":["auth"],"summary":"Refresh an access token","description":"Exchanges a refresh token for a new session; the previous refresh token is rotated.","operationId":"auth_refresh","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Authentication failed (generic; no cause disclosed)."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."}}}},"/v1/organizations":{"get":{"tags":["organizations"],"summary":"List organizations","description":"Cursor-paginated. Returns the organizations the caller owns or belongs to.","operationId":"list_organizations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_OrganizationOut_"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/organizations/{organization_id}":{"get":{"tags":["organizations"],"summary":"Get an organization","description":"Returns 404 when the organization does not exist or is not visible to the caller.","operationId":"get_organization","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Organization Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"patch":{"tags":["organizations"],"summary":"Update an organization","description":"Partial update of `label` and/or `config`. `config` is REPLACED whole, not merged: read it first and send the complete object. Requires an organization-level admin role; a write the data store refuses comes back as 403, never a silent 200. API keys need the `write` scope (not `structure`: this is not model structure).","operationId":"update_organization","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Organization Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationPatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"The data store refused the write (role too low, or a frozen field)."}}}},"/v1/workspaces":{"get":{"tags":["workspaces"],"summary":"List workspaces","description":"Cursor-paginated. Returns only what row-level security lets the caller see.","operationId":"list_workspaces","security":[{"HTTPBearer":[]}],"parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_WorkspaceOut_"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"post":{"tags":["workspaces"],"summary":"Create a workspace","description":"The caller must be a member of the target organization.","operationId":"create_workspace","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceCreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"404":{"description":"Not found / not visible."},"409":{"description":"Conflict."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/workspaces/{workspace_id}":{"get":{"tags":["workspaces"],"summary":"Get a workspace","description":"Returns 404 when the workspace does not exist or is not visible to the caller.","operationId":"get_workspace","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"patch":{"tags":["workspaces"],"summary":"Update a workspace","description":"Partial update — only the fields present in the request body are changed.","operationId":"update_workspace","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspacePatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"404":{"description":"Not found / not visible."},"409":{"description":"Conflict."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"delete":{"tags":["workspaces"],"summary":"Delete a workspace","description":"Destructive: the database cascades to every module, rail, feature, row, and view inside. A non-empty workspace returns 409 unless `force=true`.","operationId":"delete_workspace","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Confirm deleting a NON-EMPTY workspace.","default":false,"title":"Force"},"description":"Confirm deleting a NON-EMPTY workspace."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"404":{"description":"Not found / not visible."},"409":{"description":"Not empty — repeat with force=true to cascade."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/modules":{"get":{"tags":["modules"],"summary":"List modules","description":"Cursor-paginated. Optionally filtered to a single workspace.","operationId":"list_modules","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only modules of this workspace.","title":"Workspace Id"},"description":"Only modules of this workspace."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ModuleOut_"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"post":{"tags":["modules"],"summary":"Create a module","description":"The organization is derived from the parent workspace — it is never accepted from the client.","operationId":"create_module","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleCreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}}},"/v1/modules/{module_id}":{"get":{"tags":["modules"],"summary":"Get a module","description":"Returns 404 when the module does not exist or is not visible to the caller.","operationId":"get_module","security":[{"HTTPBearer":[]}],"parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Module Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"patch":{"tags":["modules"],"summary":"Update a module","description":"Partial update — only the fields present in the request body are changed.","operationId":"update_module","security":[{"HTTPBearer":[]}],"parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Module Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModulePatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}},"delete":{"tags":["modules"],"summary":"Delete a module","description":"A module that still has rails returns 409 unless `force=true` (rails cascade).","operationId":"delete_module","security":[{"HTTPBearer":[]}],"parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Module Id"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Confirm deleting a module that still has rails.","default":false,"title":"Force"},"description":"Confirm deleting a module that still has rails."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"404":{"description":"Not found / not visible."},"409":{"description":"Not empty — repeat with force=true to cascade."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/rails":{"get":{"tags":["rails"],"summary":"List rails","description":"Cursor-paginated. Optionally filtered to a single module.","operationId":"list_rails","security":[{"HTTPBearer":[]}],"parameters":[{"name":"module_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only rails of this module.","title":"Module Id"},"description":"Only rails of this module."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_RailOut_"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"post":{"tags":["rails"],"summary":"Create a rail","description":"The organization is derived from the parent module. `config` lists the views the rail shows; an object is accepted and normalized to the platform's single-item-list shape.","operationId":"create_rail","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RailCreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RailOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}}},"/v1/rails/{rail_id}":{"get":{"tags":["rails"],"summary":"Get a rail","description":"Returns 404 when the rail does not exist or is not visible to the caller.","operationId":"get_rail","security":[{"HTTPBearer":[]}],"parameters":[{"name":"rail_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Rail Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RailOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"patch":{"tags":["rails"],"summary":"Update a rail","description":"Partial update — only the fields present in the request body are changed.","operationId":"update_rail","security":[{"HTTPBearer":[]}],"parameters":[{"name":"rail_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Rail Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RailPatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RailOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}},"delete":{"tags":["rails"],"summary":"Delete a rail","description":"Removes the navigation entry; the views it pointed at are not touched.","operationId":"delete_rail","security":[{"HTTPBearer":[]}],"parameters":[{"name":"rail_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Rail Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/features":{"get":{"tags":["features"],"summary":"List features","description":"Cursor-paginated. Optionally filtered to a single workspace.","operationId":"list_features","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only features of this workspace.","title":"Workspace Id"},"description":"Only features of this workspace."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_FeatureOut_"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"post":{"tags":["features"],"summary":"Create a feature","description":"The organization is derived from the parent workspace — it is never accepted from the client.","operationId":"create_feature","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureCreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}}},"/v1/features/{feature_id}":{"get":{"tags":["features"],"summary":"Get a feature","description":"Returns 404 when the feature does not exist or is not visible to the caller.","operationId":"get_feature","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"patch":{"tags":["features"],"summary":"Update a feature","description":"Partial update — only the fields present in the request body are changed.","operationId":"update_feature","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeaturePatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}},"delete":{"tags":["features"],"summary":"Delete a feature","description":"Destructive: the database cascades to the feature's rows, columns, and views. A feature that still has rows returns 409 unless `force=true`.","operationId":"delete_feature","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Confirm deleting a feature that still has rows.","default":false,"title":"Force"},"description":"Confirm deleting a feature that still has rows."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"404":{"description":"Not found / not visible."},"409":{"description":"Not empty — repeat with force=true to cascade."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/features/{feature_id}/columns":{"get":{"tags":["features"],"summary":"List columns","description":"All column definitions of the feature, in display order.","operationId":"list_feature_columns","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FeatureColumnOut"},"title":"Response List Feature Columns"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"post":{"tags":["features"],"summary":"Create a column","description":"`type` and `default_component` are validated against the platform registries — see GET /v1/schema/definitions for the allowed values.","operationId":"create_feature_column","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColumnCreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureColumnOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}}},"/v1/features/{feature_id}/columns/{column_id}":{"patch":{"tags":["features"],"summary":"Update a column","description":"Partial update — only the fields present in the request body are changed.","operationId":"update_feature_column","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"column_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Column Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColumnPatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureColumnOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}},"delete":{"tags":["features"],"summary":"Delete a column","description":"Removes the column DEFINITION. Row data keeps the orphaned key — it simply stops being displayed and validated.","operationId":"delete_feature_column","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"column_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Column Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"404":{"description":"Not found / not visible."},"409":{"description":"Not empty — repeat with force=true to cascade."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/features/{feature_id}/rows":{"get":{"tags":["rows"],"summary":"List rows","description":"Cursor-paginated. Supports filtering (`filter`, `match`), sorting (`sort`), and response-side projection (`fields`) — see each parameter for the grammar.","operationId":"list_rows","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"filter","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Repeatable `column:op:value`, combined with AND (or OR via `match=any`). Text ops (`data->>col` as text): eq, neq, gt, gte, lt, lte, like, ilike (`*` wildcards), contains, ncontains, startswith, endswith (case-insensitive substring/prefix/suffix), in (`status:in:open|closed`), is (null|true|false). Numeric ops (`data->col` as JSONB, correct numeric order for stored JSON numbers): eqn, neqn, gtn, gten, ltn, lten. Any op can be negated with the `not.` prefix (`status:not.eq:closed`). Columns are the row data keys plus id/created_at/updated_at.","title":"Filter"},"description":"Repeatable `column:op:value`, combined with AND (or OR via `match=any`). Text ops (`data->>col` as text): eq, neq, gt, gte, lt, lte, like, ilike (`*` wildcards), contains, ncontains, startswith, endswith (case-insensitive substring/prefix/suffix), in (`status:in:open|closed`), is (null|true|false). Numeric ops (`data->col` as JSONB, correct numeric order for stored JSON numbers): eqn, neqn, gtn, gten, ltn, lten. Any op can be negated with the `not.` prefix (`status:not.eq:closed`). Columns are the row data keys plus id/created_at/updated_at."},{"name":"match","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"How the filters combine: `all` (AND, default) or `any` (OR).","title":"Match"},"description":"How the filters combine: `all` (AND, default) or `any` (OR)."},{"name":"sort","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sort key: `column` asc, `-column` desc. Changes the cursor scheme — do not mix cursors across sorts.","title":"Sort"},"description":"Sort key: `column` asc, `-column` desc. Changes the cursor scheme — do not mix cursors across sorts."},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated projection of the row `data` keys, e.g. `fields=title,amount`.","title":"Fields"},"description":"Comma-separated projection of the row `data` keys, e.g. `fields=title,amount`."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_RowOut_"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"post":{"tags":["rows"],"summary":"Create a row","description":"`data` is validated against the feature's column definitions by default — see the `validate` parameter. Send an `Idempotency-Key` header to make retries safe.","operationId":"create_row","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"validate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Write validation against the feature's columns: `on` (default — required columns present, typed columns storable, check digits of masked numbers, and a best-effort uniqueness probe for the columns that declare it), `strict` (also rejects unknown data keys), `off`. Uniqueness is ALWAYS enforced by the database, whatever the mode: a repeated value comes back as 409 naming the column. Currency columns are stored rounded to their declared decimal places (2 when unset).","title":"Validate"},"description":"Write validation against the feature's columns: `on` (default — required columns present, typed columns storable, check digits of masked numbers, and a best-effort uniqueness probe for the columns that declare it), `strict` (also rejects unknown data keys), `off`. Uniqueness is ALWAYS enforced by the database, whatever the mode: a repeated value comes back as 409 naming the column. Currency columns are stored rounded to their declared decimal places (2 when unset)."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowCreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied for this operation."},"404":{"description":"Not found / not visible."},"409":{"description":"The write conflicts with current state."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/features/{feature_id}/rows:aggregate":{"get":{"tags":["rows"],"summary":"Aggregate rows (count, sum, avg)","description":"`count` is exact and accepts the same filters as the list endpoint. `sum`/`avg` compute over a capped sample (10,000 rows — narrow with filters beyond that); non-numeric values are skipped and reported via `rows_considered`.","operationId":"aggregate_rows","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"op","in":"query","required":true,"schema":{"enum":["count","sum","avg"],"type":"string","title":"Op"}},{"name":"column","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Row data key (required for sum/avg).","title":"Column"},"description":"Row data key (required for sum/avg)."},{"name":"filter","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Repeatable `column:op:value`, combined with AND (or OR via `match=any`). Text ops (`data->>col` as text): eq, neq, gt, gte, lt, lte, like, ilike (`*` wildcards), contains, ncontains, startswith, endswith (case-insensitive substring/prefix/suffix), in (`status:in:open|closed`), is (null|true|false). Numeric ops (`data->col` as JSONB, correct numeric order for stored JSON numbers): eqn, neqn, gtn, gten, ltn, lten. Any op can be negated with the `not.` prefix (`status:not.eq:closed`). Columns are the row data keys plus id/created_at/updated_at.","title":"Filter"},"description":"Repeatable `column:op:value`, combined with AND (or OR via `match=any`). Text ops (`data->>col` as text): eq, neq, gt, gte, lt, lte, like, ilike (`*` wildcards), contains, ncontains, startswith, endswith (case-insensitive substring/prefix/suffix), in (`status:in:open|closed`), is (null|true|false). Numeric ops (`data->col` as JSONB, correct numeric order for stored JSON numbers): eqn, neqn, gtn, gten, ltn, lten. Any op can be negated with the `not.` prefix (`status:not.eq:closed`). Columns are the row data keys plus id/created_at/updated_at."},{"name":"match","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"How the filters combine: `all` (AND, default) or `any` (OR).","title":"Match"},"description":"How the filters combine: `all` (AND, default) or `any` (OR)."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregateOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/features/{feature_id}/rows/{row_id}":{"get":{"tags":["rows"],"summary":"Get a row","description":"Returns 404 when the row does not exist or is not visible to the caller.","operationId":"get_row","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"row_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Row Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"patch":{"tags":["rows"],"summary":"Update a row","description":"Replaces the whole `data` object by default; with `merge=true` the payload is shallow-merged over the current data and a `null` value removes the key.","operationId":"update_row","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"row_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Row Id"}},{"name":"validate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Write validation against the feature's columns: `on` (default — required columns present, typed columns storable, check digits of masked numbers, and a best-effort uniqueness probe for the columns that declare it), `strict` (also rejects unknown data keys), `off`. Uniqueness is ALWAYS enforced by the database, whatever the mode: a repeated value comes back as 409 naming the column. Currency columns are stored rounded to their declared decimal places (2 when unset).","title":"Validate"},"description":"Write validation against the feature's columns: `on` (default — required columns present, typed columns storable, check digits of masked numbers, and a best-effort uniqueness probe for the columns that declare it), `strict` (also rejects unknown data keys), `off`. Uniqueness is ALWAYS enforced by the database, whatever the mode: a repeated value comes back as 409 naming the column. Currency columns are stored rounded to their declared decimal places (2 when unset)."},{"name":"merge","in":"query","required":false,"schema":{"type":"boolean","description":"true → shallow-merge the payload over the current data (a null value removes the key); false (default) → replace the whole data object.","default":false,"title":"Merge"},"description":"true → shallow-merge the payload over the current data (a null value removes the key); false (default) → replace the whole data object."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowUpdateIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied for this operation."},"404":{"description":"Not found / not visible."},"409":{"description":"The write conflicts with current state."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"delete":{"tags":["rows"],"summary":"Delete a row","description":"Returns 204 on success; 404 when the row does not exist, is not visible, or the caller may not delete it.","operationId":"delete_row","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"row_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Row Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied for this operation."},"404":{"description":"Not found / not visible."},"409":{"description":"The write conflicts with current state."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/features/{feature_id}/rows:batch":{"post":{"tags":["rows"],"summary":"Create rows in bulk","description":"Up to 500 rows in one atomic insert — if any row is rejected, the whole batch rolls back. Validation errors name the offending row (`rows[i]: …`).","operationId":"create_rows_batch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Feature Id"}},{"name":"validate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Write validation against the feature's columns: `on` (default — required columns present, typed columns storable, check digits of masked numbers, and a best-effort uniqueness probe for the columns that declare it), `strict` (also rejects unknown data keys), `off`. Uniqueness is ALWAYS enforced by the database, whatever the mode: a repeated value comes back as 409 naming the column. Currency columns are stored rounded to their declared decimal places (2 when unset).","title":"Validate"},"description":"Write validation against the feature's columns: `on` (default — required columns present, typed columns storable, check digits of masked numbers, and a best-effort uniqueness probe for the columns that declare it), `strict` (also rejects unknown data keys), `off`. Uniqueness is ALWAYS enforced by the database, whatever the mode: a repeated value comes back as 409 naming the column. Currency columns are stored rounded to their declared decimal places (2 when unset)."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowsBatchIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowsBatchOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied for this operation."},"404":{"description":"Not found / not visible."},"409":{"description":"The write conflicts with current state."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/views":{"get":{"tags":["views"],"summary":"List views","description":"Cursor-paginated. Optionally filtered to a single feature.","operationId":"list_views","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only views of this feature.","title":"Feature Id"},"description":"Only views of this feature."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ViewOut_"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"post":{"tags":["views"],"summary":"Create a view","description":"`type` is validated against the view registry (see GET /v1/schema/definitions). `config` accepts an object or a list and is normalized to the platform's single-item-list shape. API keys need the `structure` scope.","operationId":"create_view","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewCreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}}},"/v1/views/{view_id}":{"get":{"tags":["views"],"summary":"Get a view","description":"Returns 404 when the view does not exist or is not visible to the caller.","operationId":"get_view","security":[{"HTTPBearer":[]}],"parameters":[{"name":"view_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"View Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"patch":{"tags":["views"],"summary":"Update a view","description":"Partial update — only the fields present in the request body are changed.","operationId":"update_view","security":[{"HTTPBearer":[]}],"parameters":[{"name":"view_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"View Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewPatchIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict."}}},"delete":{"tags":["views"],"summary":"Delete a view","description":"Removes the saved view; the feature's data is not touched.","operationId":"delete_view","security":[{"HTTPBearer":[]}],"parameters":[{"name":"view_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"View Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/schema":{"get":{"tags":["schema"],"summary":"Get the workspace model","description":"Workspaces → features → columns + views, nested. Structure only (no rows). `truncated: true` means a size cap was hit — narrow with `workspace_id`.","operationId":"get_schema","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Workspace Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Workspace not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/schema/definitions":{"get":{"tags":["schema"],"summary":"Get configuration definitions","description":"Static, versioned reference for programmatic builders (the AI MCP): allowed column types and components, view types and their config shapes, feature/workspace/rail/module config keys, row-data and file-metadata conventions. The write endpoints validate against these same allow-lists. `source` tells you where the reference came from: `generated` (emitted from the platform's field declarations) or `legacy` (the hand-maintained fallback, which may lag the code). `format=legacy` forces the older hand-maintained SHAPE (top-level `column`, `view`, `components`, `view_config`, ...): kept for one release so existing callers can migrate, and then removed. Build new clients on the default.","operationId":"get_schema_definitions","security":[{"HTTPBearer":[]}],"parameters":[{"name":"format","in":"query","required":false,"schema":{"enum":["generated","legacy"],"type":"string","description":"DEPRECATED: `legacy` serves the previous shape for one release.","deprecated":true,"default":"generated","title":"Format"},"description":"DEPRECATED: `legacy` serves the previous shape for one release.","deprecated":true},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Schema Definitions"}}}},"401":{"description":"Not authenticated."},"429":{"description":"Rate limited."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/templates":{"get":{"tags":["templates"],"summary":"List templates","description":"Cursor-paginated catalog: platform templates (`system`), shared ones (`public`), and the caller's own organization's. The package itself (`manifest`) is NOT included here, because it can be large; read one template to get it.","operationId":"list_templates","security":[{"HTTPBearer":[]}],"parameters":[{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"enum":["organization","workspace","module","feature"],"type":"string"},{"type":"null"}],"description":"Only this scope: organization|workspace|module|feature.","title":"Scope"},"description":"Only this scope: organization|workspace|module|feature."},{"name":"visibility","in":"query","required":false,"schema":{"anyOf":[{"enum":["system","org","public"],"type":"string"},{"type":"null"}],"description":"Only this visibility: system|org|public.","title":"Visibility"},"description":"Only this visibility: system|org|public."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_TemplateOut_"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/templates/{template_id}":{"get":{"tags":["templates"],"summary":"Get a template","description":"The catalog row plus the package itself (`manifest`). Returns 404 when the template does not exist or is not visible to the caller.","operationId":"get_template","security":[{"HTTPBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateDetailOut"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"404":{"description":"Not found / not visible."}}}},"/v1/templates/{template_id}/apply":{"post":{"tags":["templates"],"summary":"Apply a template to a workspace","description":"Starts a background job that applies the template's package to the target workspace: structure in one transaction, rows in batches. Returns the job id immediately — poll `GET /v1/template-jobs/{id}` for progress and the final report. Applying the SAME template to the SAME workspace twice creates nothing the second time and says so in the report. A second call while a job is still running is a 409 naming the job that is running. API keys need the `structure` scope.","operationId":"apply_template","security":[{"HTTPBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateApplyIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateApplyOut"}}}},"401":{"description":"Not authenticated."},"422":{"description":"The request cannot be applied as asked: `detail` names the refusal (`code`) and the ref to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateApplyProblem"}}}},"429":{"description":"Rate limited."},"503":{"description":"Upstream unavailable (data store, or the template executor)."},"404":{"description":"Not found / not visible."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict: this template is already being applied to this workspace."}}}},"/v1/templates/{template_id}/mapping-suggestions":{"post":{"tags":["templates"],"summary":"Suggest how a template's dependencies map onto a workspace","description":"For each dependency the package declares, the destination tables that look like it, ranked by confidence (0 to 1) from name/label similarity and the overlap of the columns the package actually uses, each with its column candidates. `proposed` is what a wizard would pre-select: `map` above 0.8, `import` when the package carries a blueprint, `decide` otherwise. Read-only: it changes nothing. Feed the answer straight into `resolutions` of apply_template. API keys need the `structure` scope.","operationId":"suggest_template_mappings","security":[{"HTTPBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateMappingSuggestionsIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateMappingSuggestionsOut"}}}},"401":{"description":"Not authenticated."},"422":{"description":"The request cannot be applied as asked: `detail` names the refusal (`code`) and the ref to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateApplyProblem"}}}},"429":{"description":"Rate limited."},"503":{"description":"Upstream unavailable (data store, or the template executor)."},"404":{"description":"Not found / not visible."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict: this template is already being applied to this workspace."}}}},"/v1/template-jobs":{"get":{"tags":["templates"],"summary":"List template apply jobs","description":"Cursor-paginated, newest state included: every apply job the caller can see, optionally narrowed to one workspace and one `status`. This is how a workspace finds the job that brought its sample rows in, since the job row itself never syncs to the app. API keys need the `structure` scope.","operationId":"list_template_jobs","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only jobs applied to this workspace.","title":"Workspace Id"},"description":"Only jobs applied to this workspace."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["queued","structure","rows","done","failed","cancelled"],"type":"string"},{"type":"null"}],"description":"Only jobs in this state.","title":"Status"},"description":"Only jobs in this state."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_TemplateJobOut_"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"404":{"description":"Not found / not visible."},"403":{"description":"The API key lacks the 'structure' scope."}}}},"/v1/template-jobs/{job_id}":{"get":{"tags":["templates"],"summary":"Get the state of a template apply job","description":"Poll this after `POST /v1/templates/{id}/apply`. `status` is `queued` (not started), `structure` (creating tables, columns and views), `rows` (loading data in batches), `done`, `failed` or `cancelled`. `report` counts what was created, skipped and inserted, plus `published` — how many automations came out published instead of draft — and is only complete once the job is `done`; `error` says what stopped a `failed` one. Returns 404 when the job does not exist or is not visible to the caller.","operationId":"get_template_job","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateJobOut"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"404":{"description":"Not found / not visible."},"403":{"description":"The API key lacks the 'structure' scope."}}}},"/v1/template-jobs/{job_id}/sample-rows/delete":{"post":{"tags":["templates"],"summary":"Remove the rows a template apply inserted","description":"Deletes the rows this job inserted, and nothing else: the tables, columns, views and workflows the package created stay exactly as they are, because work of your own may already live in them. Rows added after the apply are untouched too — only the ones the job itself created are named in its ledger. Idempotent: running it a second time removes nothing and reports zero. A job that has not finished is a 409; one applied before the executor started recording what it inserted is a 422 naming `ledger_without_row_ids`. API keys need the `structure` scope.","operationId":"delete_template_sample_rows","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateSampleRowsOut"}}}},"401":{"description":"Not authenticated."},"422":{"description":"The job did not record the rows it inserted, so they cannot be identified: `detail` names the refusal (`code`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateApplyProblem"}}}},"429":{"description":"Rate limited."},"503":{"description":"Upstream unavailable (data store, or the template executor)."},"404":{"description":"Not found / not visible."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"The job has not finished yet: poll it until `status` is `done`."}}}},"/v1/template-jobs/{job_id}/assign-me":{"post":{"tags":["templates"],"summary":"Put the caller in the empty user columns of a template apply","description":"Fills the user columns of the rows this job inserted with the caller, and only where they are empty: a row that already has someone keeps them. The structure is not touched, and neither are tables without a user column. Idempotent: a second run fills nothing and reports zero. A job that has not finished is a 409; one applied before the executor started recording what it inserted is a 422 naming `ledger_without_row_ids`. API keys need the `structure` scope.","operationId":"assign_template_sample_rows","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateSampleRowsOut"}}}},"401":{"description":"Not authenticated."},"422":{"description":"The job did not record the rows it inserted, so they cannot be identified: `detail` names the refusal (`code`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateApplyProblem"}}}},"429":{"description":"Rate limited."},"503":{"description":"Upstream unavailable (data store, or the template executor)."},"404":{"description":"Not found / not visible."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"The job has not finished yet: poll it until `status` is `done`."}}}},"/v1/files":{"post":{"tags":["files"],"summary":"Upload a file","description":"Multipart upload. The object path is built server-side as `feature_{feature_id}/column_{column_id}/{timestamp}_{fileName}`; the response is the exact metadata object a `file` component stores in row data.","operationId":"upload_file","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_file"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied for this file."},"404":{"description":"Not found / not visible."},"409":{"description":"A file already exists at this path."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream store unavailable."}}}},"/v1/files/sign":{"get":{"tags":["files"],"summary":"Create a signed download URL","description":"Returns a time-limited URL (1 minute to 7 days, default 1 hour) for a stored object. Visibility follows the same permissions as the file's feature.","operationId":"sign_file_url","security":[{"HTTPBearer":[]}],"parameters":[{"name":"path","in":"query","required":true,"schema":{"type":"string","description":"The stored object path (FileOut.path).","title":"Path"},"description":"The stored object path (FileOut.path)."},{"name":"expires_in","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Lifetime in seconds, 60 to 604800. Defaults to 3600.","title":"Expires In"},"description":"Lifetime in seconds, 60 to 604800. Defaults to 3600."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedUrlOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied for this file."},"404":{"description":"Not found / not visible."},"409":{"description":"A file already exists at this path."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream store unavailable."}}}},"/v1/workflows":{"get":{"tags":["workflows"],"summary":"List workflows","description":"Cursor-paginated. Optionally filtered to a single workspace.","operationId":"list_workflows","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only workflows of this workspace.","title":"Workspace Id"},"description":"Only workflows of this workspace."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_WorkflowOut_"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/workflows/{workflow_id}":{"get":{"tags":["workflows"],"summary":"Get a workflow","description":"Returns 404 when the workflow does not exist or is not visible to the caller.","operationId":"get_workflow","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workflow Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/workflows/{workflow_id}/trigger":{"post":{"tags":["workflows"],"summary":"Trigger a workflow run","description":"Queues one run of the workflow with the given input and returns it immediately (202 — execution is asynchronous). Poll the run's `status` until it reaches `success`, `failed`, or `cancelled`. Inactive workflows return 409. Send an `Idempotency-Key` header to make retries safe.","operationId":"trigger_workflow","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workflow Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowTriggerIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"403":{"description":"Permission denied."},"409":{"description":"The workflow is not active."}}}},"/v1/workflows/{workflow_id}/runs":{"get":{"tags":["workflows"],"summary":"List runs of a workflow","description":"Newest first. Cursor-paginated.","operationId":"list_workflow_runs","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workflow Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_WorkflowRunOut_"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/workflows/{workflow_id}/runs/{run_id}":{"get":{"tags":["workflows"],"summary":"Get a workflow run","description":"Returns 404 when the run does not exist or is not visible to the caller.","operationId":"get_workflow_run","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workflow Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunOut"}}}},"401":{"description":"Not authenticated."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/workflows-v2":{"get":{"tags":["workflows-v2"],"summary":"List workflow definitions","description":"Cursor-paginated. Each item is ONE VERSION of a workflow; `workflow_id` groups the versions of a logical workflow. Pass `active=true` for the versions that are currently firing. `active=false` is not a filter: a superseded version is also inactive, so it would list live workflows as if they were stopped.","operationId":"list_workflows_v2","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only workflows of this workspace.","title":"Workspace Id"},"description":"Only workflows of this workspace."},{"name":"active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only the currently firing versions.","title":"Active"},"description":"Only the currently firing versions."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_WorkflowV2Out_"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"post":{"tags":["workflows-v2"],"summary":"Create a workflow","description":"Creates the automation as a DRAFT (a draft never fires). The definition is validated first: errors are a 422 and nothing is written. `organization_id` and `workflow_id` are not accepted: the first is derived from the workspace, the second is minted server-side. Pass `is_draft: false` to create AND apply it in one call; the result is an applied but INACTIVE version, which you then activate in the app. Send an `Idempotency-Key` header to make retries safe. API keys need the `structure` scope. The envelope is documented at [docs.out-do.app/reference/workflows-v2](https://docs.out-do.app/reference/workflows-v2).","operationId":"create_workflow_v2","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowV2CreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowV2Out"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream unavailable (data store, or the workflow validator)."},"404":{"description":"Not found / not visible."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict: another session wrote first, or there is no active version."}}}},"/v1/workflows-v2/{workflow_id}":{"get":{"tags":["workflows-v2"],"summary":"Get a workflow definition","description":"By default returns the version that represents the workflow: the active one, or the latest APPLIED one when the workflow is paused. Never the unapplied draft, unless the workflow has nothing else. Pass `version` for an exact snapshot.","operationId":"get_workflow_v2","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workflow Id"}},{"name":"version","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"An exact version of this workflow.","title":"Version"},"description":"An exact version of this workflow."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowV2Out"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"404":{"description":"Not found / not visible."}}}},"/v1/workflows-v2/validate":{"post":{"tags":["workflows-v2"],"summary":"Validate a workflow definition","description":"Runs the platform's graph validator over `{trigger_config, graph_config}` and returns `{ok, issues}`. Writes NOTHING: a definition with errors is a 200 with `ok: false`, not a rejection. `ok` means no ERRORS; warnings never block. Each issue carries a stable `code` to branch on, and `target` tells you where it is fixed (`trigger` or the `path` inside the graph). Pass `workspace_id` (or call with an API key, which carries its organization) so the validator can authorize. It writes nothing, so an API key only needs the `read` scope.","operationId":"validate_workflow_v2","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowV2ValidateIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationOut"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"The workflow validator is unavailable."},"404":{"description":"Not found / not visible."}}}},"/v1/workflows-v2/{workflow_id}/versions":{"post":{"tags":["workflows-v2"],"summary":"Add a version to a workflow","description":"Atomically deactivates the current active version and inserts the next one as active. Validated first. Requires the workflow to HAVE an active version: a paused one, or one that only has a draft, is a 409 telling you to apply first. Fields you omit keep their previous value. API keys need the `structure` scope.","operationId":"create_workflow_v2_version","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workflow Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowV2VersionIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowV2Out"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream unavailable (data store, or the workflow validator)."},"404":{"description":"Not found / not visible."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict: another session wrote first, or there is no active version."}}}},"/v1/workflows-v2/{workflow_id}/apply":{"post":{"tags":["workflows-v2"],"summary":"Apply a workflow's draft","description":"Promotes the draft to a version, PRESERVING the activation state: a paused workflow stays paused and a brand-new one is born inactive. Validated first; the data store also re-checks, under a lock, that neither the draft nor its base moved (409 if they did, which means reload and reconcile, never retry). API keys need the `structure` scope.","operationId":"apply_workflow_v2_draft","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workflow Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowV2Out"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error (e.g. a malformed cursor)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream unavailable (data store, or the workflow validator)."},"404":{"description":"Not found / not visible."},"403":{"description":"Permission denied, or the API key lacks the 'structure' scope."},"409":{"description":"Conflict: another session wrote first, or there is no active version."}}}},"/v1/messages":{"post":{"tags":["messages"],"summary":"Post a message","description":"Post a message to a scope — a channel, feature, feature row, workspace, or organization. The organization is derived from the scope and the sender is the authenticated caller; neither is accepted from the body. Authorization is decided by the `Message` INSERT policy (organization membership): a caller who cannot see the scope gets 404.","operationId":"create_message","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageCreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Permission denied."},"404":{"description":"Scope not found / not visible."},"422":{"description":"Validation error (unknown scope_type/type, or empty content)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/api-keys":{"post":{"tags":["api-keys"],"summary":"Create an API key","description":"Mints a scoped API key backed by a machine user. The response carries the plaintext `secret` **once** — store it now, it cannot be retrieved again. The key acts as a member of the organization with the given role and scopes; requires permission to manage the organization's keys. Set `expires_at` (ISO-8601) or `expires_in_days` for a time-limited key; omit both for a non-expiring key.","operationId":"create_api_key","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreatedOut"}}}},"401":{"description":"Not authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed to manage keys for this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found / not visible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Upstream data store unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Active API key limit reached for this scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"tags":["api-keys"],"summary":"List API keys","description":"Cursor-paginated. Returns key metadata (never the secret). Optionally filtered to one organization.","operationId":"list_api_keys","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only keys of this organization.","title":"Organization Id"},"description":"Only keys of this organization."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ApiKeyOut_"}}}},"401":{"description":"Not authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"API keys cannot manage API keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found / not visible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Upstream data store unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/api-keys/{key_id}":{"get":{"tags":["api-keys"],"summary":"Get an API key","description":"Returns key metadata (never the secret). 404 when not visible to the caller.","operationId":"get_api_key","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Key Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyOut"}}}},"401":{"description":"Not authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"API keys cannot manage API keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found / not visible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Upstream data store unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["api-keys"],"summary":"Revoke an API key","description":"Revokes the key and tears down its machine user's membership immediately — the key can no longer authenticate. Idempotent.","operationId":"revoke_api_key","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Key Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed to manage keys for this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found / not visible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Upstream data store unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Active API key limit reached for this scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/external-accounts/invite":{"post":{"tags":["external-accounts"],"summary":"Invite an external account","description":"Provisions a client account for the organization: reuses the identity when one already exists for the email, otherwise creates it and sends the invite. The account is never duplicated — inviting the same email again returns the same account, and what else happens follows its status: `active` is returned untouched (the person already has a login), `invited` **sends the mail again**, and `revoked` **reactivates** the account and mails. The account never becomes a member of the organization. Requires organization administrator.\n\nWhich mail goes out depends on the identity: a person who has no login yet gets the **invite**, a person who already has one gets an **access link** (a sign-in link — they have no password to set, and the identity provider refuses to invite them at all). Except for `no_action`, every outcome means a mail left.\n\n`outcome` reports which of those happened (`created`, `access_link_sent`, `reactivated`, `invite_resent`, `no_action`) — the returned account does not show it, since all paths answer 200 with `status='invited'`. Do not infer the outcome from the account's previous status.","operationId":"invite_external_account","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalAccountInviteIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalAccountInviteOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Not an administrator of the organization."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store / identity provider unavailable."},"409":{"description":"The identity could not be reused for this email."}}}},"/v1/external-accounts/{account_id}/revoke":{"post":{"tags":["external-accounts"],"summary":"Revoke an external account","description":"Sets the account to `revoked` — access stops on the caller's next request — and closes the identity's way back in when this was its last external account and it holds no OutDo membership. Idempotent. Requires organization administrator.","operationId":"revoke_external_account","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalAccountOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Not an administrator of the organization."},"404":{"description":"Not found / not visible."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store / identity provider unavailable."}}}},"/v1/external/me":{"get":{"tags":["external"],"summary":"The caller's client accounts","description":"Resolves the signed-in identity to its client accounts — one per organization that invited it — and is the call the portal makes first. An identity with no account, or with only revoked ones, is refused with 403 and a body that does not say which of the two it was.\n\n**First call activates.** An account still `invited` becomes `active` here: signing in to the portal IS the acceptance of the invite. Until then it can read no data, and after a revocation it cannot again.","operationId":"get_external_me","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalMeOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"No active client account for this identity."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/external/features":{"get":{"tags":["external"],"summary":"The features published to client accounts","description":"The organization's features that carry an `external_user` column — adding that column IS publishing the feature — each with the columns marked externally visible.\n\nA published feature with no visible columns is listed with an empty `columns`: it is a real state (rows marked, nothing chosen to show yet), not an error. The `external_user` column itself is never listed.","operationId":"list_external_features","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"The organization whose area is being read. Must be one of the organizations returned by `GET /v1/external/me`.","title":"Organization Id"},"description":"The organization whose area is being read. Must be one of the organizations returned by `GET /v1/external/me`."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalFeaturesOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"No active client account for this identity."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/external/rows":{"get":{"tags":["external"],"summary":"The caller's rows of one published feature","description":"Cursor-paginated. Returns ONLY the rows whose `external_user` column contains the caller's client account, projected to the columns the organization marked externally visible — both imposed server-side, neither expressible by the caller.\n\nA feature without an `external_user` column answers 404, and so does one whose column was removed a moment ago: the configuration is re-read on every request, so unpublishing takes effect immediately. An account with nothing marked gets an empty page, not an error.","operationId":"list_external_rows","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","description":"A feature listed by `GET /v1/external/features`.","title":"Feature Id"},"description":"A feature listed by `GET /v1/external/features`."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor taken from `next_cursor` of the previous page.","title":"Cursor"},"description":"Opaque cursor taken from `next_cursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size, 1 to 200. Defaults to 25.","default":25,"title":"Limit"},"description":"Page size, 1 to 200. Defaults to 25."},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated subset of the row `data` keys to return. It can only NARROW the organization's own choice of externally visible columns — a key that is not visible is not returned by naming it here.","title":"Fields"},"description":"Comma-separated subset of the row `data` keys to return. It can only NARROW the organization's own choice of externally visible columns — a key that is not visible is not returned by naming it here."},{"name":"organization_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"The organization whose area is being read. Must be one of the organizations returned by `GET /v1/external/me`.","title":"Organization Id"},"description":"The organization whose area is being read. Must be one of the organizations returned by `GET /v1/external/me`."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ExternalRowOut_"}}}},"401":{"description":"Not authenticated."},"403":{"description":"No active client account for this identity."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."},"404":{"description":"No such feature, or it is not published to client accounts."}}}},"/v1/ai/catalog":{"get":{"tags":["ai"],"summary":"List the active AI model catalog (operator-curated)","operationId":"list_ai_models","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter the catalog to the models this org's tier may use.","title":"Organization Id"},"description":"Filter the catalog to the models this org's tier may use."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CatalogModelOut"},"title":"Response List Ai Models"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/ai/settings":{"get":{"tags":["ai"],"summary":"Get an organization's per-mode model selection","operationId":"get_ai_settings","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","description":"Organization id.","title":"Organization Id"},"description":"Organization id."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettingsOut"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}},"patch":{"tags":["ai"],"summary":"Set an organization's per-mode model selection (owner/admin)","operationId":"set_ai_settings","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSettingsIn"}}}},"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated."},"403":{"description":"Not allowed to manage AI settings for this organization."},"422":{"description":"Validation error (unknown mode/model, or model not allowed for tier)."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/ai/service-defaults":{"get":{"tags":["ai"],"summary":"Get the global service-default mode->model_key map (editable by superadmin)","operationId":"get_ai_service_defaults","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceDefaultsOut"}}}},"401":{"description":"Not authenticated."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/ai/credits/balance":{"get":{"tags":["ai"],"summary":"Get an organization's AI credit balance (member-gated)","operationId":"get_org_credit_balance","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","description":"Organization id.","title":"Organization Id"},"description":"Organization id."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditBalanceOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Not a member of this organization."},"422":{"description":"Validation error."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/ai/usage-summary":{"get":{"tags":["ai"],"summary":"Get an organization's AI usage summary (member-gated)","operationId":"get_ai_usage_summary","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","description":"Organization id.","title":"Organization Id"},"description":"Organization id."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 timestamp; only events after this point are included.","title":"Since"},"description":"ISO-8601 timestamp; only events after this point are included."},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageSummaryOut"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Not a member of this organization."},"422":{"description":"Validation error."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/ai/org/{organization_id}/provider-secrets":{"get":{"tags":["ai"],"summary":"List which providers the organization has a BYOK key for (no values)","operationId":"list_org_provider_secrets","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Organization Id"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProviderSecretMaskedOut"},"title":"Response List Org Provider Secrets"}}}},"401":{"description":"Not authenticated."},"403":{"description":"Not allowed to manage provider secrets for this organization."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/ai/org/{organization_id}/provider-secret/{provider}":{"put":{"tags":["ai"],"summary":"Set/replace the organization's BYOK key for a provider (owner/admin)","operationId":"set_org_provider_secret","security":[{"HTTPBearer":[]}],"parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Organization Id"}},{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}},{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetProviderSecretIn"}}}},"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated."},"403":{"description":"Not allowed to manage provider secrets for this organization."},"422":{"description":"Validation error."},"429":{"description":"Rate limited."},"503":{"description":"Upstream data store unavailable."}}}},"/v1/admin/stats":{"get":{"tags":["admin"],"summary":"Service stats (superadmin)","description":"Version and uptime of the outdo_api_data service. Superadmin session only.","operationId":"admin_stats","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-outdo-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Outdo-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminStatsOut"}}}},"401":{"description":"Not authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not a superadmin (or an API-key caller).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Upstream data store unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AdminStatsOut":{"properties":{"service":{"type":"string","title":"Service","default":"outdo_api_data"},"version":{"type":"string","title":"Version"},"uptime_s":{"type":"integer","title":"Uptime S"},"started_at":{"type":"string","title":"Started At"}},"type":"object","required":["version","uptime_s","started_at"],"title":"AdminStatsOut","description":"Service facts for the console — deliberately free of org data and secrets."},"AggregateOut":{"properties":{"op":{"type":"string","title":"Op"},"column":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Column"},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value"},"rows_considered":{"type":"integer","title":"Rows Considered"}},"type":"object","required":["op","rows_considered"],"title":"AggregateOut","description":"Result of rows:aggregate. `value` is null for avg over zero numeric values;\n`rows_considered` is the number of values that entered the computation (for count,\nthe count itself)."},"ApiKeyCreateIn":{"properties":{"organization_id":{"type":"string","title":"Organization Id"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"},"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"role":{"type":"string","enum":["admin","dev","user","view"],"title":"Role","default":"user"},"scopes":{"items":{"type":"string","enum":["read","write","structure"]},"type":"array","minItems":1,"title":"Scopes"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"Absolute expiry as ISO-8601 UTC. Mutually exclusive with expires_in_days."},"expires_in_days":{"anyOf":[{"type":"integer","maximum":3650.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days","description":"Relative expiry in days from creation. Mutually exclusive with expires_at."}},"type":"object","required":["organization_id","name"],"title":"ApiKeyCreateIn"},"ApiKeyCreatedOut":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"},"role":{"type":"string","title":"Role"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"secret":{"type":"string","title":"Secret"}},"type":"object","required":["id","organization_id","name","key_prefix","role","scopes","secret"],"title":"ApiKeyCreatedOut","description":"The creation response — carries the one-time plaintext `secret`. Store it now; it\ncannot be retrieved again."},"ApiKeyOut":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"},"role":{"type":"string","title":"Role"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"}},"type":"object","required":["id","organization_id","name","key_prefix","role","scopes"],"title":"ApiKeyOut","description":"A key's metadata. The secret is NEVER part of this — see ApiKeyCreatedOut."},"Body_upload_file":{"properties":{"feature_id":{"type":"string","format":"uuid","title":"Feature Id"},"column_id":{"type":"string","format":"uuid","title":"Column Id"},"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["feature_id","column_id","file"],"title":"Body_upload_file"},"CatalogModelOut":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label"},"api_type":{"type":"string","title":"Api Type"},"model":{"type":"string","title":"Model"},"base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Url"},"provider_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Secret"},"mask_sensitive":{"type":"boolean","title":"Mask Sensitive"},"capability":{"type":"string","title":"Capability"},"credit_multiplier":{"type":"number","title":"Credit Multiplier"},"unit_price_eur":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Unit Price Eur"},"input_price_eur":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Input Price Eur"},"cached_input_price_eur":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cached Input Price Eur"},"cache_write_price_eur":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cache Write Price Eur"},"output_price_eur":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Output Price Eur"},"allowed_tier_names":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Tier Names"},"is_active":{"type":"boolean","title":"Is Active"},"sort_order":{"type":"integer","title":"Sort Order"}},"type":"object","required":["key","label","api_type","model","mask_sensitive","capability","credit_multiplier","is_active","sort_order"],"title":"CatalogModelOut"},"ColumnCreateIn":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name"},"label":{"type":"string","maxLength":120,"minLength":1,"title":"Label"},"type":{"type":"string","title":"Type","default":"text"},"default_component":{"type":"string","title":"Default Component","default":"textfield"},"config":{"additionalProperties":true,"type":"object","title":"Config"},"required":{"type":"boolean","title":"Required","default":false},"read_only":{"type":"boolean","title":"Read Only","default":false},"hidden":{"type":"boolean","title":"Hidden","default":false},"inline_editable":{"type":"boolean","title":"Inline Editable","default":false},"externally_visible":{"type":"boolean","title":"Externally Visible","default":false},"order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Order"},"initial_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Initial Value"}},"type":"object","required":["name","label"],"title":"ColumnCreateIn","description":"A column definition; `type` and `default_component` are validated against the\nplatform registries (see GET /v1/schema/definitions)."},"ColumnPatchIn":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Name"},"label":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Label"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"default_component":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Component"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"required":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Required"},"read_only":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Read Only"},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Hidden"},"inline_editable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Inline Editable"},"externally_visible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Externally Visible"},"order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Order"},"initial_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Initial Value"}},"type":"object","title":"ColumnPatchIn"},"CreditBalanceOut":{"properties":{"organization_id":{"type":"string","title":"Organization Id"},"allowance_remaining":{"type":"integer","title":"Allowance Remaining"},"topup_balance":{"type":"integer","title":"Topup Balance"},"period_start":{"type":"string","title":"Period Start"},"tier_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier Name"}},"type":"object","required":["organization_id","allowance_remaining","topup_balance","period_start"],"title":"CreditBalanceOut"},"ExternalAccountInviteIn":{"properties":{"organization_id":{"type":"string","minLength":1,"title":"Organization Id"},"email":{"type":"string","maxLength":320,"minLength":3,"title":"Email"},"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Name"}},"type":"object","required":["organization_id","email"],"title":"ExternalAccountInviteIn","description":"`POST /v1/external-accounts/invite` — who to invite, and to which organization."},"ExternalAccountInviteOut":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"email":{"type":"string","title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"status":{"type":"string","title":"Status"},"invited_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invited At"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"outcome":{"type":"string","enum":["created","access_link_sent","reactivated","invite_resent","no_action"],"title":"Outcome"}},"type":"object","required":["id","organization_id","email","status","outcome"],"title":"ExternalAccountInviteOut","description":"The invite response: the account, plus what this call did to it.\n\n`outcome` is here because the caller cannot infer it. The same 200 with\n`status='invited'` comes back from a fresh provisioning, from a reactivation and from a\nplain re-send, so a client can only guess — from the status the row had BEFORE, which it\nmay not have, and which does not say whether a mail went out. Additive: every inherited\nfield keeps its meaning, so a client that ignores `outcome` is unaffected."},"ExternalAccountOut":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"email":{"type":"string","title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"status":{"type":"string","title":"Status"},"invited_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invited At"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","organization_id","email","status"],"title":"ExternalAccountOut","description":"A client account. `user_id` is the shared GoTrue identity — the same person can hold an\nOutDo membership and any number of external accounts, so it is NOT an \"is external\" flag."},"ExternalColumnOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"default_component":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Component"}},"type":"object","required":["id","name"],"title":"ExternalColumnOut","description":"One column the organization switched on for clients (`externally_visible`). It is a\nRENDERING hint: `type` and `default_component` tell the portal how to draw the value it\nalready has, and nothing here is ever consulted to decide access.\n\nThe `external_user` column itself is never in this list, however it is configured — see\n`read_service.publication`."},"ExternalFeatureOut":{"properties":{"id":{"type":"string","title":"Id"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"columns":{"items":{"$ref":"#/components/schemas/ExternalColumnOut"},"type":"array","title":"Columns"}},"type":"object","required":["id","columns"],"title":"ExternalFeatureOut","description":"A published feature: one that carries an `external_user` column (spec §3.1).\n\n`columns` MAY be empty — published with nothing switched on is a real state (the rows are\nmarked, the organization has not yet decided what to show), and the portal decides what to\ndo with it. Answering 404 for it would say \"no such feature\" about a feature that exists."},"ExternalFeaturesOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ExternalFeatureOut"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"ExternalFeaturesOut","description":"The published features of ONE organization — the one the account resolved to."},"ExternalMeAccount":{"properties":{"account_id":{"type":"string","title":"Account Id"},"organization_id":{"type":"string","title":"Organization Id"},"organization_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Label"},"email":{"type":"string","title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"status":{"type":"string","title":"Status"}},"type":"object","required":["account_id","organization_id","email","status"],"title":"ExternalMeAccount","description":"One account = one organization. The same person may hold several (the Ofitech case: one\nportal, many workshops), which is why this is a list and not an object."},"ExternalMeOut":{"properties":{"user_id":{"type":"string","title":"User Id"},"accounts":{"items":{"$ref":"#/components/schemas/ExternalMeAccount"},"type":"array","title":"Accounts"}},"type":"object","required":["user_id","accounts"],"title":"ExternalMeOut","description":"The caller's client accounts. `accounts` is never empty — no active account is a 403,\nnot an empty list, so the portal cannot render a logged-in shell for someone with no access."},"ExternalRowOut":{"properties":{"id":{"type":"string","title":"Id"},"feature_id":{"type":"string","title":"Feature Id"},"data":{"additionalProperties":true,"type":"object","title":"Data"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","feature_id","data"],"title":"ExternalRowOut","description":"A row the caller is marked on, projected to the visible columns.\n\n`data` is already filtered server-side; `created_by`/`updated_by`/`workspace_id` are\ndeliberately absent (they are the organization's own people and its internal structure)."},"FeatureColumnOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"label":{"type":"string","title":"Label"},"type":{"type":"string","title":"Type"},"config":{"additionalProperties":true,"type":"object","title":"Config"},"feature_id":{"type":"string","title":"Feature Id"},"default_component":{"type":"string","title":"Default Component"},"initial_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Initial Value"},"read_only":{"type":"boolean","title":"Read Only"},"required":{"type":"boolean","title":"Required"},"hidden":{"type":"boolean","title":"Hidden"},"inline_editable":{"type":"boolean","title":"Inline Editable","default":false},"externally_visible":{"type":"boolean","title":"Externally Visible","default":false},"order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Order"}},"type":"object","required":["id","label","type","config","feature_id","default_component","read_only","required","hidden"],"title":"FeatureColumnOut","description":"A column definition: how a row-data key is typed, edited, and displayed."},"FeatureCreateIn":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","required":["workspace_id","name"],"title":"FeatureCreateIn","description":"`organization_id` is NEVER accepted — derived from the parent workspace."},"FeatureOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"workspace_id":{"type":"string","title":"Workspace Id"},"organization_id":{"type":"string","title":"Organization Id"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","required":["id","name","workspace_id","organization_id"],"title":"FeatureOut","description":"A feature — a data table whose rows are described by its column definitions."},"FeaturePatchIn":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","title":"FeaturePatchIn"},"FileOut":{"properties":{"path":{"type":"string","title":"Path"},"fileName":{"type":"string","title":"Filename"},"mimeType":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mimetype"},"fileSize":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Filesize"},"syncStatus":{"type":"string","title":"Syncstatus","default":"synced"}},"type":"object","required":["path","fileName"],"title":"FileOut"},"GraphIssueOut":{"properties":{"severity":{"type":"string","title":"Severity"},"message":{"type":"string","title":"Message"},"code":{"type":"string","title":"Code"},"path":{"type":"string","title":"Path","default":""},"field_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field Path"},"target":{"type":"string","title":"Target","default":"graph"}},"type":"object","required":["severity","message","code"],"title":"GraphIssueOut","description":"A problem with the definition, as the validator reports it.\n\n`code` is the stable identity (snake_case) to branch on; `message` is copy and may\nchange. `target` says where it is fixed: `trigger` opens the trigger, `graph` is located\nby `path` (empty means the whole graph)."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthOut":{"properties":{"status":{"type":"string","title":"Status","default":"ok"}},"type":"object","title":"HealthOut","description":"Liveness response."},"MessageCreateIn":{"properties":{"scope_type":{"type":"string","title":"Scope Type"},"scope_id":{"type":"string","title":"Scope Id"},"content":{"type":"string","minLength":1,"title":"Content"},"type":{"type":"string","title":"Type","default":"chat"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["scope_type","scope_id","content"],"title":"MessageCreateIn","description":"`organization_id` (derived from the scope) and `sender_id` (the authenticated caller)\nare NEVER accepted from the client — a caller cannot mislabel tenancy or spoof authorship.\n`scope_type` and `type` are validated against the `Message` CHECK constraints."},"MessageOut":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"scope_type":{"type":"string","title":"Scope Type"},"scope_id":{"type":"string","title":"Scope Id"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"},"sender_id":{"type":"string","title":"Sender Id"},"content":{"type":"string","title":"Content"},"type":{"type":"string","title":"Type"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","organization_id","scope_type","scope_id","sender_id","content","type"],"title":"MessageOut","description":"A message posted to a scope (a channel, feature, feature row, workspace, or org)."},"ModuleCreateIn":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"label":{"type":"string","maxLength":120,"minLength":1,"title":"Label"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"}},"type":"object","required":["workspace_id","name","label"],"title":"ModuleCreateIn","description":"`organization_id` is NEVER accepted — derived from the parent workspace."},"ModuleOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"workspace_id":{"type":"string","title":"Workspace Id"},"organization_id":{"type":"string","title":"Organization Id"},"default_rail_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Rail Id"},"rail_order_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Rail Order Config"}},"type":"object","required":["id","name","label","workspace_id","organization_id"],"title":"ModuleOut"},"ModulePatchIn":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"label":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Label"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"default_rail_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Rail Id"},"rail_order_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Rail Order Config"}},"type":"object","title":"ModulePatchIn"},"OrganizationOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","required":["id","name"],"title":"OrganizationOut","description":"An organization — the tenancy root that owns workspaces and memberships."},"OrganizationPatchIn":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Label"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","title":"OrganizationPatchIn","description":"Partial update. `config` é substituído por inteiro, não fundido: quem chama leu o\nGET e manda o objeto completo. Um merge implícito perderia chaves sem ninguém pedir.\n\n`name` fica de fora de propósito: é o handle da organização (ver\n`docs/architecture/organization-handle.md`) e mudá-lo tem consequências noutros sistemas.\n`owner_id`, `trial_started_at`, `created_at` e `created_by` não são aceitáveis aqui porque\na base os congela em silêncio para não-donos (274:231-234)."},"Page_ApiKeyOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ApiKeyOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[ApiKeyOut]"},"Page_ExternalRowOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ExternalRowOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[ExternalRowOut]"},"Page_FeatureOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/FeatureOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[FeatureOut]"},"Page_ModuleOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ModuleOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[ModuleOut]"},"Page_OrganizationOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/OrganizationOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[OrganizationOut]"},"Page_RailOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RailOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[RailOut]"},"Page_RowOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RowOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[RowOut]"},"Page_TemplateJobOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TemplateJobOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[TemplateJobOut]"},"Page_TemplateOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TemplateOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[TemplateOut]"},"Page_ViewOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ViewOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[ViewOut]"},"Page_WorkflowOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WorkflowOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[WorkflowOut]"},"Page_WorkflowRunOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WorkflowRunOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[WorkflowRunOut]"},"Page_WorkflowV2Out_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WorkflowV2Out"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[WorkflowV2Out]"},"Page_WorkspaceOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WorkspaceOut"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"Page[WorkspaceOut]"},"Problem":{"properties":{"type":{"type":"string","title":"Type","default":"about:blank"},"title":{"type":"string","title":"Title"},"status":{"type":"integer","title":"Status"},"detail":{"anyOf":[{"type":"string"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Detail"},"instance":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instance"},"code":{"type":"string","title":"Code","default":"error"}},"type":"object","required":["title","status"],"title":"Problem","description":"RFC 7807 Problem Details: the single error response shape across the API.\n\n`code` is an extension member: a short, stable, machine-readable identifier the client\nmaps to its own copy. It never changes for a given failure cause."},"ProviderSecretMaskedOut":{"properties":{"provider":{"type":"string","title":"Provider"},"is_set":{"type":"boolean","title":"Is Set"}},"type":"object","required":["provider","is_set"],"title":"ProviderSecretMaskedOut","description":"Browser-facing — which providers the org has a key for, WITHOUT the value."},"RailCreateIn":{"properties":{"module_id":{"type":"string","title":"Module Id"},"label":{"type":"string","maxLength":120,"minLength":1,"title":"Label"},"path":{"type":"string","title":"Path","default":""},"config":{"anyOf":[{"items":{},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","required":["module_id","label"],"title":"RailCreateIn","description":"`organization_id` is NEVER accepted — derived from the parent module."},"RailOut":{"properties":{"id":{"type":"string","title":"Id"},"label":{"type":"string","title":"Label"},"path":{"type":"string","title":"Path"},"module_id":{"type":"string","title":"Module Id"},"organization_id":{"type":"string","title":"Organization Id"},"config":{"anyOf":[{"items":{},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","required":["id","label","path","module_id","organization_id"],"title":"RailOut"},"RailPatchIn":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Label"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path"},"config":{"anyOf":[{"items":{},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","title":"RailPatchIn"},"RefreshRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"}},"type":"object","required":["refresh_token"],"title":"RefreshRequest","description":"A refresh token for POST /v1/auth/refresh."},"RowCreateIn":{"properties":{"data":{"additionalProperties":true,"type":"object","title":"Data"}},"additionalProperties":false,"type":"object","required":["data"],"title":"RowCreateIn","description":"The row payload. `organization_id` is NEVER accepted from the client — the service\nderives it from the parent feature, so a caller cannot mislabel a row's tenancy."},"RowOut":{"properties":{"id":{"type":"string","title":"Id"},"feature_id":{"type":"string","title":"Feature Id"},"organization_id":{"type":"string","title":"Organization Id"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"items":{},"type":"array"},{"type":"null"}],"title":"Data"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"}},"type":"object","required":["id","feature_id","organization_id"],"title":"RowOut","description":"A feature row. `data` is a JSON object keyed by column name."},"RowUpdateIn":{"properties":{"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["data"],"title":"RowUpdateIn"},"RowsBatchIn":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/RowCreateIn"},"type":"array","maxItems":500,"minItems":1,"title":"Rows"}},"additionalProperties":false,"type":"object","required":["rows"],"title":"RowsBatchIn","description":"Bulk create: up to 500 rows, inserted ATOMICALLY (one PostgREST call — any rejected\nrow rolls back the whole batch)."},"RowsBatchOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RowOut"},"type":"array","title":"Items"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["items","count"],"title":"RowsBatchOut"},"SchemaCounts":{"properties":{"workspaces":{"type":"integer","title":"Workspaces"},"modules":{"type":"integer","title":"Modules"},"rails":{"type":"integer","title":"Rails"},"features":{"type":"integer","title":"Features"},"columns":{"type":"integer","title":"Columns"},"views":{"type":"integer","title":"Views"}},"type":"object","required":["workspaces","modules","rails","features","columns","views"],"title":"SchemaCounts"},"SchemaFeatureOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"workspace_id":{"type":"string","title":"Workspace Id"},"organization_id":{"type":"string","title":"Organization Id"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"columns":{"items":{"$ref":"#/components/schemas/FeatureColumnOut"},"type":"array","title":"Columns"},"views":{"items":{"$ref":"#/components/schemas/ViewOut"},"type":"array","title":"Views"}},"type":"object","required":["id","name","workspace_id","organization_id","columns","views"],"title":"SchemaFeatureOut"},"SchemaModuleOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"workspace_id":{"type":"string","title":"Workspace Id"},"default_rail_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Rail Id"},"rails":{"items":{"$ref":"#/components/schemas/RailOut"},"type":"array","title":"Rails"}},"type":"object","required":["id","name","label","workspace_id","rails"],"title":"SchemaModuleOut"},"SchemaOut":{"properties":{"workspaces":{"items":{"$ref":"#/components/schemas/SchemaWorkspaceOut"},"type":"array","title":"Workspaces"},"counts":{"$ref":"#/components/schemas/SchemaCounts"},"truncated":{"type":"boolean","title":"Truncated"}},"type":"object","required":["workspaces","counts","truncated"],"title":"SchemaOut","description":"The caller's entire visible model in one response. `truncated: true` means a size\ncap was hit — narrow the request with `workspace_id`."},"SchemaWorkspaceOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"organization_id":{"type":"string","title":"Organization Id"},"default_module_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Module Id"},"modules":{"items":{"$ref":"#/components/schemas/SchemaModuleOut"},"type":"array","title":"Modules"},"features":{"items":{"$ref":"#/components/schemas/SchemaFeatureOut"},"type":"array","title":"Features"}},"type":"object","required":["id","name","organization_id","modules","features"],"title":"SchemaWorkspaceOut"},"ServiceDefaultsOut":{"properties":{"defaults":{"additionalProperties":{"type":"string"},"type":"object","title":"Defaults"}},"type":"object","title":"ServiceDefaultsOut","description":"Global service-default mode->model_key map from the DB (editable by superadmin).\n\nShape: {\"defaults\": {\"query\": \"fast-model\", \"author\": \"smart-model\", ...}}\nValues are catalog model keys (AiModelCatalog.key), not vertente buckets."},"SetProviderSecretIn":{"properties":{"secret":{"type":"string","minLength":1,"title":"Secret"}},"type":"object","required":["secret"],"title":"SetProviderSecretIn","description":"Set/replace the org's BYOK key for a provider. Empty is rejected (422)."},"SetSettingsIn":{"properties":{"organization_id":{"type":"string","format":"uuid","title":"Organization Id"},"mode_models":{"additionalProperties":{"type":"string"},"type":"object","title":"Mode Models"}},"type":"object","required":["organization_id","mode_models"],"title":"SetSettingsIn"},"SettingsOut":{"properties":{"organization_id":{"type":"string","title":"Organization Id"},"mode_models":{"additionalProperties":{"type":"string"},"type":"object","title":"Mode Models"}},"type":"object","required":["organization_id"],"title":"SettingsOut"},"SignedUrlOut":{"properties":{"url":{"type":"string","title":"Url"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["url","expires_in"],"title":"SignedUrlOut"},"TemplateApplyIn":{"properties":{"workspace_id":{"type":"string","format":"uuid","title":"Workspace Id"},"include_rows":{"type":"boolean","title":"Include Rows","default":true},"resolutions":{"additionalProperties":true,"type":"object","title":"Resolutions","description":"What to do with each dependency the package names, one entry per ref. `{\"action\": \"import\"}` brings the dependency in from the package's own blueprint; `{\"action\": \"map\", \"id\": \"<feature id>\", \"columns\": {\"<column ref>\": \"<column id>\"}}` points it at a table that already exists in the destination organization, with the columns matched one by one; `{\"action\": \"skip\"}` clears every reference to it and reports each cleared field with reason `dependencia_saltada` — nothing is left pointing at an id from the source organization. Mapped ids have to belong to the destination organization: one that does not is refused, naming the ref."},"rows_modes":{"additionalProperties":{"type":"string"},"type":"object","title":"Rows Modes","description":"What to do with the rows of each table the package carries, one entry per feature ref. `all` inserts every row and is the default for a ref that is not listed; `missingByKey` inserts only the rows whose key is not in the destination yet, and needs the package to carry a VERIFIED key for that table (`keyColumn` with `keyUnique: true`) — asking for it without one is refused, naming the ref; `none` skips that table's rows entirely. `include_rows: false` is the global switch and always wins: with it, no mode is read at all. The report of the job counts the rows left out by `missingByKey` under `existing`."},"job_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Job Id","description":"Resume this job instead of starting a new one. Omit it and the server decides: it resumes the last unfinished job for this template and workspace, and starts a fresh one when the last apply finished. `include_rows`, `resolutions` and `rows_modes` are ignored when resuming — the job carries the plan it was created with."}},"type":"object","required":["workspace_id"],"title":"TemplateApplyIn","description":"O pedido de aplicação. O template vem do caminho e a organização do workspace de\ndestino: aceitá-la no corpo era medir a pertença contra a organização errada."},"TemplateApplyOut":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"}},"type":"object","required":["job_id","status"],"title":"TemplateApplyOut","description":"O job acabado de arrancar. O relatório lê-se em `GET /v1/template-jobs/{id}`."},"TemplateApplyProblem":{"properties":{"type":{"type":"string","title":"Type","default":"about:blank"},"title":{"type":"string","title":"Title"},"status":{"type":"integer","title":"Status"},"detail":{"$ref":"#/components/schemas/TemplateApplyProblemDetail"},"instance":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instance"}},"type":"object","required":["title","status","detail"],"title":"TemplateApplyProblem","description":"O 422 de `apply_template`/`suggest_template_mappings`: o mesmo envelope RFC 7807\ndas outras rotas, com o `detail` estruturado."},"TemplateApplyProblemDetail":{"properties":{"code":{"type":"string","title":"Code","description":"What the executor refused, as a stable name to branch on: `mapping_feature_not_found` (the mapped table is not in the destination workspace, nor shared in its organization), `mapping_column_not_found` (the mapped column is not in the table that ref maps to), `mapping_columns_incomplete` (a mapped dependency's identity column is still unmapped and the plan uses it), `rows_mode_without_key` (`missingByKey` for a table whose key the package did not verify), `dependency_requires_mapping` (a dependency with no blueprint that `resolutions` does not answer), `dependency_has_no_blueprint` (resolved as `import` and the package carries no blueprint for it), `ledger_without_row_ids` (the job did not record the rows it inserted, so they cannot be removed or assigned)."},"ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref","description":"The `$ref` the refusal is about — a dependency ref, a column ref, or a feature ref, depending on `code`. This is the key to fix in `resolutions` or `rows_modes`."},"message":{"type":"string","title":"Message","description":"What the caller can do about it, in English."}},"type":"object","required":["code","message"],"title":"TemplateApplyProblemDetail","description":"O `detail` de uma recusa nomeada do executor. Objeto e não frase: o `ref` diz\nqual é o campo a corrigir, e uma frase só obrigava quem chama a lê-la à procura\ndele."},"TemplateDetailOut":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id"},"scope":{"type":"string","title":"Scope"},"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","default":[]},"semver":{"type":"string","title":"Semver"},"visibility":{"type":"string","title":"Visibility"},"source_workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Workspace Id"},"published_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published By"},"published_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published At"},"manifest":{"additionalProperties":true,"type":"object","title":"Manifest","default":{}}},"type":"object","required":["id","scope","name","label","semver","visibility"],"title":"TemplateDetailOut","description":"A linha mais o pacote (`manifest`), só em `GET /v1/templates/{id}`."},"TemplateJobOut":{"properties":{"id":{"type":"string","title":"Id"},"template_id":{"type":"string","title":"Template Id"},"workspace_id":{"type":"string","title":"Workspace Id"},"status":{"type":"string","title":"Status"},"include_rows":{"type":"boolean","title":"Include Rows"},"report":{"additionalProperties":true,"type":"object","title":"Report","description":"What the job did, rebuilt at every commit. `structure` counts the plan steps applied and skipped; `created` counts the entities by kind; `rows` counts, per feature ref, `inserted`, `updated`, `skipped` (what an earlier pass had already written) and `existing` (what `missingByKey` filtered out); `unresolved` lists what was left pointing nowhere, one entry per field, all in ONE shape: `uid` (the item or row entry it belongs to), `path` (e.g. `rows[].data.<column>`), `kind`, `form`, `reason`, `action`, and optionally `count` (how many rows the entry covers), `ref` (the dependency that emptied it) and `required: true` (that dependency was required). Only complete once `status` is `done`."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"locked_until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locked Until","description":"Until when the executor that holds this job keeps it. It is renewed while the job advances, so a value in the future means someone is working on it right now; a value in the past, or none at all, means the job is free to be resumed (`POST /v1/templates/{id}/apply` with `job_id`). Resuming one that is still held is refused with 409."}},"type":"object","required":["id","template_id","workspace_id","status","include_rows"],"title":"TemplateJobOut","description":"O estado de um job. O `plan_snapshot`, o `uid_map` e o `ledger` NÃO saem na API: são\nestado interno do executor e o snapshot de um ERP é grande — a mesma razão por que o\n`manifest` não entra na lista do catálogo."},"TemplateMappingSuggestionsIn":{"properties":{"workspace_id":{"type":"string","format":"uuid","title":"Workspace Id"}},"type":"object","required":["workspace_id"],"title":"TemplateMappingSuggestionsIn","description":"O destino sobre que se pergunta. Só o workspace: a organização deriva-se dele, como\nno apply, e as sugestões não escrevem nada."},"TemplateMappingSuggestionsOut":{"properties":{"suggestions":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Suggestions","default":[]}},"type":"object","title":"TemplateMappingSuggestionsOut","description":"As sugestões tal como o executor as devolve, uma por dependência. Os objetos não se\ntipam aqui: quem os escreve é a `DependencyMatcher` da `outdo_library`, e um segundo\nmodelo em Python divergia dela à primeira regra nova — a mesma razão do `apply_client`.\n\nPor sugestão: `ref`, `kind`, `form`, `required`, `proposed` (`map`, `import` ou\n`decide`) e `candidates`, cada candidata com `id`, `name`, `label`, `confidence` (0 a 1)\ne as `columns` que casaram."},"TemplateOut":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id"},"scope":{"type":"string","title":"Scope"},"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","default":[]},"semver":{"type":"string","title":"Semver"},"visibility":{"type":"string","title":"Visibility"},"source_workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Workspace Id"},"published_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published By"},"published_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published At"}},"type":"object","required":["id","scope","name","label","semver","visibility"],"title":"TemplateOut","description":"Uma linha do catálogo, sem o pacote."},"TemplateSampleRowsOut":{"properties":{"job_id":{"type":"string","title":"Job Id"},"total":{"type":"integer","title":"Total"},"tables":{"items":{"$ref":"#/components/schemas/TemplateSampleRowsTable"},"type":"array","title":"Tables","default":[]}},"type":"object","required":["job_id","total"],"title":"TemplateSampleRowsOut","description":"O resultado de remover ou atribuir as linhas de exemplo de um job: o total e o\ndetalhe por tabela. A estrutura que o pacote criou não entra aqui — não se toca nela."},"TemplateSampleRowsTable":{"properties":{"feature_id":{"type":"string","title":"Feature Id"},"label":{"type":"string","title":"Label"},"columns":{"items":{"type":"string"},"type":"array","title":"Columns","default":[]},"rows":{"type":"integer","title":"Rows"}},"type":"object","required":["feature_id","label","rows"],"title":"TemplateSampleRowsTable","description":"O que uma ação de pós-aplicação fez numa tabela. O `label` vem da base: quem chama\npode não ter o pacote em mão. As `columns` são as que a atribuição preencheu, e ficam\nvazias na remoção, que não toca em coluna nenhuma."},"TokenRequest":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"TokenRequest","description":"Credentials for POST /v1/auth/token (delegated to Supabase GoTrue).\n\n`email` is a plain str (no EmailStr) — GoTrue is the credential authority; the API does\nnot pre-validate the address shape, it simply forwards. A missing field still → 422."},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["access_token","refresh_token","expires_in"],"title":"TokenResponse","description":"The Supabase session triple returned on a clean login/refresh."},"UsageSummaryOut":{"properties":{"organization_id":{"type":"string","title":"Organization Id"},"rows":{"items":{"$ref":"#/components/schemas/UsageSummaryRow"},"type":"array","title":"Rows"}},"type":"object","required":["organization_id","rows"],"title":"UsageSummaryOut","description":"Response body for GET /v1/ai/usage-summary."},"UsageSummaryRow":{"properties":{"mode":{"type":"string","title":"Mode"},"model_key":{"type":"string","title":"Model Key"},"unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit"},"total_quantity":{"type":"number","title":"Total Quantity"},"event_count":{"type":"integer","title":"Event Count"}},"type":"object","required":["mode","model_key","total_quantity","event_count"],"title":"UsageSummaryRow","description":"One aggregated row returned by `org_ai_usage_summary`."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ValidationOut":{"properties":{"ok":{"type":"boolean","title":"Ok"},"issues":{"items":{"$ref":"#/components/schemas/GraphIssueOut"},"type":"array","title":"Issues"}},"type":"object","required":["ok"],"title":"ValidationOut","description":"The verdict. `ok` is the absence of ERRORS: a warning never blocks anything."},"ViewCreateIn":{"properties":{"feature_id":{"type":"string","title":"Feature Id"},"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"label":{"type":"string","maxLength":120,"minLength":1,"title":"Label"},"type":{"type":"string","title":"Type"},"config":{"anyOf":[{"items":{},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","required":["feature_id","name","label","type"],"title":"ViewCreateIn","description":"`organization_id` is NEVER accepted — derived from the parent feature. `type` is\nvalidated against the view registry (see GET /v1/schema/definitions)."},"ViewOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"type":{"type":"string","title":"Type"},"config":{"anyOf":[{"items":{},"type":"array"},{"additionalProperties":true,"type":"object"}],"title":"Config"},"feature_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feature Id"},"organization_id":{"type":"string","title":"Organization Id"}},"type":"object","required":["id","name","label","type","config","organization_id"],"title":"ViewOut","description":"A saved view over a feature (table, kanban, form, …)."},"ViewPatchIn":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"label":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Label"},"config":{"anyOf":[{"items":{},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","title":"ViewPatchIn"},"WorkflowOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"workspace_id":{"type":"string","title":"Workspace Id"},"organization_id":{"type":"string","title":"Organization Id"},"is_active":{"type":"boolean","title":"Is Active"},"trigger_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"items":{},"type":"array"},{"type":"null"}],"title":"Trigger Config"}},"type":"object","required":["id","name","workspace_id","organization_id","is_active"],"title":"WorkflowOut","description":"A workflow definition. Triggerable via the API when active."},"WorkflowRunOut":{"properties":{"id":{"type":"string","title":"Id"},"workflow_id":{"type":"string","title":"Workflow Id"},"workspace_id":{"type":"string","title":"Workspace Id"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"triggered_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Triggered By"},"trigger_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trigger Type"},"input_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"items":{},"type":"array"},{"type":"null"}],"title":"Input Data"},"output_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"items":{},"type":"array"},{"type":"null"}],"title":"Output Data"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"steps_executed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Steps Executed"},"total_steps":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Steps"},"is_dry_run":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Dry Run"}},"type":"object","required":["id","workflow_id","workspace_id"],"title":"WorkflowRunOut","description":"One execution of a workflow (a `WorkflowLog` row). Poll until `status` reaches\n`success`, `failed`, or `cancelled`."},"WorkflowTriggerIn":{"properties":{"input_data":{"additionalProperties":true,"type":"object","title":"Input Data"},"dry_run":{"type":"boolean","title":"Dry Run","default":false}},"type":"object","title":"WorkflowTriggerIn","description":"Trigger payload: optional input context handed to the run."},"WorkflowV2CreateIn":{"properties":{"workspace_id":{"type":"string","format":"uuid","title":"Workspace Id"},"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"label":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Label"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"trigger_config":{"additionalProperties":true,"type":"object","title":"Trigger Config","description":"What fires the automation: `{ \"type\": <trigger key>, \"config\": {…} }`. `type` is a trigger key from the catalog (`record.event`, `cron`, …) and `config` is that trigger's own configuration, which each trigger validates. See [the trigger catalog](https://docs.out-do.app/reference/gatilhos/)."},"graph_config":{"additionalProperties":true,"type":"object","title":"Graph Config","description":"What happens next: `{ \"steps\": [ { \"id\"?: \"stp_…\", \"key\": <step key>, \"config\": {…} } ] }`. There are no nodes, edges or coordinates: the list order IS the chain. A step that branches keeps its branches INSIDE its own config, as lists of steps with the same shape (`then` and `else` on `ifElse`, the routes on `switch`). `id` is optional (`stp_` plus 12 alphanumerics) but must be unique, and it is what `{{vars.steps.<id>.<output>}}` refers to. See [the step catalog](https://docs.out-do.app/reference/passos/)."},"is_draft":{"type":"boolean","title":"Is Draft","description":"Create as a draft, which never fires, and publish it later with `POST /v1/workflows-v2/{workflow_id}/apply`. Pass `false` to create AND apply in one call; either way the result is INACTIVE — a person activates the automation in the app.","default":true}},"type":"object","required":["workspace_id","name"],"title":"WorkflowV2CreateIn","description":"A new automation. `organization_id` and `workflow_id` are not accepted: the first is\nderived from the validated workspace, the second is minted server-side. The definition is\nvalidated before anything is written. Send an `Idempotency-Key` header to make retries\nsafe.","examples":[{"graph_config":{"steps":[{"config":{"condition":{"op":"eq","subject":"var","value":"Validado","var":"{{vars.trigger.estado}}"},"else":[],"then":[{"config":{"bindings":[{"targetColumn":"notas","type":"text","value":"Validado em {{sys.today}}"}],"mode":"updateTrigger","targetFeatureId":"d174bd26-c7f0-46ed-a2e3-7bad133b28ce"},"id":"stp_escrever0001","key":"feature.write"}]},"id":"stp_ifestado0001","key":"ifElse"}]},"name":"Note validated purchases","trigger_config":{"config":{"featureId":"d174bd26-c7f0-46ed-a2e3-7bad133b28ce","modes":["updated"],"watchedColumns":["estado"]},"type":"record.event"},"workspace_id":"a5a50556-ef07-449d-a556-077a2a310461"},{"graph_config":{"steps":[{"config":{"featureId":"d174bd26-c7f0-46ed-a2e3-7bad133b28ce","filterGroup":{"combinator":"and","rules":[{"columnName":"estado","id":"r1","operator":"equals","value":"Aguarda revisão"}]},"limit":100,"mergeMode":"replace","orderBy":{"column":"vencimento","desc":false}},"id":"stp_lerfaturas01","key":"feature.read"},{"config":{"attachFiles":false,"body":"<p>A mais antiga é a {{record.numero}}, de {{record.fornecedor_nome}}, no valor de {{record.total}}.</p>","subject":"Faturas por rever em {{sys.today}}","to":"compras@exemplo.pt"},"id":"stp_enviaremail1","key":"email.send"}]},"name":"Daily invoice digest","trigger_config":{"config":{"atTime":"08:00","mode":"daily","timezone":"Europe/Lisbon"},"type":"cron"},"workspace_id":"a5a50556-ef07-449d-a556-077a2a310461"}]},"WorkflowV2Out":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"},"workflow_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Id"},"version":{"type":"integer","title":"Version","default":1},"is_active":{"type":"boolean","title":"Is Active","default":false},"is_draft":{"type":"boolean","title":"Is Draft","description":"An unapplied draft. A draft never fires and can never be active.","default":false},"deactivation_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deactivation Reason"},"name":{"type":"string","title":"Name","default":""},"label":{"type":"string","title":"Label","default":""},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"trigger_config":{"additionalProperties":true,"type":"object","title":"Trigger Config","description":"What fires the automation: `{ \"type\": <trigger key>, \"config\": {…} }`. `type` is a trigger key from the catalog (`record.event`, `cron`, …) and `config` is that trigger's own configuration, which each trigger validates. See [the trigger catalog](https://docs.out-do.app/reference/gatilhos/)."},"graph_config":{"additionalProperties":true,"type":"object","title":"Graph Config","description":"What happens next: `{ \"steps\": [ { \"id\"?: \"stp_…\", \"key\": <step key>, \"config\": {…} } ] }`. There are no nodes, edges or coordinates: the list order IS the chain. A step that branches keeps its branches INSIDE its own config, as lists of steps with the same shape (`then` and `else` on `ifElse`, the routes on `switch`). `id` is optional (`stp_` plus 12 alphanumerics) but must be unique, and it is what `{{vars.steps.<id>.<output>}}` refers to. See [the step catalog](https://docs.out-do.app/reference/passos/)."},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"archived_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Archived At"}},"type":"object","required":["id"],"title":"WorkflowV2Out","description":"One VERSION of an automation. Address the automation by `workflow_id`, which groups\nthe versions, and not by `id`, which identifies this snapshot alone."},"WorkflowV2ValidateIn":{"properties":{"trigger_config":{"additionalProperties":true,"type":"object","title":"Trigger Config","description":"What fires the automation: `{ \"type\": <trigger key>, \"config\": {…} }`. `type` is a trigger key from the catalog (`record.event`, `cron`, …) and `config` is that trigger's own configuration, which each trigger validates. See [the trigger catalog](https://docs.out-do.app/reference/gatilhos/)."},"graph_config":{"additionalProperties":true,"type":"object","title":"Graph Config","description":"What happens next: `{ \"steps\": [ { \"id\"?: \"stp_…\", \"key\": <step key>, \"config\": {…} } ] }`. There are no nodes, edges or coordinates: the list order IS the chain. A step that branches keeps its branches INSIDE its own config, as lists of steps with the same shape (`then` and `else` on `ifElse`, the routes on `switch`). `id` is optional (`stp_` plus 12 alphanumerics) but must be unique, and it is what `{{vars.steps.<id>.<output>}}` refers to. See [the step catalog](https://docs.out-do.app/reference/passos/)."},"workspace_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Workspace Id"},"workflow_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Workflow Id","description":"The workflow this graph belongs to, so self-recursion is detected."}},"type":"object","title":"WorkflowV2ValidateIn","description":"A definition to validate without writing anything. `workspace_id` derives the\norganization the validator authenticates against; without it, the organization of the\ncaller's API key is used.","examples":[{"graph_config":{"steps":[{"config":{"condition":{"op":"eq","subject":"var","value":"Validado","var":"{{vars.trigger.estado}}"},"else":[],"then":[{"config":{"bindings":[{"targetColumn":"notas","type":"text","value":"Validado em {{sys.today}}"}],"mode":"updateTrigger","targetFeatureId":"d174bd26-c7f0-46ed-a2e3-7bad133b28ce"},"id":"stp_escrever0001","key":"feature.write"}]},"id":"stp_ifestado0001","key":"ifElse"}]},"trigger_config":{"config":{"featureId":"d174bd26-c7f0-46ed-a2e3-7bad133b28ce","modes":["updated"],"watchedColumns":["estado"]},"type":"record.event"},"workspace_id":"a5a50556-ef07-449d-a556-077a2a310461"},{"graph_config":{"steps":[{"config":{"featureId":"d174bd26-c7f0-46ed-a2e3-7bad133b28ce","filterGroup":{"combinator":"and","rules":[{"columnName":"estado","id":"r1","operator":"equals","value":"Aguarda revisão"}]},"limit":100,"mergeMode":"replace","orderBy":{"column":"vencimento","desc":false}},"id":"stp_lerfaturas01","key":"feature.read"},{"config":{"attachFiles":false,"body":"<p>A mais antiga é a {{record.numero}}, de {{record.fornecedor_nome}}, no valor de {{record.total}}.</p>","subject":"Faturas por rever em {{sys.today}}","to":"compras@exemplo.pt"},"id":"stp_enviaremail1","key":"email.send"}]},"trigger_config":{"config":{"atTime":"08:00","mode":"daily","timezone":"Europe/Lisbon"},"type":"cron"},"workspace_id":"a5a50556-ef07-449d-a556-077a2a310461"}]},"WorkflowV2VersionIn":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"label":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Label"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"trigger_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Trigger Config","description":"What fires the automation: `{ \"type\": <trigger key>, \"config\": {…} }`. `type` is a trigger key from the catalog (`record.event`, `cron`, …) and `config` is that trigger's own configuration, which each trigger validates. See [the trigger catalog](https://docs.out-do.app/reference/gatilhos/). Omit to keep the current trigger."},"graph_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Graph Config","description":"What happens next: `{ \"steps\": [ { \"id\"?: \"stp_…\", \"key\": <step key>, \"config\": {…} } ] }`. There are no nodes, edges or coordinates: the list order IS the chain. A step that branches keeps its branches INSIDE its own config, as lists of steps with the same shape (`then` and `else` on `ifElse`, the routes on `switch`). `id` is optional (`stp_` plus 12 alphanumerics) but must be unique, and it is what `{{vars.steps.<id>.<output>}}` refers to. See [the step catalog](https://docs.out-do.app/reference/passos/). Omit to keep the current graph."}},"type":"object","title":"WorkflowV2VersionIn","description":"The next version of a workflow that already has an active one. Whatever you omit keeps\nthe value of the active version.\n\n`trigger_config` and `graph_config` are null by default, and not `{}`: an empty object\ncannot tell \"I did not touch the trigger\" from \"I erased the trigger\", and the validator\nwould answer `trigger_missing` to a call that only wanted to change the label."},"WorkspaceCreateIn":{"properties":{"organization_id":{"type":"string","title":"Organization Id"},"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"label":{"type":"string","maxLength":120,"minLength":1,"title":"Label"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"visibility_policy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visibility Policy"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","required":["organization_id","name","label"],"title":"WorkspaceCreateIn","description":"Payload for creating a workspace. The caller must belong to the organization."},"WorkspaceOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"organization_id":{"type":"string","title":"Organization Id"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"visibility_policy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visibility Policy"},"default_module_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Module Id"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"module_order_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Module Order Config"}},"type":"object","required":["id","name","organization_id"],"title":"WorkspaceOut","description":"A workspace — the top-level container for modules, features, and views."},"WorkspacePatchIn":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"label":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Label"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"visibility_policy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visibility Policy"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"module_order_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Module Order Config"},"default_module_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Module Id"}},"type":"object","title":"WorkspacePatchIn","description":"Partial update — only the provided fields change (exclude_unset semantics)."}},"securitySchemes":{"HTTPBearer":{"type":"http","description":"A Supabase user JWT (from POST /v1/auth/token) OR a scoped API key (outdo_sk_…).","scheme":"bearer","bearerFormat":"JWT"}}},"tags":[{"name":"health","description":"Service liveness."},{"name":"auth","description":"Sign-in and token refresh, delegated to the platform's identity provider."},{"name":"organizations","description":"Organizations the caller belongs to."},{"name":"workspaces","description":"Workspaces — the top-level containers of the model."},{"name":"modules","description":"Navigation modules inside a workspace."},{"name":"rails","description":"Navigation rails inside a module; a rail's config lists the views it shows."},{"name":"features","description":"Features (data tables) and their column definitions."},{"name":"rows","description":"Row data: CRUD, bulk insert, filtering, sorting, and aggregates."},{"name":"views","description":"Saved views over a feature (table, kanban, form, …)."},{"name":"schema","description":"The caller's whole visible model in one call, plus config definitions."},{"name":"files","description":"File uploads and signed downloads for file columns."},{"name":"workflows","description":"Workflow definitions, triggering, and run status."},{"name":"messages","description":"Messages posted to a scope (channel, feature, row, workspace, or organization)."},{"name":"api-keys","description":"Scoped API keys (machine identities) — create, list, and revoke."},{"name":"workflows-v2","description":"WorkflowV2 automations: list and read definitions, validate a graph, create a workflow as a draft, add a version, and apply a draft. Writes need the `structure` scope. Activation is never an API call — a person turns the automation on in the app. The envelope of a definition and the full vocabulary are documented at [docs.out-do.app/reference/workflows-v2](https://docs.out-do.app/reference/workflows-v2)."},{"name":"templates","description":"The template catalog (system, public, and the caller's own organization). Read-only here: publishing and promotion are human actions in the app."},{"name":"external","description":"The client-portal gateway: what an external (client) account may read."},{"name":"external-accounts","description":"Invitations and lifecycle of external (client-portal) accounts."},{"name":"ai","description":"AI control plane: per-organization model configuration, credit balance, usage records, and provider secrets. Not an inference endpoint."},{"name":"admin","description":"Operational endpoints for platform administrators, outside the tenant surface."}]}