components:
  schemas:
    AdminStatsOut:
      description: >-
        Service facts for the console — deliberately free of org data and
        secrets.
      properties:
        service:
          default: outdo_api_data
          title: Service
          type: string
        started_at:
          title: Started At
          type: string
        uptime_s:
          title: Uptime S
          type: integer
        version:
          title: Version
          type: string
      required:
        - version
        - uptime_s
        - started_at
      title: AdminStatsOut
      type: object
    AggregateOut:
      description: >-
        Result of rows:aggregate. `value` is null for avg over zero numeric
        values;

        `rows_considered` is the number of values that entered the computation
        (for count,

        the count itself).
      properties:
        column:
          anyOf:
            - type: string
            - type: 'null'
          title: Column
        op:
          title: Op
          type: string
        rows_considered:
          title: Rows Considered
          type: integer
        value:
          anyOf:
            - type: number
            - type: 'null'
          title: Value
      required:
        - op
        - rows_considered
      title: AggregateOut
      type: object
    ApiKeyCreateIn:
      properties:
        expires_at:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Absolute expiry as ISO-8601 UTC. Mutually exclusive with
            expires_in_days.
          title: Expires At
        expires_in_days:
          anyOf:
            - maximum: 3650
              minimum: 1
              type: integer
            - type: 'null'
          description: >-
            Relative expiry in days from creation. Mutually exclusive with
            expires_at.
          title: Expires In Days
        name:
          maxLength: 120
          minLength: 1
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        role:
          default: user
          enum:
            - admin
            - dev
            - user
            - view
          title: Role
          type: string
        scopes:
          items:
            enum:
              - read
              - write
              - structure
            type: string
          minItems: 1
          title: Scopes
          type: array
        workspace_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Workspace Id
      required:
        - organization_id
        - name
      title: ApiKeyCreateIn
      type: object
    ApiKeyCreatedOut:
      description: >-
        The creation response — carries the one-time plaintext `secret`. Store
        it now; it

        cannot be retrieved again.
      properties:
        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
        id:
          title: Id
          type: string
        key_prefix:
          title: Key Prefix
          type: string
        last_used_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Used At
        name:
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        revoked_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Revoked At
        role:
          title: Role
          type: string
        scopes:
          items:
            type: string
          title: Scopes
          type: array
        secret:
          title: Secret
          type: string
        workspace_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Workspace Id
      required:
        - id
        - organization_id
        - name
        - key_prefix
        - role
        - scopes
        - secret
      title: ApiKeyCreatedOut
      type: object
    ApiKeyOut:
      description: >-
        A key's metadata. The secret is NEVER part of this — see
        ApiKeyCreatedOut.
      properties:
        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
        id:
          title: Id
          type: string
        key_prefix:
          title: Key Prefix
          type: string
        last_used_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Used At
        name:
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        revoked_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Revoked At
        role:
          title: Role
          type: string
        scopes:
          items:
            type: string
          title: Scopes
          type: array
        workspace_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Workspace Id
      required:
        - id
        - organization_id
        - name
        - key_prefix
        - role
        - scopes
      title: ApiKeyOut
      type: object
    Body_upload_file:
      properties:
        column_id:
          format: uuid
          title: Column Id
          type: string
        feature_id:
          format: uuid
          title: Feature Id
          type: string
        file:
          contentMediaType: application/octet-stream
          title: File
          type: string
      required:
        - feature_id
        - column_id
        - file
      title: Body_upload_file
      type: object
    CatalogModelOut:
      properties:
        allowed_tier_names:
          anyOf:
            - items:
                type: string
              type: array
            - type: 'null'
          title: Allowed Tier Names
        api_type:
          title: Api Type
          type: string
        base_url:
          anyOf:
            - type: string
            - type: 'null'
          title: Base Url
        cache_write_price_eur:
          anyOf:
            - type: number
            - type: 'null'
          title: Cache Write Price Eur
        cached_input_price_eur:
          anyOf:
            - type: number
            - type: 'null'
          title: Cached Input Price Eur
        capability:
          title: Capability
          type: string
        credit_multiplier:
          title: Credit Multiplier
          type: number
        input_price_eur:
          anyOf:
            - type: number
            - type: 'null'
          title: Input Price Eur
        is_active:
          title: Is Active
          type: boolean
        key:
          title: Key
          type: string
        label:
          title: Label
          type: string
        mask_sensitive:
          title: Mask Sensitive
          type: boolean
        model:
          title: Model
          type: string
        output_price_eur:
          anyOf:
            - type: number
            - type: 'null'
          title: Output Price Eur
        provider_secret:
          anyOf:
            - type: string
            - type: 'null'
          title: Provider Secret
        sort_order:
          title: Sort Order
          type: integer
        unit_price_eur:
          anyOf:
            - type: number
            - type: 'null'
          title: Unit Price Eur
      required:
        - key
        - label
        - api_type
        - model
        - mask_sensitive
        - capability
        - credit_multiplier
        - is_active
        - sort_order
      title: CatalogModelOut
      type: object
    ColumnCreateIn:
      description: >-
        A column definition; `type` and `default_component` are validated
        against the

        platform registries (see GET /v1/schema/definitions).
      properties:
        config:
          additionalProperties: true
          title: Config
          type: object
        default_component:
          default: textfield
          title: Default Component
          type: string
        externally_visible:
          default: false
          title: Externally Visible
          type: boolean
        hidden:
          default: false
          title: Hidden
          type: boolean
        initial_value:
          anyOf:
            - type: string
            - type: 'null'
          title: Initial Value
        inline_editable:
          default: false
          title: Inline Editable
          type: boolean
        label:
          maxLength: 120
          minLength: 1
          title: Label
          type: string
        name:
          maxLength: 64
          minLength: 1
          title: Name
          type: string
        order:
          anyOf:
            - type: integer
            - type: 'null'
          title: Order
        read_only:
          default: false
          title: Read Only
          type: boolean
        required:
          default: false
          title: Required
          type: boolean
        type:
          default: text
          title: Type
          type: string
      required:
        - name
        - label
      title: ColumnCreateIn
      type: object
    ColumnPatchIn:
      properties:
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        default_component:
          anyOf:
            - type: string
            - type: 'null'
          title: Default Component
        externally_visible:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Externally Visible
        hidden:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Hidden
        initial_value:
          anyOf:
            - type: string
            - type: 'null'
          title: Initial Value
        inline_editable:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Inline Editable
        label:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Label
        name:
          anyOf:
            - maxLength: 64
              minLength: 1
              type: string
            - type: 'null'
          title: Name
        order:
          anyOf:
            - type: integer
            - type: 'null'
          title: Order
        read_only:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Read Only
        required:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Required
        type:
          anyOf:
            - type: string
            - type: 'null'
          title: Type
      title: ColumnPatchIn
      type: object
    CreditBalanceOut:
      properties:
        allowance_remaining:
          title: Allowance Remaining
          type: integer
        organization_id:
          title: Organization Id
          type: string
        period_start:
          title: Period Start
          type: string
        tier_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Tier Name
        topup_balance:
          title: Topup Balance
          type: integer
      required:
        - organization_id
        - allowance_remaining
        - topup_balance
        - period_start
      title: CreditBalanceOut
      type: object
    ExternalAccountInviteIn:
      description: >-
        `POST /v1/external-accounts/invite` — who to invite, and to which
        organization.
      properties:
        email:
          maxLength: 320
          minLength: 3
          title: Email
          type: string
        name:
          anyOf:
            - maxLength: 200
              type: string
            - type: 'null'
          title: Name
        organization_id:
          minLength: 1
          title: Organization Id
          type: string
      required:
        - organization_id
        - email
      title: ExternalAccountInviteIn
      type: object
    ExternalAccountInviteOut:
      description: >-
        The invite response: the account, plus what this call did to it.


        `outcome` is here because the caller cannot infer it. The same 200 with

        `status='invited'` comes back from a fresh provisioning, from a
        reactivation and from a

        plain re-send, so a client can only guess — from the status the row had
        BEFORE, which it

        may not have, and which does not say whether a mail went out. Additive:
        every inherited

        field keeps its meaning, so a client that ignores `outcome` is
        unaffected.
      properties:
        created_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Created At
        email:
          title: Email
          type: string
        id:
          title: Id
          type: string
        invited_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Invited At
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
        organization_id:
          title: Organization Id
          type: string
        outcome:
          enum:
            - created
            - access_link_sent
            - reactivated
            - invite_resent
            - no_action
          title: Outcome
          type: string
        status:
          title: Status
          type: string
        updated_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Updated At
        user_id:
          anyOf:
            - type: string
            - type: 'null'
          title: User Id
      required:
        - id
        - organization_id
        - email
        - status
        - outcome
      title: ExternalAccountInviteOut
      type: object
    ExternalAccountOut:
      description: >-
        A client account. `user_id` is the shared GoTrue identity — the same
        person can hold an

        OutDo membership and any number of external accounts, so it is NOT an
        "is external" flag.
      properties:
        created_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Created At
        email:
          title: Email
          type: string
        id:
          title: Id
          type: string
        invited_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Invited At
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
        organization_id:
          title: Organization Id
          type: string
        status:
          title: Status
          type: string
        updated_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Updated At
        user_id:
          anyOf:
            - type: string
            - type: 'null'
          title: User Id
      required:
        - id
        - organization_id
        - email
        - status
      title: ExternalAccountOut
      type: object
    ExternalColumnOut:
      description: >-
        One column the organization switched on for clients
        (`externally_visible`). It is a

        RENDERING hint: `type` and `default_component` tell the portal how to
        draw the value it

        already has, and nothing here is ever consulted to decide access.


        The `external_user` column itself is never in this list, however it is
        configured — see

        `read_service.publication`.
      properties:
        default_component:
          anyOf:
            - type: string
            - type: 'null'
          title: Default Component
        id:
          title: Id
          type: string
        label:
          anyOf:
            - type: string
            - type: 'null'
          title: Label
        name:
          title: Name
          type: string
        type:
          anyOf:
            - type: string
            - type: 'null'
          title: Type
      required:
        - id
        - name
      title: ExternalColumnOut
      type: object
    ExternalFeatureOut:
      description: >-
        A published feature: one that carries an `external_user` column (spec
        §3.1).


        `columns` MAY be empty — published with nothing switched on is a real
        state (the rows are

        marked, the organization has not yet decided what to show), and the
        portal decides what to

        do with it. Answering 404 for it would say "no such feature" about a
        feature that exists.
      properties:
        columns:
          items:
            $ref: '#/components/schemas/ExternalColumnOut'
          title: Columns
          type: array
        id:
          title: Id
          type: string
        label:
          anyOf:
            - type: string
            - type: 'null'
          title: Label
      required:
        - id
        - columns
      title: ExternalFeatureOut
      type: object
    ExternalFeaturesOut:
      description: >-
        The published features of ONE organization — the one the account
        resolved to.
      properties:
        items:
          items:
            $ref: '#/components/schemas/ExternalFeatureOut'
          title: Items
          type: array
      required:
        - items
      title: ExternalFeaturesOut
      type: object
    ExternalMeAccount:
      description: >-
        One account = one organization. The same person may hold several (the
        Ofitech case: one

        portal, many workshops), which is why this is a list and not an object.
      properties:
        account_id:
          title: Account Id
          type: string
        email:
          title: Email
          type: string
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
        organization_id:
          title: Organization Id
          type: string
        organization_label:
          anyOf:
            - type: string
            - type: 'null'
          title: Organization Label
        status:
          title: Status
          type: string
      required:
        - account_id
        - organization_id
        - email
        - status
      title: ExternalMeAccount
      type: object
    ExternalMeOut:
      description: >-
        The caller's client accounts. `accounts` is never empty — no active
        account is a 403,

        not an empty list, so the portal cannot render a logged-in shell for
        someone with no access.
      properties:
        accounts:
          items:
            $ref: '#/components/schemas/ExternalMeAccount'
          title: Accounts
          type: array
        user_id:
          title: User Id
          type: string
      required:
        - user_id
        - accounts
      title: ExternalMeOut
      type: object
    ExternalRowOut:
      description: >-
        A row the caller is marked on, projected to the visible columns.


        `data` is already filtered server-side;
        `created_by`/`updated_by`/`workspace_id` are

        deliberately absent (they are the organization's own people and its
        internal structure).
      properties:
        created_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Created At
        data:
          additionalProperties: true
          title: Data
          type: object
        feature_id:
          title: Feature Id
          type: string
        id:
          title: Id
          type: string
        updated_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Updated At
      required:
        - id
        - feature_id
        - data
      title: ExternalRowOut
      type: object
    FeatureColumnOut:
      description: 'A column definition: how a row-data key is typed, edited, and displayed.'
      properties:
        config:
          additionalProperties: true
          title: Config
          type: object
        default_component:
          title: Default Component
          type: string
        externally_visible:
          default: false
          title: Externally Visible
          type: boolean
        feature_id:
          title: Feature Id
          type: string
        hidden:
          title: Hidden
          type: boolean
        id:
          title: Id
          type: string
        initial_value:
          anyOf:
            - type: string
            - type: 'null'
          title: Initial Value
        inline_editable:
          default: false
          title: Inline Editable
          type: boolean
        label:
          title: Label
          type: string
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
        order:
          anyOf:
            - type: integer
            - type: 'null'
          title: Order
        read_only:
          title: Read Only
          type: boolean
        required:
          title: Required
          type: boolean
        type:
          title: Type
          type: string
      required:
        - id
        - label
        - type
        - config
        - feature_id
        - default_component
        - read_only
        - required
        - hidden
      title: FeatureColumnOut
      type: object
    FeatureCreateIn:
      description: '`organization_id` is NEVER accepted — derived from the parent workspace.'
      properties:
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        label:
          anyOf:
            - maxLength: 120
              type: string
            - type: 'null'
          title: Label
        name:
          maxLength: 120
          minLength: 1
          title: Name
          type: string
        workspace_id:
          title: Workspace Id
          type: string
      required:
        - workspace_id
        - name
      title: FeatureCreateIn
      type: object
    FeatureOut:
      description: >-
        A feature — a data table whose rows are described by its column
        definitions.
      properties:
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        id:
          title: Id
          type: string
        label:
          anyOf:
            - type: string
            - type: 'null'
          title: Label
        name:
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        workspace_id:
          title: Workspace Id
          type: string
      required:
        - id
        - name
        - workspace_id
        - organization_id
      title: FeatureOut
      type: object
    FeaturePatchIn:
      properties:
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        label:
          anyOf:
            - maxLength: 120
              type: string
            - type: 'null'
          title: Label
        name:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Name
      title: FeaturePatchIn
      type: object
    FileOut:
      properties:
        fileName:
          title: Filename
          type: string
        fileSize:
          anyOf:
            - type: integer
            - type: 'null'
          title: Filesize
        mimeType:
          anyOf:
            - type: string
            - type: 'null'
          title: Mimetype
        path:
          title: Path
          type: string
        syncStatus:
          default: synced
          title: Syncstatus
          type: string
      required:
        - path
        - fileName
      title: FileOut
      type: object
    GraphIssueOut:
      description: >-
        A problem with the definition, as the validator reports it.


        `code` is the stable identity (snake_case) to branch on; `message` is
        copy and may

        change. `target` says where it is fixed: `trigger` opens the trigger,
        `graph` is located

        by `path` (empty means the whole graph).
      properties:
        code:
          title: Code
          type: string
        field_path:
          anyOf:
            - type: string
            - type: 'null'
          title: Field Path
        message:
          title: Message
          type: string
        path:
          default: ''
          title: Path
          type: string
        severity:
          title: Severity
          type: string
        target:
          default: graph
          title: Target
          type: string
      required:
        - severity
        - message
        - code
      title: GraphIssueOut
      type: object
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          title: Detail
          type: array
      title: HTTPValidationError
      type: object
    HealthOut:
      description: Liveness response.
      properties:
        status:
          default: ok
          title: Status
          type: string
      title: HealthOut
      type: object
    MessageCreateIn:
      description: >-
        `organization_id` (derived from the scope) and `sender_id` (the
        authenticated caller)

        are NEVER accepted from the client — a caller cannot mislabel tenancy or
        spoof authorship.

        `scope_type` and `type` are validated against the `Message` CHECK
        constraints.
      properties:
        content:
          minLength: 1
          title: Content
          type: string
        metadata:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Metadata
        parent_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Parent Id
        scope_id:
          title: Scope Id
          type: string
        scope_type:
          title: Scope Type
          type: string
        type:
          default: chat
          title: Type
          type: string
      required:
        - scope_type
        - scope_id
        - content
      title: MessageCreateIn
      type: object
    MessageOut:
      description: >-
        A message posted to a scope (a channel, feature, feature row, workspace,
        or org).
      properties:
        content:
          title: Content
          type: string
        created_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Created At
        id:
          title: Id
          type: string
        metadata:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Metadata
        organization_id:
          title: Organization Id
          type: string
        parent_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Parent Id
        scope_id:
          title: Scope Id
          type: string
        scope_type:
          title: Scope Type
          type: string
        sender_id:
          title: Sender Id
          type: string
        type:
          title: Type
          type: string
        updated_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Updated At
      required:
        - id
        - organization_id
        - scope_type
        - scope_id
        - sender_id
        - content
        - type
      title: MessageOut
      type: object
    ModuleCreateIn:
      description: '`organization_id` is NEVER accepted — derived from the parent workspace.'
      properties:
        icon:
          anyOf:
            - type: string
            - type: 'null'
          title: Icon
        label:
          maxLength: 120
          minLength: 1
          title: Label
          type: string
        name:
          maxLength: 120
          minLength: 1
          title: Name
          type: string
        workspace_id:
          title: Workspace Id
          type: string
      required:
        - workspace_id
        - name
        - label
      title: ModuleCreateIn
      type: object
    ModuleOut:
      properties:
        default_rail_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Default Rail Id
        icon:
          anyOf:
            - type: string
            - type: 'null'
          title: Icon
        id:
          title: Id
          type: string
        label:
          title: Label
          type: string
        name:
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        rail_order_config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Rail Order Config
        workspace_id:
          title: Workspace Id
          type: string
      required:
        - id
        - name
        - label
        - workspace_id
        - organization_id
      title: ModuleOut
      type: object
    ModulePatchIn:
      properties:
        default_rail_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Default Rail Id
        icon:
          anyOf:
            - type: string
            - type: 'null'
          title: Icon
        label:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Label
        name:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Name
        rail_order_config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Rail Order Config
      title: ModulePatchIn
      type: object
    OrganizationOut:
      description: An organization — the tenancy root that owns workspaces and memberships.
      properties:
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        id:
          title: Id
          type: string
        label:
          anyOf:
            - type: string
            - type: 'null'
          title: Label
        name:
          title: Name
          type: string
      required:
        - id
        - name
      title: OrganizationOut
      type: object
    OrganizationPatchIn:
      description: >-
        Partial update. `config` é substituído por inteiro, não fundido: quem
        chama leu o

        GET e manda o objeto completo. Um merge implícito perderia chaves sem
        ninguém pedir.


        `name` fica de fora de propósito: é o handle da organização (ver

        `docs/architecture/organization-handle.md`) e mudá-lo tem consequências
        noutros sistemas.

        `owner_id`, `trial_started_at`, `created_at` e `created_by` não são
        aceitáveis aqui porque

        a base os congela em silêncio para não-donos (274:231-234).
      properties:
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        label:
          anyOf:
            - maxLength: 200
              minLength: 1
              type: string
            - type: 'null'
          title: Label
      title: OrganizationPatchIn
      type: object
    Page_ApiKeyOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/ApiKeyOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[ApiKeyOut]
      type: object
    Page_ExternalRowOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/ExternalRowOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[ExternalRowOut]
      type: object
    Page_FeatureOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/FeatureOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[FeatureOut]
      type: object
    Page_ModuleOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/ModuleOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[ModuleOut]
      type: object
    Page_OrganizationOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/OrganizationOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[OrganizationOut]
      type: object
    Page_RailOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/RailOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[RailOut]
      type: object
    Page_RowOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/RowOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[RowOut]
      type: object
    Page_TemplateJobOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/TemplateJobOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[TemplateJobOut]
      type: object
    Page_TemplateOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/TemplateOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[TemplateOut]
      type: object
    Page_ViewOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/ViewOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[ViewOut]
      type: object
    Page_WorkflowOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/WorkflowOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[WorkflowOut]
      type: object
    Page_WorkflowRunOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/WorkflowRunOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[WorkflowRunOut]
      type: object
    Page_WorkflowV2Out_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/WorkflowV2Out'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[WorkflowV2Out]
      type: object
    Page_WorkspaceOut_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/WorkspaceOut'
          title: Items
          type: array
        next_cursor:
          anyOf:
            - type: string
            - type: 'null'
          title: Next Cursor
      required:
        - items
      title: Page[WorkspaceOut]
      type: object
    Problem:
      description: >-
        RFC 7807 Problem Details: the single error response shape across the
        API.


        `code` is an extension member: a short, stable, machine-readable
        identifier the client

        maps to its own copy. It never changes for a given failure cause.
      properties:
        code:
          default: error
          title: Code
          type: string
        detail:
          anyOf:
            - type: string
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Detail
        instance:
          anyOf:
            - type: string
            - type: 'null'
          title: Instance
        status:
          title: Status
          type: integer
        title:
          title: Title
          type: string
        type:
          default: about:blank
          title: Type
          type: string
      required:
        - title
        - status
      title: Problem
      type: object
    ProviderSecretMaskedOut:
      description: >-
        Browser-facing — which providers the org has a key for, WITHOUT the
        value.
      properties:
        is_set:
          title: Is Set
          type: boolean
        provider:
          title: Provider
          type: string
      required:
        - provider
        - is_set
      title: ProviderSecretMaskedOut
      type: object
    RailCreateIn:
      description: '`organization_id` is NEVER accepted — derived from the parent module.'
      properties:
        config:
          anyOf:
            - items: {}
              type: array
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        label:
          maxLength: 120
          minLength: 1
          title: Label
          type: string
        module_id:
          title: Module Id
          type: string
        path:
          default: ''
          title: Path
          type: string
      required:
        - module_id
        - label
      title: RailCreateIn
      type: object
    RailOut:
      properties:
        config:
          anyOf:
            - items: {}
              type: array
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        id:
          title: Id
          type: string
        label:
          title: Label
          type: string
        module_id:
          title: Module Id
          type: string
        organization_id:
          title: Organization Id
          type: string
        path:
          title: Path
          type: string
      required:
        - id
        - label
        - path
        - module_id
        - organization_id
      title: RailOut
      type: object
    RailPatchIn:
      properties:
        config:
          anyOf:
            - items: {}
              type: array
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        label:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Label
        path:
          anyOf:
            - type: string
            - type: 'null'
          title: Path
      title: RailPatchIn
      type: object
    RefreshRequest:
      description: A refresh token for POST /v1/auth/refresh.
      properties:
        refresh_token:
          title: Refresh Token
          type: string
      required:
        - refresh_token
      title: RefreshRequest
      type: object
    RowCreateIn:
      additionalProperties: false
      description: >-
        The row payload. `organization_id` is NEVER accepted from the client —
        the service

        derives it from the parent feature, so a caller cannot mislabel a row's
        tenancy.
      properties:
        data:
          additionalProperties: true
          title: Data
          type: object
      required:
        - data
      title: RowCreateIn
      type: object
    RowOut:
      description: A feature row. `data` is a JSON object keyed by column name.
      properties:
        created_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Created At
        created_by:
          anyOf:
            - type: string
            - type: 'null'
          title: Created By
        data:
          anyOf:
            - additionalProperties: true
              type: object
            - items: {}
              type: array
            - type: 'null'
          title: Data
        feature_id:
          title: Feature Id
          type: string
        id:
          title: Id
          type: string
        organization_id:
          title: Organization Id
          type: string
        updated_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Updated At
        updated_by:
          anyOf:
            - type: string
            - type: 'null'
          title: Updated By
      required:
        - id
        - feature_id
        - organization_id
      title: RowOut
      type: object
    RowUpdateIn:
      properties:
        data:
          additionalProperties: true
          title: Data
          type: object
      required:
        - data
      title: RowUpdateIn
      type: object
    RowsBatchIn:
      additionalProperties: false
      description: >-
        Bulk create: up to 500 rows, inserted ATOMICALLY (one PostgREST call —
        any rejected

        row rolls back the whole batch).
      properties:
        rows:
          items:
            $ref: '#/components/schemas/RowCreateIn'
          maxItems: 500
          minItems: 1
          title: Rows
          type: array
      required:
        - rows
      title: RowsBatchIn
      type: object
    RowsBatchOut:
      properties:
        count:
          title: Count
          type: integer
        items:
          items:
            $ref: '#/components/schemas/RowOut'
          title: Items
          type: array
      required:
        - items
        - count
      title: RowsBatchOut
      type: object
    SchemaCounts:
      properties:
        columns:
          title: Columns
          type: integer
        features:
          title: Features
          type: integer
        modules:
          title: Modules
          type: integer
        rails:
          title: Rails
          type: integer
        views:
          title: Views
          type: integer
        workspaces:
          title: Workspaces
          type: integer
      required:
        - workspaces
        - modules
        - rails
        - features
        - columns
        - views
      title: SchemaCounts
      type: object
    SchemaFeatureOut:
      properties:
        columns:
          items:
            $ref: '#/components/schemas/FeatureColumnOut'
          title: Columns
          type: array
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        id:
          title: Id
          type: string
        label:
          anyOf:
            - type: string
            - type: 'null'
          title: Label
        name:
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        views:
          items:
            $ref: '#/components/schemas/ViewOut'
          title: Views
          type: array
        workspace_id:
          title: Workspace Id
          type: string
      required:
        - id
        - name
        - workspace_id
        - organization_id
        - columns
        - views
      title: SchemaFeatureOut
      type: object
    SchemaModuleOut:
      properties:
        default_rail_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Default Rail Id
        icon:
          anyOf:
            - type: string
            - type: 'null'
          title: Icon
        id:
          title: Id
          type: string
        label:
          title: Label
          type: string
        name:
          title: Name
          type: string
        rails:
          items:
            $ref: '#/components/schemas/RailOut'
          title: Rails
          type: array
        workspace_id:
          title: Workspace Id
          type: string
      required:
        - id
        - name
        - label
        - workspace_id
        - rails
      title: SchemaModuleOut
      type: object
    SchemaOut:
      description: >-
        The caller's entire visible model in one response. `truncated: true`
        means a size

        cap was hit — narrow the request with `workspace_id`.
      properties:
        counts:
          $ref: '#/components/schemas/SchemaCounts'
        truncated:
          title: Truncated
          type: boolean
        workspaces:
          items:
            $ref: '#/components/schemas/SchemaWorkspaceOut'
          title: Workspaces
          type: array
      required:
        - workspaces
        - counts
        - truncated
      title: SchemaOut
      type: object
    SchemaWorkspaceOut:
      properties:
        default_module_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Default Module Id
        features:
          items:
            $ref: '#/components/schemas/SchemaFeatureOut'
          title: Features
          type: array
        id:
          title: Id
          type: string
        label:
          anyOf:
            - type: string
            - type: 'null'
          title: Label
        modules:
          items:
            $ref: '#/components/schemas/SchemaModuleOut'
          title: Modules
          type: array
        name:
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
      required:
        - id
        - name
        - organization_id
        - modules
        - features
      title: SchemaWorkspaceOut
      type: object
    ServiceDefaultsOut:
      description: >-
        Global service-default mode->model_key map from the DB (editable by
        superadmin).


        Shape: {"defaults": {"query": "fast-model", "author": "smart-model",
        ...}}

        Values are catalog model keys (AiModelCatalog.key), not vertente
        buckets.
      properties:
        defaults:
          additionalProperties:
            type: string
          title: Defaults
          type: object
      title: ServiceDefaultsOut
      type: object
    SetProviderSecretIn:
      description: Set/replace the org's BYOK key for a provider. Empty is rejected (422).
      properties:
        secret:
          minLength: 1
          title: Secret
          type: string
      required:
        - secret
      title: SetProviderSecretIn
      type: object
    SetSettingsIn:
      properties:
        mode_models:
          additionalProperties:
            type: string
          title: Mode Models
          type: object
        organization_id:
          format: uuid
          title: Organization Id
          type: string
      required:
        - organization_id
        - mode_models
      title: SetSettingsIn
      type: object
    SettingsOut:
      properties:
        mode_models:
          additionalProperties:
            type: string
          title: Mode Models
          type: object
        organization_id:
          title: Organization Id
          type: string
      required:
        - organization_id
      title: SettingsOut
      type: object
    SignedUrlOut:
      properties:
        expires_in:
          title: Expires In
          type: integer
        url:
          title: Url
          type: string
      required:
        - url
        - expires_in
      title: SignedUrlOut
      type: object
    TemplateApplyIn:
      description: >-
        O pedido de aplicação. O template vem do caminho e a organização do
        workspace de

        destino: aceitá-la no corpo era medir a pertença contra a organização
        errada.
      properties:
        include_rows:
          default: true
          title: Include Rows
          type: boolean
        job_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          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.
          title: Job Id
        resolutions:
          additionalProperties: true
          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.
          title: Resolutions
          type: object
        rows_modes:
          additionalProperties:
            type: string
          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`.
          title: Rows Modes
          type: object
        workspace_id:
          format: uuid
          title: Workspace Id
          type: string
      required:
        - workspace_id
      title: TemplateApplyIn
      type: object
    TemplateApplyOut:
      description: >-
        O job acabado de arrancar. O relatório lê-se em `GET
        /v1/template-jobs/{id}`.
      properties:
        job_id:
          title: Job Id
          type: string
        status:
          title: Status
          type: string
      required:
        - job_id
        - status
      title: TemplateApplyOut
      type: object
    TemplateApplyProblem:
      description: >-
        O 422 de `apply_template`/`suggest_template_mappings`: o mesmo envelope
        RFC 7807

        das outras rotas, com o `detail` estruturado.
      properties:
        detail:
          $ref: '#/components/schemas/TemplateApplyProblemDetail'
        instance:
          anyOf:
            - type: string
            - type: 'null'
          title: Instance
        status:
          title: Status
          type: integer
        title:
          title: Title
          type: string
        type:
          default: about:blank
          title: Type
          type: string
      required:
        - title
        - status
        - detail
      title: TemplateApplyProblem
      type: object
    TemplateApplyProblemDetail:
      description: >-
        O `detail` de uma recusa nomeada do executor. Objeto e não frase: o
        `ref` diz

        qual é o campo a corrigir, e uma frase só obrigava quem chama a lê-la à
        procura

        dele.
      properties:
        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).
          title: Code
          type: string
        message:
          description: What the caller can do about it, in English.
          title: Message
          type: string
        ref:
          anyOf:
            - type: string
            - type: 'null'
          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`.
          title: Ref
      required:
        - code
        - message
      title: TemplateApplyProblemDetail
      type: object
    TemplateDetailOut:
      description: A linha mais o pacote (`manifest`), só em `GET /v1/templates/{id}`.
      properties:
        category:
          anyOf:
            - type: string
            - type: 'null'
          title: Category
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
        icon:
          anyOf:
            - type: string
            - type: 'null'
          title: Icon
        id:
          title: Id
          type: string
        label:
          title: Label
          type: string
        manifest:
          additionalProperties: true
          default: {}
          title: Manifest
          type: object
        name:
          title: Name
          type: string
        organization_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Organization Id
        published_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Published At
        published_by:
          anyOf:
            - type: string
            - type: 'null'
          title: Published By
        scope:
          title: Scope
          type: string
        semver:
          title: Semver
          type: string
        source_workspace_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Source Workspace Id
        tags:
          default: []
          items:
            type: string
          title: Tags
          type: array
        visibility:
          title: Visibility
          type: string
      required:
        - id
        - scope
        - name
        - label
        - semver
        - visibility
      title: TemplateDetailOut
      type: object
    TemplateJobOut:
      description: >-
        O estado de um job. O `plan_snapshot`, o `uid_map` e o `ledger` NÃO saem
        na API: são

        estado interno do executor e o snapshot de um ERP é grande — a mesma
        razão por que o

        `manifest` não entra na lista do catálogo.
      properties:
        created_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Created At
        error:
          anyOf:
            - type: string
            - type: 'null'
          title: Error
        id:
          title: Id
          type: string
        include_rows:
          title: Include Rows
          type: boolean
        locked_until:
          anyOf:
            - type: string
            - type: 'null'
          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.
          title: Locked Until
        report:
          additionalProperties: true
          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`.
          title: Report
          type: object
        status:
          title: Status
          type: string
        template_id:
          title: Template Id
          type: string
        updated_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Updated At
        workspace_id:
          title: Workspace Id
          type: string
      required:
        - id
        - template_id
        - workspace_id
        - status
        - include_rows
      title: TemplateJobOut
      type: object
    TemplateMappingSuggestionsIn:
      description: >-
        O destino sobre que se pergunta. Só o workspace: a organização deriva-se
        dele, como

        no apply, e as sugestões não escrevem nada.
      properties:
        workspace_id:
          format: uuid
          title: Workspace Id
          type: string
      required:
        - workspace_id
      title: TemplateMappingSuggestionsIn
      type: object
    TemplateMappingSuggestionsOut:
      description: >-
        As sugestões tal como o executor as devolve, uma por dependência. Os
        objetos não se

        tipam aqui: quem os escreve é a `DependencyMatcher` da `outdo_library`,
        e um segundo

        modelo em Python divergia dela à primeira regra nova — a mesma razão do
        `apply_client`.


        Por sugestão: `ref`, `kind`, `form`, `required`, `proposed` (`map`,
        `import` ou

        `decide`) e `candidates`, cada candidata com `id`, `name`, `label`,
        `confidence` (0 a 1)

        e as `columns` que casaram.
      properties:
        suggestions:
          default: []
          items:
            additionalProperties: true
            type: object
          title: Suggestions
          type: array
      title: TemplateMappingSuggestionsOut
      type: object
    TemplateOut:
      description: Uma linha do catálogo, sem o pacote.
      properties:
        category:
          anyOf:
            - type: string
            - type: 'null'
          title: Category
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
        icon:
          anyOf:
            - type: string
            - type: 'null'
          title: Icon
        id:
          title: Id
          type: string
        label:
          title: Label
          type: string
        name:
          title: Name
          type: string
        organization_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Organization Id
        published_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Published At
        published_by:
          anyOf:
            - type: string
            - type: 'null'
          title: Published By
        scope:
          title: Scope
          type: string
        semver:
          title: Semver
          type: string
        source_workspace_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Source Workspace Id
        tags:
          default: []
          items:
            type: string
          title: Tags
          type: array
        visibility:
          title: Visibility
          type: string
      required:
        - id
        - scope
        - name
        - label
        - semver
        - visibility
      title: TemplateOut
      type: object
    TemplateSampleRowsOut:
      description: >-
        O resultado de remover ou atribuir as linhas de exemplo de um job: o
        total e o

        detalhe por tabela. A estrutura que o pacote criou não entra aqui — não
        se toca nela.
      properties:
        job_id:
          title: Job Id
          type: string
        tables:
          default: []
          items:
            $ref: '#/components/schemas/TemplateSampleRowsTable'
          title: Tables
          type: array
        total:
          title: Total
          type: integer
      required:
        - job_id
        - total
      title: TemplateSampleRowsOut
      type: object
    TemplateSampleRowsTable:
      description: >-
        O que uma ação de pós-aplicação fez numa tabela. O `label` vem da base:
        quem chama

        pode não ter o pacote em mão. As `columns` são as que a atribuição
        preencheu, e ficam

        vazias na remoção, que não toca em coluna nenhuma.
      properties:
        columns:
          default: []
          items:
            type: string
          title: Columns
          type: array
        feature_id:
          title: Feature Id
          type: string
        label:
          title: Label
          type: string
        rows:
          title: Rows
          type: integer
      required:
        - feature_id
        - label
        - rows
      title: TemplateSampleRowsTable
      type: object
    TokenRequest:
      description: >-
        Credentials for POST /v1/auth/token (delegated to Supabase GoTrue).


        `email` is a plain str (no EmailStr) — GoTrue is the credential
        authority; the API does

        not pre-validate the address shape, it simply forwards. A missing field
        still → 422.
      properties:
        email:
          title: Email
          type: string
        password:
          title: Password
          type: string
      required:
        - email
        - password
      title: TokenRequest
      type: object
    TokenResponse:
      description: The Supabase session triple returned on a clean login/refresh.
      properties:
        access_token:
          title: Access Token
          type: string
        expires_in:
          title: Expires In
          type: integer
        refresh_token:
          title: Refresh Token
          type: string
      required:
        - access_token
        - refresh_token
        - expires_in
      title: TokenResponse
      type: object
    UsageSummaryOut:
      description: Response body for GET /v1/ai/usage-summary.
      properties:
        organization_id:
          title: Organization Id
          type: string
        rows:
          items:
            $ref: '#/components/schemas/UsageSummaryRow'
          title: Rows
          type: array
      required:
        - organization_id
        - rows
      title: UsageSummaryOut
      type: object
    UsageSummaryRow:
      description: One aggregated row returned by `org_ai_usage_summary`.
      properties:
        event_count:
          title: Event Count
          type: integer
        mode:
          title: Mode
          type: string
        model_key:
          title: Model Key
          type: string
        total_quantity:
          title: Total Quantity
          type: number
        unit:
          anyOf:
            - type: string
            - type: 'null'
          title: Unit
      required:
        - mode
        - model_key
        - total_quantity
        - event_count
      title: UsageSummaryRow
      type: object
    ValidationError:
      properties:
        ctx:
          title: Context
          type: object
        input:
          title: Input
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          title: Location
          type: array
        msg:
          title: Message
          type: string
        type:
          title: Error Type
          type: string
      required:
        - loc
        - msg
        - type
      title: ValidationError
      type: object
    ValidationOut:
      description: >-
        The verdict. `ok` is the absence of ERRORS: a warning never blocks
        anything.
      properties:
        issues:
          items:
            $ref: '#/components/schemas/GraphIssueOut'
          title: Issues
          type: array
        ok:
          title: Ok
          type: boolean
      required:
        - ok
      title: ValidationOut
      type: object
    ViewCreateIn:
      description: >-
        `organization_id` is NEVER accepted — derived from the parent feature.
        `type` is

        validated against the view registry (see GET /v1/schema/definitions).
      properties:
        config:
          anyOf:
            - items: {}
              type: array
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        feature_id:
          title: Feature Id
          type: string
        label:
          maxLength: 120
          minLength: 1
          title: Label
          type: string
        name:
          maxLength: 120
          minLength: 1
          title: Name
          type: string
        type:
          title: Type
          type: string
      required:
        - feature_id
        - name
        - label
        - type
      title: ViewCreateIn
      type: object
    ViewOut:
      description: A saved view over a feature (table, kanban, form, …).
      properties:
        config:
          anyOf:
            - items: {}
              type: array
            - additionalProperties: true
              type: object
          title: Config
        feature_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Feature Id
        id:
          title: Id
          type: string
        label:
          title: Label
          type: string
        name:
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        type:
          title: Type
          type: string
      required:
        - id
        - name
        - label
        - type
        - config
        - organization_id
      title: ViewOut
      type: object
    ViewPatchIn:
      properties:
        config:
          anyOf:
            - items: {}
              type: array
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        label:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Label
        name:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Name
      title: ViewPatchIn
      type: object
    WorkflowOut:
      description: A workflow definition. Triggerable via the API when active.
      properties:
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
        id:
          title: Id
          type: string
        is_active:
          title: Is Active
          type: boolean
        name:
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        trigger_config:
          anyOf:
            - additionalProperties: true
              type: object
            - items: {}
              type: array
            - type: 'null'
          title: Trigger Config
        workspace_id:
          title: Workspace Id
          type: string
      required:
        - id
        - name
        - workspace_id
        - organization_id
        - is_active
      title: WorkflowOut
      type: object
    WorkflowRunOut:
      description: >-
        One execution of a workflow (a `WorkflowLog` row). Poll until `status`
        reaches

        `success`, `failed`, or `cancelled`.
      properties:
        completed_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Completed At
        error_message:
          anyOf:
            - type: string
            - type: 'null'
          title: Error Message
        id:
          title: Id
          type: string
        input_data:
          anyOf:
            - additionalProperties: true
              type: object
            - items: {}
              type: array
            - type: 'null'
          title: Input Data
        is_dry_run:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Is Dry Run
        output_data:
          anyOf:
            - additionalProperties: true
              type: object
            - items: {}
              type: array
            - type: 'null'
          title: Output Data
        started_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Started At
        status:
          anyOf:
            - type: string
            - type: 'null'
          title: Status
        steps_executed:
          anyOf:
            - type: integer
            - type: 'null'
          title: Steps Executed
        total_steps:
          anyOf:
            - type: integer
            - type: 'null'
          title: Total Steps
        trigger_type:
          anyOf:
            - type: string
            - type: 'null'
          title: Trigger Type
        triggered_by:
          anyOf:
            - type: string
            - type: 'null'
          title: Triggered By
        workflow_id:
          title: Workflow Id
          type: string
        workspace_id:
          title: Workspace Id
          type: string
      required:
        - id
        - workflow_id
        - workspace_id
      title: WorkflowRunOut
      type: object
    WorkflowTriggerIn:
      description: 'Trigger payload: optional input context handed to the run.'
      properties:
        dry_run:
          default: false
          title: Dry Run
          type: boolean
        input_data:
          additionalProperties: true
          title: Input Data
          type: object
      title: WorkflowTriggerIn
      type: object
    WorkflowV2CreateIn:
      description: >-
        A new automation. `organization_id` and `workflow_id` are not accepted:
        the first is

        derived from the validated workspace, the second is minted server-side.
        The definition is

        validated before anything is written. Send an `Idempotency-Key` header
        to make retries

        safe.
      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
      properties:
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
        graph_config:
          additionalProperties: true
          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/).
          title: Graph Config
          type: object
        is_draft:
          default: true
          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.
          title: Is Draft
          type: boolean
        label:
          anyOf:
            - maxLength: 200
              type: string
            - type: 'null'
          title: Label
        name:
          maxLength: 120
          minLength: 1
          title: Name
          type: string
        trigger_config:
          additionalProperties: true
          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/).
          title: Trigger Config
          type: object
        workspace_id:
          format: uuid
          title: Workspace Id
          type: string
      required:
        - workspace_id
        - name
      title: WorkflowV2CreateIn
      type: object
    WorkflowV2Out:
      description: >-
        One VERSION of an automation. Address the automation by `workflow_id`,
        which groups

        the versions, and not by `id`, which identifies this snapshot alone.
      properties:
        archived_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Archived At
        created_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Created At
        created_by:
          anyOf:
            - type: string
            - type: 'null'
          title: Created By
        deactivation_reason:
          anyOf:
            - type: string
            - type: 'null'
          title: Deactivation Reason
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
        graph_config:
          additionalProperties: true
          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/).
          title: Graph Config
          type: object
        id:
          title: Id
          type: string
        is_active:
          default: false
          title: Is Active
          type: boolean
        is_draft:
          default: false
          description: An unapplied draft. A draft never fires and can never be active.
          title: Is Draft
          type: boolean
        label:
          default: ''
          title: Label
          type: string
        name:
          default: ''
          title: Name
          type: string
        organization_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Organization Id
        trigger_config:
          additionalProperties: true
          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/).
          title: Trigger Config
          type: object
        updated_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Updated At
        version:
          default: 1
          title: Version
          type: integer
        workflow_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Workflow Id
        workspace_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Workspace Id
      required:
        - id
      title: WorkflowV2Out
      type: object
    WorkflowV2ValidateIn:
      description: >-
        A definition to validate without writing anything. `workspace_id`
        derives the

        organization the validator authenticates against; without it, the
        organization of the

        caller'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
      properties:
        graph_config:
          additionalProperties: true
          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/).
          title: Graph Config
          type: object
        trigger_config:
          additionalProperties: true
          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/).
          title: Trigger Config
          type: object
        workflow_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          description: The workflow this graph belongs to, so self-recursion is detected.
          title: Workflow Id
        workspace_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          title: Workspace Id
      title: WorkflowV2ValidateIn
      type: object
    WorkflowV2VersionIn:
      description: >-
        The next version of a workflow that already has an active one. Whatever
        you omit keeps

        the value of the active version.


        `trigger_config` and `graph_config` are null by default, and not `{}`:
        an empty object

        cannot tell "I did not touch the trigger" from "I erased the trigger",
        and the validator

        would answer `trigger_missing` to a call that only wanted to change the
        label.
      properties:
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
        graph_config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          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.
          title: Graph Config
        label:
          anyOf:
            - maxLength: 200
              type: string
            - type: 'null'
          title: Label
        name:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Name
        trigger_config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          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.
          title: Trigger Config
      title: WorkflowV2VersionIn
      type: object
    WorkspaceCreateIn:
      description: >-
        Payload for creating a workspace. The caller must belong to the
        organization.
      properties:
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        label:
          maxLength: 120
          minLength: 1
          title: Label
          type: string
        name:
          maxLength: 120
          minLength: 1
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        timezone:
          anyOf:
            - type: string
            - type: 'null'
          title: Timezone
        visibility_policy:
          anyOf:
            - type: string
            - type: 'null'
          title: Visibility Policy
      required:
        - organization_id
        - name
        - label
      title: WorkspaceCreateIn
      type: object
    WorkspaceOut:
      description: A workspace — the top-level container for modules, features, and views.
      properties:
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        default_module_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Default Module Id
        id:
          title: Id
          type: string
        label:
          anyOf:
            - type: string
            - type: 'null'
          title: Label
        module_order_config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Module Order Config
        name:
          title: Name
          type: string
        organization_id:
          title: Organization Id
          type: string
        timezone:
          anyOf:
            - type: string
            - type: 'null'
          title: Timezone
        visibility_policy:
          anyOf:
            - type: string
            - type: 'null'
          title: Visibility Policy
      required:
        - id
        - name
        - organization_id
      title: WorkspaceOut
      type: object
    WorkspacePatchIn:
      description: >-
        Partial update — only the provided fields change (exclude_unset
        semantics).
      properties:
        config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Config
        default_module_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Default Module Id
        label:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Label
        module_order_config:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Module Order Config
        name:
          anyOf:
            - maxLength: 120
              minLength: 1
              type: string
            - type: 'null'
          title: Name
        timezone:
          anyOf:
            - type: string
            - type: 'null'
          title: Timezone
        visibility_policy:
          anyOf:
            - type: string
            - type: 'null'
          title: Visibility Policy
      title: WorkspacePatchIn
      type: object
  securitySchemes:
    HTTPBearer:
      bearerFormat: JWT
      description: >-
        A Supabase user JWT (from POST /v1/auth/token) OR a scoped API key
        (outdo_sk_…).
      scheme: bearer
      type: http
info:
  contact:
    name: OutDo
    url: https://out-do.app/
  description: >-
    The public REST API for the OutDo platform: authentication, the workspace
    model (workspaces, modules, rails, features, columns, views), row data,
    aggregates, and files.


    ### Authentication

    Sign 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.


    Machines 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`.


    ### Conventions

    - **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.

    - **Pagination** is cursor-based: pass `next_cursor` from the previous page
    back as `cursor`.

    - **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.

    - **Rate limits** apply per route and per client (and per API key when one
    is used); `429` responses carry a `Retry-After` header.

    - **Versioning**: this surface is `/v1`; breaking changes ship under a new
    prefix.


    Machine-readable structure: `GET /v1/schema` (the caller's visible model)
    and `GET /v1/schema/definitions` (what each entity's config accepts).


    ### Documentation

    The 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/).
  title: OutDo Data API
  version: 0.1.0
openapi: 3.1.0
paths:
  /v1/admin/stats:
    get:
      description: >-
        Version and uptime of the outdo_api_data service. Superadmin session
        only.
      operationId: admin_stats
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminStatsOut'
          description: Successful Response
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not authenticated.
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not a superadmin (or an API-key caller).
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
          description: Validation Error
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Rate limited.
        '503':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Service stats (superadmin)
      tags:
        - admin
  /v1/ai/catalog:
    get:
      operationId: list_ai_models
      parameters:
        - description: Filter the catalog to the models this org's tier may use.
          in: query
          name: organization_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            description: Filter the catalog to the models this org's tier may use.
            title: Organization Id
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/CatalogModelOut'
                title: Response List Ai Models
                type: array
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List the active AI model catalog (operator-curated)
      tags:
        - ai
  /v1/ai/credits/balance:
    get:
      operationId: get_org_credit_balance
      parameters:
        - description: Organization id.
          in: query
          name: organization_id
          required: true
          schema:
            description: Organization id.
            format: uuid
            title: Organization Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreditBalanceOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '403':
          description: Not a member of this organization.
        '422':
          description: Validation error.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get an organization's AI credit balance (member-gated)
      tags:
        - ai
  /v1/ai/org/{organization_id}/provider-secret/{provider}:
    put:
      operationId: set_org_provider_secret
      parameters:
        - in: path
          name: organization_id
          required: true
          schema:
            format: uuid
            title: Organization Id
            type: string
        - in: path
          name: provider
          required: true
          schema:
            title: Provider
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetProviderSecretIn'
        required: true
      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.
      security:
        - HTTPBearer: []
      summary: Set/replace the organization's BYOK key for a provider (owner/admin)
      tags:
        - ai
  /v1/ai/org/{organization_id}/provider-secrets:
    get:
      operationId: list_org_provider_secrets
      parameters:
        - in: path
          name: organization_id
          required: true
          schema:
            format: uuid
            title: Organization Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/ProviderSecretMaskedOut'
                title: Response List Org Provider Secrets
                type: array
          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.
      security:
        - HTTPBearer: []
      summary: List which providers the organization has a BYOK key for (no values)
      tags:
        - ai
  /v1/ai/service-defaults:
    get:
      operationId: get_ai_service_defaults
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServiceDefaultsOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: >-
        Get the global service-default mode->model_key map (editable by
        superadmin)
      tags:
        - ai
  /v1/ai/settings:
    get:
      operationId: get_ai_settings
      parameters:
        - description: Organization id.
          in: query
          name: organization_id
          required: true
          schema:
            description: Organization id.
            format: uuid
            title: Organization Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SettingsOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get an organization's per-mode model selection
      tags:
        - ai
    patch:
      operationId: set_ai_settings
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetSettingsIn'
        required: true
      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.
      security:
        - HTTPBearer: []
      summary: Set an organization's per-mode model selection (owner/admin)
      tags:
        - ai
  /v1/ai/usage-summary:
    get:
      operationId: get_ai_usage_summary
      parameters:
        - description: Organization id.
          in: query
          name: organization_id
          required: true
          schema:
            description: Organization id.
            format: uuid
            title: Organization Id
            type: string
        - description: ISO-8601 timestamp; only events after this point are included.
          in: query
          name: since
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: ISO-8601 timestamp; only events after this point are included.
            title: Since
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageSummaryOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '403':
          description: Not a member of this organization.
        '422':
          description: Validation error.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get an organization's AI usage summary (member-gated)
      tags:
        - ai
  /v1/api-keys:
    get:
      description: >-
        Cursor-paginated. Returns key metadata (never the secret). Optionally
        filtered to one organization.
      operationId: list_api_keys
      parameters:
        - description: Only keys of this organization.
          in: query
          name: organization_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            description: Only keys of this organization.
            title: Organization Id
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_ApiKeyOut_'
          description: Successful Response
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not authenticated.
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: API keys cannot manage API keys.
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not found / not visible.
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Validation error.
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Rate limited.
        '503':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List API keys
      tags:
        - api-keys
    post:
      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
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiKeyCreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyCreatedOut'
          description: Successful Response
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not authenticated.
        '402':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Active API key limit reached for this scope.
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not allowed to manage keys for this organization.
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not found / not visible.
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Validation error.
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Rate limited.
        '503':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Create an API key
      tags:
        - api-keys
  /v1/api-keys/{key_id}:
    delete:
      description: >-
        Revokes the key and tears down its machine user's membership immediately
        — the key can no longer authenticate. Idempotent.
      operationId: revoke_api_key
      parameters:
        - in: path
          name: key_id
          required: true
          schema:
            format: uuid
            title: Key Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '204':
          description: Successful Response
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not authenticated.
        '402':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Active API key limit reached for this scope.
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not allowed to manage keys for this organization.
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not found / not visible.
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Validation error.
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Rate limited.
        '503':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Revoke an API key
      tags:
        - api-keys
    get:
      description: >-
        Returns key metadata (never the secret). 404 when not visible to the
        caller.
      operationId: get_api_key
      parameters:
        - in: path
          name: key_id
          required: true
          schema:
            format: uuid
            title: Key Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyOut'
          description: Successful Response
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not authenticated.
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: API keys cannot manage API keys.
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Not found / not visible.
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Validation error.
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Rate limited.
        '503':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get an API key
      tags:
        - api-keys
  /v1/auth/refresh:
    post:
      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':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
          description: Successful Response
        '401':
          description: Authentication failed (generic; no cause disclosed).
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
      summary: Refresh an access token
      tags:
        - auth
  /v1/auth/token:
    post:
      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':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
          description: Successful Response
        '401':
          description: Authentication failed (generic; no cause disclosed).
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
      summary: Sign in with email and password
      tags:
        - auth
  /v1/external-accounts/invite:
    post:
      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.


        Which 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.


        `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
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExternalAccountInviteIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccountInviteOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '403':
          description: Not an administrator of the organization.
        '404':
          description: Not found / not visible.
        '409':
          description: The identity could not be reused for this email.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store / identity provider unavailable.
      security:
        - HTTPBearer: []
      summary: Invite an external account
      tags:
        - external-accounts
  /v1/external-accounts/{account_id}/revoke:
    post:
      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
      parameters:
        - in: path
          name: account_id
          required: true
          schema:
            title: Account Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccountOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: Revoke an external account
      tags:
        - external-accounts
  /v1/external/features:
    get:
      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.


        A 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
      parameters:
        - description: >-
            The organization whose area is being read. Must be one of the
            organizations returned by `GET /v1/external/me`.
          in: query
          name: organization_id
          required: true
          schema:
            description: >-
              The organization whose area is being read. Must be one of the
              organizations returned by `GET /v1/external/me`.
            minLength: 1
            title: Organization Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalFeaturesOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: The features published to client accounts
      tags:
        - external
  /v1/external/me:
    get:
      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.


        **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
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalMeOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: The caller's client accounts
      tags:
        - external
  /v1/external/rows:
    get:
      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.


        A 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
      parameters:
        - description: A feature listed by `GET /v1/external/features`.
          in: query
          name: feature_id
          required: true
          schema:
            description: A feature listed by `GET /v1/external/features`.
            format: uuid
            title: Feature Id
            type: string
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - 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.
          in: query
          name: fields
          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: >-
            The organization whose area is being read. Must be one of the
            organizations returned by `GET /v1/external/me`.
          in: query
          name: organization_id
          required: true
          schema:
            description: >-
              The organization whose area is being read. Must be one of the
              organizations returned by `GET /v1/external/me`.
            minLength: 1
            title: Organization Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_ExternalRowOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '403':
          description: No active client account for this identity.
        '404':
          description: No such feature, or it is not published to client accounts.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: The caller's rows of one published feature
      tags:
        - external
  /v1/features:
    get:
      description: Cursor-paginated. Optionally filtered to a single workspace.
      operationId: list_features
      parameters:
        - description: Only features of this workspace.
          in: query
          name: workspace_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            description: Only features of this workspace.
            title: Workspace Id
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_FeatureOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          description: Validation error (e.g. a malformed cursor).
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List features
      tags:
        - features
    post:
      description: >-
        The organization is derived from the parent workspace — it is never
        accepted from the client.
      operationId: create_feature
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FeatureCreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Create a feature
      tags:
        - features
  /v1/features/{feature_id}:
    delete:
      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
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - description: Confirm deleting a feature that still has rows.
          in: query
          name: force
          required: false
          schema:
            default: false
            description: Confirm deleting a feature that still has rows.
            title: Force
            type: boolean
        - in: header
          name: x-outdo-service-key
          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.
      security:
        - HTTPBearer: []
      summary: Delete a feature
      tags:
        - features
    get:
      description: >-
        Returns 404 when the feature does not exist or is not visible to the
        caller.
      operationId: get_feature
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get a feature
      tags:
        - features
    patch:
      description: >-
        Partial update — only the fields present in the request body are
        changed.
      operationId: update_feature
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FeaturePatchIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Update a feature
      tags:
        - features
  /v1/features/{feature_id}/columns:
    get:
      description: All column definitions of the feature, in display order.
      operationId: list_feature_columns
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/FeatureColumnOut'
                title: Response List Feature Columns
                type: array
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List columns
      tags:
        - features
    post:
      description: >-
        `type` and `default_component` are validated against the platform
        registries — see GET /v1/schema/definitions for the allowed values.
      operationId: create_feature_column
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ColumnCreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureColumnOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Create a column
      tags:
        - features
  /v1/features/{feature_id}/columns/{column_id}:
    delete:
      description: >-
        Removes the column DEFINITION. Row data keeps the orphaned key — it
        simply stops being displayed and validated.
      operationId: delete_feature_column
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: path
          name: column_id
          required: true
          schema:
            format: uuid
            title: Column Id
            type: string
        - in: header
          name: x-outdo-service-key
          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.
      security:
        - HTTPBearer: []
      summary: Delete a column
      tags:
        - features
    patch:
      description: >-
        Partial update — only the fields present in the request body are
        changed.
      operationId: update_feature_column
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: path
          name: column_id
          required: true
          schema:
            format: uuid
            title: Column Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ColumnPatchIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureColumnOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Update a column
      tags:
        - features
  /v1/features/{feature_id}/rows:
    get:
      description: >-
        Cursor-paginated. Supports filtering (`filter`, `match`), sorting
        (`sort`), and response-side projection (`fields`) — see each parameter
        for the grammar.
      operationId: list_rows
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - 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.
          in: query
          name: filter
          required: false
          schema:
            anyOf:
              - items:
                  type: string
                type: array
              - 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: 'How the filters combine: `all` (AND, default) or `any` (OR).'
          in: query
          name: match
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: 'How the filters combine: `all` (AND, default) or `any` (OR).'
            title: Match
        - description: >-
            Sort key: `column` asc, `-column` desc. Changes the cursor scheme —
            do not mix cursors across sorts.
          in: query
          name: sort
          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: >-
            Comma-separated projection of the row `data` keys, e.g.
            `fields=title,amount`.
          in: query
          name: fields
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: >-
              Comma-separated projection of the row `data` keys, e.g.
              `fields=title,amount`.
            title: Fields
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_RowOut_'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: List rows
      tags:
        - rows
    post:
      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
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - 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).
          in: query
          name: validate
          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
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RowCreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RowOut'
          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.
      security:
        - HTTPBearer: []
      summary: Create a row
      tags:
        - rows
  /v1/features/{feature_id}/rows/{row_id}:
    delete:
      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
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: path
          name: row_id
          required: true
          schema:
            format: uuid
            title: Row Id
            type: string
        - in: header
          name: x-outdo-service-key
          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.
      security:
        - HTTPBearer: []
      summary: Delete a row
      tags:
        - rows
    get:
      description: Returns 404 when the row does not exist or is not visible to the caller.
      operationId: get_row
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: path
          name: row_id
          required: true
          schema:
            format: uuid
            title: Row Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RowOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: Get a row
      tags:
        - rows
    patch:
      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
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: path
          name: row_id
          required: true
          schema:
            format: uuid
            title: Row Id
            type: string
        - 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).
          in: query
          name: validate
          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: >-
            true → shallow-merge the payload over the current data (a null value
            removes the key); false (default) → replace the whole data object.
          in: query
          name: merge
          required: false
          schema:
            default: false
            description: >-
              true → shallow-merge the payload over the current data (a null
              value removes the key); false (default) → replace the whole data
              object.
            title: Merge
            type: boolean
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RowUpdateIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RowOut'
          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.
      security:
        - HTTPBearer: []
      summary: Update a row
      tags:
        - rows
  /v1/features/{feature_id}/rows:aggregate:
    get:
      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
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - in: query
          name: op
          required: true
          schema:
            enum:
              - count
              - sum
              - avg
            title: Op
            type: string
        - description: Row data key (required for sum/avg).
          in: query
          name: column
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Row data key (required for sum/avg).
            title: Column
        - 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.
          in: query
          name: filter
          required: false
          schema:
            anyOf:
              - items:
                  type: string
                type: array
              - 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: 'How the filters combine: `all` (AND, default) or `any` (OR).'
          in: query
          name: match
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: 'How the filters combine: `all` (AND, default) or `any` (OR).'
            title: Match
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AggregateOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: Aggregate rows (count, sum, avg)
      tags:
        - rows
  /v1/features/{feature_id}/rows:batch:
    post:
      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
      parameters:
        - in: path
          name: feature_id
          required: true
          schema:
            format: uuid
            title: Feature Id
            type: string
        - 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).
          in: query
          name: validate
          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
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RowsBatchIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RowsBatchOut'
          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.
      security:
        - HTTPBearer: []
      summary: Create rows in bulk
      tags:
        - rows
  /v1/files:
    post:
      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
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/Body_upload_file'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: Upload a file
      tags:
        - files
  /v1/files/sign:
    get:
      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
      parameters:
        - description: The stored object path (FileOut.path).
          in: query
          name: path
          required: true
          schema:
            description: The stored object path (FileOut.path).
            title: Path
            type: string
        - description: Lifetime in seconds, 60 to 604800. Defaults to 3600.
          in: query
          name: expires_in
          required: false
          schema:
            anyOf:
              - minimum: 1
                type: integer
              - type: 'null'
            description: Lifetime in seconds, 60 to 604800. Defaults to 3600.
            title: Expires In
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignedUrlOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: Create a signed download URL
      tags:
        - files
  /v1/health:
    get:
      description: >-
        Unauthenticated. Touches no downstream service — safe for load-balancer
        checks.
      operationId: health
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthOut'
          description: Successful Response
      summary: Liveness probe
      tags:
        - health
  /v1/messages:
    post:
      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
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageCreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: Post a message
      tags:
        - messages
  /v1/modules:
    get:
      description: Cursor-paginated. Optionally filtered to a single workspace.
      operationId: list_modules
      parameters:
        - description: Only modules of this workspace.
          in: query
          name: workspace_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            description: Only modules of this workspace.
            title: Workspace Id
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_ModuleOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List modules
      tags:
        - modules
    post:
      description: >-
        The organization is derived from the parent workspace — it is never
        accepted from the client.
      operationId: create_module
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ModuleCreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModuleOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Create a module
      tags:
        - modules
  /v1/modules/{module_id}:
    delete:
      description: >-
        A module that still has rails returns 409 unless `force=true` (rails
        cascade).
      operationId: delete_module
      parameters:
        - in: path
          name: module_id
          required: true
          schema:
            format: uuid
            title: Module Id
            type: string
        - description: Confirm deleting a module that still has rails.
          in: query
          name: force
          required: false
          schema:
            default: false
            description: Confirm deleting a module that still has rails.
            title: Force
            type: boolean
        - in: header
          name: x-outdo-service-key
          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.
      security:
        - HTTPBearer: []
      summary: Delete a module
      tags:
        - modules
    get:
      description: >-
        Returns 404 when the module does not exist or is not visible to the
        caller.
      operationId: get_module
      parameters:
        - in: path
          name: module_id
          required: true
          schema:
            format: uuid
            title: Module Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModuleOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get a module
      tags:
        - modules
    patch:
      description: >-
        Partial update — only the fields present in the request body are
        changed.
      operationId: update_module
      parameters:
        - in: path
          name: module_id
          required: true
          schema:
            format: uuid
            title: Module Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ModulePatchIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModuleOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Update a module
      tags:
        - modules
  /v1/organizations:
    get:
      description: >-
        Cursor-paginated. Returns the organizations the caller owns or belongs
        to.
      operationId: list_organizations
      parameters:
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_OrganizationOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          description: Validation error (e.g. a malformed cursor).
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List organizations
      tags:
        - organizations
  /v1/organizations/{organization_id}:
    get:
      description: >-
        Returns 404 when the organization does not exist or is not visible to
        the caller.
      operationId: get_organization
      parameters:
        - in: path
          name: organization_id
          required: true
          schema:
            format: uuid
            title: Organization Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrganizationOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get an organization
      tags:
        - organizations
    patch:
      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
      parameters:
        - in: path
          name: organization_id
          required: true
          schema:
            format: uuid
            title: Organization Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrganizationPatchIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrganizationOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '403':
          description: The data store refused the write (role too low, or a frozen field).
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Update an organization
      tags:
        - organizations
  /v1/rails:
    get:
      description: Cursor-paginated. Optionally filtered to a single module.
      operationId: list_rails
      parameters:
        - description: Only rails of this module.
          in: query
          name: module_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            description: Only rails of this module.
            title: Module Id
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_RailOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List rails
      tags:
        - rails
    post:
      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
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RailCreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RailOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Create a rail
      tags:
        - rails
  /v1/rails/{rail_id}:
    delete:
      description: Removes the navigation entry; the views it pointed at are not touched.
      operationId: delete_rail
      parameters:
        - in: path
          name: rail_id
          required: true
          schema:
            format: uuid
            title: Rail Id
            type: string
        - in: header
          name: x-outdo-service-key
          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.
      security:
        - HTTPBearer: []
      summary: Delete a rail
      tags:
        - rails
    get:
      description: >-
        Returns 404 when the rail does not exist or is not visible to the
        caller.
      operationId: get_rail
      parameters:
        - in: path
          name: rail_id
          required: true
          schema:
            format: uuid
            title: Rail Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RailOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get a rail
      tags:
        - rails
    patch:
      description: >-
        Partial update — only the fields present in the request body are
        changed.
      operationId: update_rail
      parameters:
        - in: path
          name: rail_id
          required: true
          schema:
            format: uuid
            title: Rail Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RailPatchIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RailOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Update a rail
      tags:
        - rails
  /v1/schema:
    get:
      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
      parameters:
        - in: query
          name: workspace_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            title: Workspace Id
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SchemaOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: Get the workspace model
      tags:
        - schema
  /v1/schema/definitions:
    get:
      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
      parameters:
        - deprecated: true
          description: 'DEPRECATED: `legacy` serves the previous shape for one release.'
          in: query
          name: format
          required: false
          schema:
            default: generated
            deprecated: true
            description: 'DEPRECATED: `legacy` serves the previous shape for one release.'
            enum:
              - generated
              - legacy
            title: Format
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                additionalProperties: true
                title: Response Get Schema Definitions
                type: object
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
          description: Validation Error
        '429':
          description: Rate limited.
      security:
        - HTTPBearer: []
      summary: Get configuration definitions
      tags:
        - schema
  /v1/template-jobs:
    get:
      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
      parameters:
        - description: Only jobs applied to this workspace.
          in: query
          name: workspace_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            description: Only jobs applied to this workspace.
            title: Workspace Id
        - description: Only jobs in this state.
          in: query
          name: status
          required: false
          schema:
            anyOf:
              - enum:
                  - queued
                  - structure
                  - rows
                  - done
                  - failed
                  - cancelled
                type: string
              - type: 'null'
            description: Only jobs in this state.
            title: Status
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_TemplateJobOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '403':
          description: The API key lacks the 'structure' scope.
        '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.
      security:
        - HTTPBearer: []
      summary: List template apply jobs
      tags:
        - templates
  /v1/template-jobs/{job_id}:
    get:
      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
      parameters:
        - in: path
          name: job_id
          required: true
          schema:
            format: uuid
            title: Job Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateJobOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '403':
          description: The API key lacks the 'structure' scope.
        '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.
      security:
        - HTTPBearer: []
      summary: Get the state of a template apply job
      tags:
        - templates
  /v1/template-jobs/{job_id}/assign-me:
    post:
      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
      parameters:
        - in: path
          name: job_id
          required: true
          schema:
            format: uuid
            title: Job Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateSampleRowsOut'
          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: 'The job has not finished yet: poll it until `status` is `done`.'
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateApplyProblem'
          description: >-
            The job did not record the rows it inserted, so they cannot be
            identified: `detail` names the refusal (`code`).
        '429':
          description: Rate limited.
        '503':
          description: Upstream unavailable (data store, or the template executor).
      security:
        - HTTPBearer: []
      summary: Put the caller in the empty user columns of a template apply
      tags:
        - templates
  /v1/template-jobs/{job_id}/sample-rows/delete:
    post:
      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
      parameters:
        - in: path
          name: job_id
          required: true
          schema:
            format: uuid
            title: Job Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateSampleRowsOut'
          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: 'The job has not finished yet: poll it until `status` is `done`.'
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateApplyProblem'
          description: >-
            The job did not record the rows it inserted, so they cannot be
            identified: `detail` names the refusal (`code`).
        '429':
          description: Rate limited.
        '503':
          description: Upstream unavailable (data store, or the template executor).
      security:
        - HTTPBearer: []
      summary: Remove the rows a template apply inserted
      tags:
        - templates
  /v1/templates:
    get:
      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
      parameters:
        - description: 'Only this scope: organization|workspace|module|feature.'
          in: query
          name: scope
          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 visibility: system|org|public.'
          in: query
          name: visibility
          required: false
          schema:
            anyOf:
              - enum:
                  - system
                  - org
                  - public
                type: string
              - type: 'null'
            description: 'Only this visibility: system|org|public.'
            title: Visibility
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_TemplateOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          description: Validation error (e.g. a malformed cursor).
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List templates
      tags:
        - templates
  /v1/templates/{template_id}:
    get:
      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
      parameters:
        - in: path
          name: template_id
          required: true
          schema:
            format: uuid
            title: Template Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateDetailOut'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: Get a template
      tags:
        - templates
  /v1/templates/{template_id}/apply:
    post:
      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
      parameters:
        - in: path
          name: template_id
          required: true
          schema:
            format: uuid
            title: Template Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TemplateApplyIn'
        required: true
      responses:
        '202':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateApplyOut'
          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: 'Conflict: this template is already being applied to this workspace.'
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateApplyProblem'
          description: >-
            The request cannot be applied as asked: `detail` names the refusal
            (`code`) and the ref to fix.
        '429':
          description: Rate limited.
        '503':
          description: Upstream unavailable (data store, or the template executor).
      security:
        - HTTPBearer: []
      summary: Apply a template to a workspace
      tags:
        - templates
  /v1/templates/{template_id}/mapping-suggestions:
    post:
      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
      parameters:
        - in: path
          name: template_id
          required: true
          schema:
            format: uuid
            title: Template Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TemplateMappingSuggestionsIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateMappingSuggestionsOut'
          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: 'Conflict: this template is already being applied to this workspace.'
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateApplyProblem'
          description: >-
            The request cannot be applied as asked: `detail` names the refusal
            (`code`) and the ref to fix.
        '429':
          description: Rate limited.
        '503':
          description: Upstream unavailable (data store, or the template executor).
      security:
        - HTTPBearer: []
      summary: Suggest how a template's dependencies map onto a workspace
      tags:
        - templates
  /v1/views:
    get:
      description: Cursor-paginated. Optionally filtered to a single feature.
      operationId: list_views
      parameters:
        - description: Only views of this feature.
          in: query
          name: feature_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            description: Only views of this feature.
            title: Feature Id
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_ViewOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          description: Validation error (e.g. a malformed cursor).
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List views
      tags:
        - views
    post:
      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
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ViewCreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ViewOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Create a view
      tags:
        - views
  /v1/views/{view_id}:
    delete:
      description: Removes the saved view; the feature's data is not touched.
      operationId: delete_view
      parameters:
        - in: path
          name: view_id
          required: true
          schema:
            format: uuid
            title: View Id
            type: string
        - in: header
          name: x-outdo-service-key
          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.
      security:
        - HTTPBearer: []
      summary: Delete a view
      tags:
        - views
    get:
      description: >-
        Returns 404 when the view does not exist or is not visible to the
        caller.
      operationId: get_view
      parameters:
        - in: path
          name: view_id
          required: true
          schema:
            format: uuid
            title: View Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ViewOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get a view
      tags:
        - views
    patch:
      description: >-
        Partial update — only the fields present in the request body are
        changed.
      operationId: update_view
      parameters:
        - in: path
          name: view_id
          required: true
          schema:
            format: uuid
            title: View Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ViewPatchIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ViewOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Update a view
      tags:
        - views
  /v1/workflows:
    get:
      description: Cursor-paginated. Optionally filtered to a single workspace.
      operationId: list_workflows
      parameters:
        - description: Only workflows of this workspace.
          in: query
          name: workspace_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            description: Only workflows of this workspace.
            title: Workspace Id
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_WorkflowOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List workflows
      tags:
        - workflows
  /v1/workflows-v2:
    get:
      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
      parameters:
        - description: Only workflows of this workspace.
          in: query
          name: workspace_id
          required: false
          schema:
            anyOf:
              - format: uuid
                type: string
              - type: 'null'
            description: Only workflows of this workspace.
            title: Workspace Id
        - description: Only the currently firing versions.
          in: query
          name: active
          required: false
          schema:
            anyOf:
              - type: boolean
              - type: 'null'
            description: Only the currently firing versions.
            title: Active
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_WorkflowV2Out_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          description: Validation error (e.g. a malformed cursor).
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List workflow definitions
      tags:
        - workflows-v2
    post:
      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
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkflowV2CreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowV2Out'
          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: >-
            Conflict: another session wrote first, or there is no active
            version.
        '422':
          description: Validation error (e.g. a malformed cursor).
        '429':
          description: Rate limited.
        '503':
          description: Upstream unavailable (data store, or the workflow validator).
      security:
        - HTTPBearer: []
      summary: Create a workflow
      tags:
        - workflows-v2
  /v1/workflows-v2/validate:
    post:
      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
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkflowV2ValidateIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationOut'
          description: Successful Response
        '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: The workflow validator is unavailable.
      security:
        - HTTPBearer: []
      summary: Validate a workflow definition
      tags:
        - workflows-v2
  /v1/workflows-v2/{workflow_id}:
    get:
      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
      parameters:
        - in: path
          name: workflow_id
          required: true
          schema:
            format: uuid
            title: Workflow Id
            type: string
        - description: An exact version of this workflow.
          in: query
          name: version
          required: false
          schema:
            anyOf:
              - minimum: 1
                type: integer
              - type: 'null'
            description: An exact version of this workflow.
            title: Version
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowV2Out'
          description: Successful Response
        '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.
      security:
        - HTTPBearer: []
      summary: Get a workflow definition
      tags:
        - workflows-v2
  /v1/workflows-v2/{workflow_id}/apply:
    post:
      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
      parameters:
        - in: path
          name: workflow_id
          required: true
          schema:
            format: uuid
            title: Workflow Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowV2Out'
          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: >-
            Conflict: another session wrote first, or there is no active
            version.
        '422':
          description: Validation error (e.g. a malformed cursor).
        '429':
          description: Rate limited.
        '503':
          description: Upstream unavailable (data store, or the workflow validator).
      security:
        - HTTPBearer: []
      summary: Apply a workflow's draft
      tags:
        - workflows-v2
  /v1/workflows-v2/{workflow_id}/versions:
    post:
      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
      parameters:
        - in: path
          name: workflow_id
          required: true
          schema:
            format: uuid
            title: Workflow Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkflowV2VersionIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowV2Out'
          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: >-
            Conflict: another session wrote first, or there is no active
            version.
        '422':
          description: Validation error (e.g. a malformed cursor).
        '429':
          description: Rate limited.
        '503':
          description: Upstream unavailable (data store, or the workflow validator).
      security:
        - HTTPBearer: []
      summary: Add a version to a workflow
      tags:
        - workflows-v2
  /v1/workflows/{workflow_id}:
    get:
      description: >-
        Returns 404 when the workflow does not exist or is not visible to the
        caller.
      operationId: get_workflow
      parameters:
        - in: path
          name: workflow_id
          required: true
          schema:
            format: uuid
            title: Workflow Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get a workflow
      tags:
        - workflows
  /v1/workflows/{workflow_id}/runs:
    get:
      description: Newest first. Cursor-paginated.
      operationId: list_workflow_runs
      parameters:
        - in: path
          name: workflow_id
          required: true
          schema:
            format: uuid
            title: Workflow Id
            type: string
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_WorkflowRunOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List runs of a workflow
      tags:
        - workflows
  /v1/workflows/{workflow_id}/runs/{run_id}:
    get:
      description: Returns 404 when the run does not exist or is not visible to the caller.
      operationId: get_workflow_run
      parameters:
        - in: path
          name: workflow_id
          required: true
          schema:
            format: uuid
            title: Workflow Id
            type: string
        - in: path
          name: run_id
          required: true
          schema:
            format: uuid
            title: Run Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowRunOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get a workflow run
      tags:
        - workflows
  /v1/workflows/{workflow_id}/trigger:
    post:
      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
      parameters:
        - in: path
          name: workflow_id
          required: true
          schema:
            format: uuid
            title: Workflow Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkflowTriggerIn'
        required: true
      responses:
        '202':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowRunOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '403':
          description: Permission denied.
        '404':
          description: Not found / not visible.
        '409':
          description: The workflow is not active.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Trigger a workflow run
      tags:
        - workflows
  /v1/workspaces:
    get:
      description: >-
        Cursor-paginated. Returns only what row-level security lets the caller
        see.
      operationId: list_workspaces
      parameters:
        - description: Opaque cursor taken from `next_cursor` of the previous page.
          in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Opaque cursor taken from `next_cursor` of the previous page.
            title: Cursor
        - description: Page size, 1 to 200. Defaults to 25.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Page size, 1 to 200. Defaults to 25.
            minimum: 1
            title: Limit
            type: integer
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page_WorkspaceOut_'
          description: Successful Response
        '401':
          description: Not authenticated.
        '422':
          description: Validation error (e.g. a malformed cursor).
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: List workspaces
      tags:
        - workspaces
    post:
      description: The caller must be a member of the target organization.
      operationId: create_workspace
      parameters:
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkspaceCreateIn'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Create a workspace
      tags:
        - workspaces
  /v1/workspaces/{workspace_id}:
    delete:
      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
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema:
            format: uuid
            title: Workspace Id
            type: string
        - description: Confirm deleting a NON-EMPTY workspace.
          in: query
          name: force
          required: false
          schema:
            default: false
            description: Confirm deleting a NON-EMPTY workspace.
            title: Force
            type: boolean
        - in: header
          name: x-outdo-service-key
          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.
      security:
        - HTTPBearer: []
      summary: Delete a workspace
      tags:
        - workspaces
    get:
      description: >-
        Returns 404 when the workspace does not exist or is not visible to the
        caller.
      operationId: get_workspace
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema:
            format: uuid
            title: Workspace Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceOut'
          description: Successful Response
        '401':
          description: Not authenticated.
        '404':
          description: Not found / not visible.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Get a workspace
      tags:
        - workspaces
    patch:
      description: >-
        Partial update — only the fields present in the request body are
        changed.
      operationId: update_workspace
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema:
            format: uuid
            title: Workspace Id
            type: string
        - in: header
          name: x-outdo-service-key
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: X-Outdo-Service-Key
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkspacePatchIn'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceOut'
          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: Conflict.
        '422':
          description: Validation error.
        '429':
          description: Rate limited.
        '503':
          description: Upstream data store unavailable.
      security:
        - HTTPBearer: []
      summary: Update a workspace
      tags:
        - workspaces
servers:
  - url: https://api.out-do.app
  - url: https://api.dev.out-do.app
tags:
  - description: Service liveness.
    name: health
  - description: Sign-in and token refresh, delegated to the platform's identity provider.
    name: auth
  - description: Organizations the caller belongs to.
    name: organizations
  - description: Workspaces — the top-level containers of the model.
    name: workspaces
  - description: Navigation modules inside a workspace.
    name: modules
  - description: >-
      Navigation rails inside a module; a rail's config lists the views it
      shows.
    name: rails
  - description: Features (data tables) and their column definitions.
    name: features
  - description: 'Row data: CRUD, bulk insert, filtering, sorting, and aggregates.'
    name: rows
  - description: Saved views over a feature (table, kanban, form, …).
    name: views
  - description: The caller's whole visible model in one call, plus config definitions.
    name: schema
  - description: File uploads and signed downloads for file columns.
    name: files
  - description: Workflow definitions, triggering, and run status.
    name: workflows
  - description: >-
      Messages posted to a scope (channel, feature, row, workspace, or
      organization).
    name: messages
  - description: Scoped API keys (machine identities) — create, list, and revoke.
    name: api-keys
  - 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: workflows-v2
  - 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: templates
  - description: 'The client-portal gateway: what an external (client) account may read.'
    name: external
  - description: Invitations and lifecycle of external (client-portal) accounts.
    name: external-accounts
  - description: >-
      AI control plane: per-organization model configuration, credit balance,
      usage records, and provider secrets. Not an inference endpoint.
    name: ai
  - description: >-
      Operational endpoints for platform administrators, outside the tenant
      surface.
    name: admin
