openapi: 3.1.0
info:
  title: Omni API
  description: |
    The Omni REST API provides programmatic access to your Omni instance for managing users, documents, queries, schedules, and more.  
  version: 1.0.0
  contact:
    name: Omni Support
    url: https://docs.omni.co

servers:
  - url: https://{instance}.omniapp.co/api
    description: Production
    variables:
      instance:
        default: blobsrus
        description: Your production Omni instance subdomain
  - url: https://{instance}.playground.exploreomni.dev/api
    description: Playground
    variables:
      instance:
        default: blobsrus
        description: Your playground Omni instance subdomain

security:
  - bearerAuth: []
  - orgApiKey: []

tags:
  - name: Who Am I
    description: Inspect your own user permissions
  - name: AI
    description: AI-powered query generation
  - name: AI Credit Controls
    description: Manage organization-level AI credit usage
  - name: AI Credit Usage
    description: Monitor AI credit usage
  - name: AI Evals
    description: Manage prompt sets and runs used to score AI quality against curated prompt suites.
  - name: AI Model Suggestions
    description: Manage AI-generated suggestions for shared models
  - name: AI Routines
    description: Manage Routines, which are scheduled, recurring AI-powered tasks that run automatically on your data
  - name: Documents v2
    description: |
      A draft-based workflow for creating and editing documents: create a document, patch a draft, then publish. Replaces the one-shot `PUT`/`PATCH` v1 document write endpoints.
  - name: Documents
    description: Create, retrieve, and manage documents
  - name: API Tokens
    description: Manage API tokens (Organization keys, Personal Access Tokens, MCP OAuth grants)
  - name: Connections
    description: Manage database connections
  - name: Connection environments
    description: Manage connection environments database connections
  - name: Content
    description: Unified content retrieval (documents and folders)
  - name: Content migration
    description: Export and import dashboards
  - name: Content validator
    description: Validate content against models and perform find/replace operations
  - name: Dashboard downloads
    description: Download dashboards and tiles as PDF, PNG, XLSX, CSV, or JSON files
  - name: Dashboard filters and controls
    description: Read and update dashboard filter and control default values
  - name: dbt
    description: Manage dbt configuration for connections
  - name: Document favorites
    description: Favorite and unfavorite documents
  - name: Document labels
    description: Apply and manage labels on documents
  - name: Document permissions
    description: Manage document-level access
  - name: Labels
    description: |
      Manage labels in the organization
  - name: Folders
    description: Create and organize content folders
  - name: Folder labels
    description: Apply and manage labels on folders
  - name: Folder permissions
    description: Manage folder-level access
  - name: Jobs
    description: Check status of asynchronous jobs
  - name: Models
    description: Create and manage data models
  - name: Model branches
    description: Manage model branches and merge changes
  - name: Model git configuration
    description: Manage git configuration for shared models
  - name: Queries
    description: Execute workbook queries
  - name: Schedules
    description: Create and manage scheduled tasks
  - name: Schedule recipients
    description: Manage schedule recipients
  - name: Schema refresh schedules
    description: Manage automated schema refresh schedules for connections
  - name: Topics
    description: Retrieve topic information from models
  - name: Uploads
    description: Manage file uploads
  - name: Users
    description: Manage users
  - name: User attributes
    description: Manage user attribute definitions
  - name: User groups
    description: Manage user groups
  - name: User model roles
    description: Manage model and connection role assignments for users
  - name: User group model roles
    description: Manage model and connection role assignments for user groups
  - name: Uploads
    description: Manage CSV and spreadsheet uploads

paths:
  /v1/whoami:
    get:
      description: |
        Retrieve the authenticated caller's own identity, API key scope, organization role, and resolved per-model permissions. Useful for allowing a caller to determine if they can execute a specific action without attempting it. Pass `modelId` to scope `rolesByModel` to specific models.

        This endpoint can be used by any user with a valid API key.
      operationId: whoami
      summary: Get current identity and permissions
      tags:
        - Who Am I
      parameters:
        - name: modelId
          in: query
          schema:
            type: string
            example: 550e8400-e29b-41d4-a716-446655440000
          required: false
          description: |
            Optional model filter. A single model ID or a comma-separated list.
            
            When provided, `rolesByModel` in the response will contain only these models. When omitted, models the caller can access are returned up to a limit; see `rolesByModelTruncated`.
      responses:
        '200':
          description: Caller's identity, key scope, org role, and per-model permissions
          content:
            application/json:
              schema:
                type: object
                required:
                  - keyScope
                  - orgRole
                  - rolesByModel
                  - user
                properties:
                  keyScope:
                    type: string
                    enum:
                      - user
                      - organization
                    description: |
                      Scope of the API key in use.
                      
                      - `user` - Personal Access Token. This is a user-scoped key (PAT/OAuth) that acts as a single user and cannot use SCIM, regardless of the user's organization role.
                      - `organization` - Organization API key
                  orgRole:
                    type: string
                    enum:
                      - MEMBER
                      - ORG_ADMIN
                    description: The caller's organization role.
                    example: MEMBER
                  rolesByModel:
                    type: object
                    additionalProperties:
                      $ref: '#/components/schemas/WhoamiModelRole'
                    description: Resolved role and effective permissions per model, keyed by model ID. Connection role resolves per shared model, so this is per-model rather than a single global role.
                  rolesByModelTruncated:
                    type: boolean
                    description: Present and `true` when `rolesByModel` was truncated because the caller can access more models than the unfiltered limit. Pass a `modelId` filter to retrieve specific models.
                  user:
                    $ref: '#/components/schemas/WhoamiUser'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '404':
          description: One or more requested `modelId`s do not exist or are not accessible to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'

  /v1/ai/branding:
    get:
      tags:
        - AI
      summary: Get AI Agent branding
      description: |
        Return the organization's AI Agent's [branding configuration](/ai/settings/branding), including display name, custom logo URL, and copy for AI Agent landing surfaces. Falls back to Omni's default values when custom branding hasn't been configured.
      security:
        - bearerAuth: []
      operationId: getAiBranding
      responses:
        '200':
          description: Successfully retrieved AI Agent branding
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                    description: The display name for the AI Agent. Defaults to `Omni Agent` if branding hasn't been configured.
                    example: "Omni AI"
                  logoUrl:
                    type: string
                    nullable: true
                    description: Absolute URL to the custom logo image for the AI Agent. Returns `null` if no custom logo is configured.
                    example: "https://example.com/custom-logo.png"
                  headline:
                    type: string
                    description: Headline text displayed on AI Agent landing surfaces
                    example: "Welcome to Omni AI"
                  body:
                    type: string
                    description: Body copy displayed on AI Agent landing surfaces
                    example: "Ask questions about your data in natural language"
                  promptPlaceholder:
                    type: string
                    description: Placeholder text for the AI prompt input field
                    example: "Ask a question about your data..."
              examples:
                customBranding:
                  summary: Custom branding
                  value:
                    displayName: "DataBot"
                    logoUrl: "https://example.com/custom-logo.png"
                    headline: "Welcome to DataBot"
                    body: "Your intelligent data assistant"
                    promptPlaceholder: "What would you like to know?"
                defaultBranding:
                  summary: Default branding (fallback)
                  value:
                    displayName: "Omni AI"
                    logoUrl: null
                    headline: "Welcome to Omni AI"
                    body: "Ask questions about your data in natural language"
                    promptPlaceholder: "Ask a question about your data..."
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:
            - `Insufficient permissions` - User does not have AI permissions on any model
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/generate-query:
    post:
      tags:
        - AI
      summary: Generate a query
      description: |
        Generate a structured Omni query from natural language using AI. 

        For exploratory or cross-topic questions, use [Create AI job](/api/ai/create-ai-job) instead.
      security:
        - bearerAuth: []
      operationId: generateQuery
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - modelId
                - prompt
              properties:
                modelId:
                  type: string
                  format: uuid
                  description: The ID of the model to run the query against
                currentTopicName:
                  type: string
                  description: Name of the base topic/table. If left empty, AI will automatically choose a topic based on the question.
                prompt:
                  type: string
                  description: Natural language instruction describing the desired query output
                workbookUrl:
                  type: boolean
                  description: When `true`, returns an Omni workbook URL that opens and runs the generated query
                branchId:
                  type: string
                  description: The ID of the model branch to use to generate the query. If not provided, the `main` branch will be used. To retrieve branch IDs, use the [List models](/api/models/list-models) endpoint with `modelKind=BRANCH`.
                runQuery:
                  type: boolean
                  default: true
                  description: |
                    When `true` (default), the generated query is executed against the database and results are included in the response. 
                    
                    When `false`, only the generated query object is returned without executing it. Set to `false` to preview what query would be generated without incurring a database query.
                contextQuery:
                  type: object
                  description: A query object to provide as context. Use this to reference previous queries or provide additional context for the generation.
                queryAllViews:
                  type: boolean
                  default: false
                  description: |
                    When `true` and the model's [`query_all_views_and_fields`](/modeling/models/parameters/ai-settings/query-all-views-and-fields) setting is enabled, allows the AI to query views that are not included in topics. If the setting is disabled, this parameter has no effect.

                    When both the API parameter and model setting are enabled:
                    
                    - The AI can select from any view in the model, including views not in topics
                    - The AI will prefer topic-organized views unless the question clearly targets a standalone view

                    **Note:** Users with topic-locked permissions cannot use this parameter, even if the model setting is enabled.
            example:
              modelId: "123e4567-e89b-12d3-a456-426614174000"
              currentTopicName: "orders"
              prompt: "Show me total revenue by product category for the last quarter"
      responses:
        '200':
          description: Successfully generated query
          content:
            application/json:
              schema:
                type: object
                properties:
                  topic:
                    type: string
                    nullable: true
                    description: The name of the topic used for query generation. This will be populated if the AI used a topic and null if the query was generated using a base view.
                    example: "order_items"
                  baseView:
                    type: string
                    nullable: true
                    description: The name of the base view used for query generation. This will be populated if the AI used a view outside of a topic and null if the query was generated using a topic.
                    example: "users"
                  query:
                    type: object
                    properties:
                      model_job:
                        type: object
                        description: A structured Omni query object that can be used with the [Query run API](/api/queries/run-query)
                        properties:
                          model_id:
                            type: string
                            format: uuid
                            description: Model identifier
                          table:
                            type: string
                            description: Base table/topic name
                          fields:
                            type: array
                            items:
                              type: string
                            description: Array of field names to include in query
                          calculations:
                            type: array
                            items:
                              type: object
                            description: Custom calculations
                          filters:
                            type: object
                            description: Filter conditions
                          sorts:
                            type: array
                            items:
                              type: object
                              properties:
                                column_name:
                                  type: string
                                sort_descending:
                                  type: boolean
                                is_column_sort:
                                  type: boolean
                                null_sort:
                                  type: string
                            description: Sort specifications
                          limit:
                            oneOf:
                              - type: integer
                              - type: "null"
                            description: Result row limit
                          pivots:
                            type: array
                            items:
                              type: object
                            description: Pivot configurations
                          fill_fields:
                            type: array
                            items:
                              type: string
                            description: Fields to fill
                          column_totals:
                            type: object
                            description: Column totals configuration
                          row_totals:
                            type: object
                            description: Row totals configuration
                          column_limit:
                            type: integer
                            description: Column limit for pivots
                          default_group_by:
                            type: boolean
                            description: Enable default grouping
                          join_via_map:
                            type: object
                            description: Join configuration map
                          join_paths_from_topic_name:
                            type: string
                            description: Topic name for join paths
                          version:
                            type: integer
                            description: Query version
                          period_over_period_computations:
                            type: array
                            items:
                              type: object
                            description: Period over period calculations
                          query_references:
                            type: object
                            description: Query references
                          metadata:
                            type: object
                            description: Query metadata
                          custom_summary_types:
                            type: object
                            description: Custom summary type definitions
              example:
                query:
                  model_job:
                    model_id: "bcf0cffd-ec1b-44d5-945a-a261ebe407fc"
                    table: "order_items"
                    fields:
                      - "products.item_name"
                      - "order_items.total_sale_price"
                    calculations: []
                    filters: {}
                    sorts:
                      - column_name: "order_items.total_sale_price"
                        sort_descending: true
                        is_column_sort: false
                        null_sort: "OMNI_DEFAULT"
                    limit: 10
                    pivots: []
                    fill_fields: []
                    column_totals: {}
                    row_totals: {}
                    column_limit: 50
                    default_group_by: true
                    join_via_map: {}
                    join_paths_from_topic_name: "order_items"
                    version: 5
                    period_over_period_computations: []
                    query_references: {}
                    metadata: {}
                    custom_summary_types: {}
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid method`
            - `Invalid JSON`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Feature not enabled`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/pick-topic:
    post:
      tags:
        - AI
      summary: Pick topic
      description: |
        Analyze a natural language prompt and determines which topic in the model is the best fit for answering the question. 
        
        Useful as a preprocessing step before calling the [Generate AI query](/api/ai/generate-a-query) or [Create AI job](/api/ai/create-ai-job) endpoints, especially when the user's question could relate to multiple topics.
      security:
        - bearerAuth: []
      operationId: aiPickTopic
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AiPickTopicBody'
      responses:
        '200':
          description: Topic selected successfully. The returned `topicId` can be used as the `topicName` parameter in other AI endpoints.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AiPickTopicResponse'
        '400':
          description: Invalid request body. The `prompt` or `modelId` may be missing or malformed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions. Requires the **Querier** role on the specified model.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: The specified model was not found, or no accessible topics exist in the model.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          description: AI service error.

  /v1/ai/conversations:
    get:
      tags:
        - AI
      summary: List AI conversations
      description: |
        List a user's recent AI conversations, ordered by most-recent activity. Each conversation includes an ID that can be passed as `conversationId` to subsequent [Create AI job](/api/ai/create-ai-job) requests to continue the thread.

        Returns a paginated list with conversation metadata including optional names and the most recent user prompt for display purposes.
      security:
        - bearerAuth: []
      operationId: listAIConversations
      parameters:
        - name: userId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: |
            **Requires an Organization API key.** Filter conversations to a specific user by their membership ID.

            User-scoped tokens automatically see only their own conversations and cannot use this parameter. Organization API keys can optionally filter by user or see all conversations in the organization.
          example: 9e8719d9-276a-4964-9395-a493189a247c
        - name: pageSize
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Number of conversations to return per page.
        - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: Pagination cursor from a previous response's `pageInfo.nextCursor`. Use to fetch the next page of results.
      responses:
        '200':
          description: Successfully retrieved conversations
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pageInfo
                properties:
                  data:
                    type: array
                    description: List of AI conversations
                    items:
                      type: object
                      required:
                        - id
                        - updatedAt
                        - createdAt
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: Unique identifier for the conversation. Pass this as `conversationId` when creating AI jobs to continue the thread.
                        name:
                          type: string
                          nullable: true
                          description: Optional name for the conversation. May be `null` if no name has been set.
                        lastUserPrompt:
                          type: string
                          nullable: true
                          description: The most recent user prompt in the conversation, suitable for display as a one-line summary. May be `null` for empty conversations.
                        updatedAt:
                          type: string
                          format: date-time
                          description: Timestamp of the most recent activity in the conversation in ISO 8601 format.
                        createdAt:
                          type: string
                          format: date-time
                          description: Timestamp when the conversation was created in ISO 8601 format.
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
              example:
                data:
                  - id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    name: "Q1 Revenue Analysis"
                    lastUserPrompt: "Show me revenue trends by region for Q1"
                    updatedAt: "2026-05-15T14:32:10Z"
                    createdAt: "2026-05-15T09:15:30Z"
                  - id: "c3d4e5f6-a7b8-9012-cdef-123456789012"
                    name: null
                    lastUserPrompt: "What were our top selling products last month?"
                    updatedAt: "2026-05-14T16:20:45Z"
                    createdAt: "2026-05-14T16:20:45Z"
                pageInfo:
                  hasNextPage: false
                  nextCursor: null
                  pageSize: 50
                  totalRecords: 2
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid userId format` - `userId` must be a valid UUID
            - `Invalid pageSize` - `pageSize` must be an integer between 1 and 100
            - `Invalid nextCursor` - pagination `cursor` is malformed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:
            - `Insufficient permissions` - User lacks ability to use AI on any model in the organization
            - `User-scoped tokens cannot filter by userId` - Personal Access Tokens cannot use the `userId` parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - `User not found` - the specified `userId` does not exist in the organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/conversations/{conversationId}:
    get:
      tags:
        - AI
      summary: Get conversation
      description: |
        Retrieve a conversation with its full message history — alternating user and assistant turns. Each assistant turn carries the originating `jobId` and an `omniChatUrl` deep link.

        The conversations returned by this API depend on the type of API key being used:

        - Organization API keys can access any conversation in the organization
        - Personal Access Tokens can only access the authenticating user's conversations
      security:
        - bearerAuth: []
      operationId: getConversation
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the conversation
      responses:
        '200':
          description: Conversation retrieved successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - userId
                  - organizationId
                  - createdAt
                  - updatedAt
                  - messages
                properties:
                  id:
                    type: string
                    format: uuid
                    description: The unique identifier for this conversation.
                    example: "660e8400-e29b-41d4-a716-446655440001"
                  userId:
                    type: string
                    format: uuid
                    description: The user ID who created this conversation.
                    example: "990e8400-e29b-41d4-a716-446655440004"
                  organizationId:
                    type: string
                    format: uuid
                    description: The organization that owns this conversation.
                    example: "880e8400-e29b-41d4-a716-446655440003"
                  createdAt:
                    type: string
                    format: date-time
                    description: When the conversation was created.
                    example: "2025-01-15T10:00:00.000Z"
                  updatedAt:
                    type: string
                    format: date-time
                    description: When the conversation was last modified.
                    example: "2025-01-15T10:01:30.000Z"
                  messages:
                    type: array
                    description: Ordered list of messages in the conversation, alternating between user and assistant turns.
                    items:
                      type: object
                      required:
                        - role
                        - content
                        - timestamp
                      properties:
                        role:
                          type: string
                          enum: [user, assistant]
                          description: The role of the message sender.
                          example: "user"
                        content:
                          type: string
                          description: The message content. For user messages, this is the prompt. For assistant messages, this is the AI's response in Markdown format.
                          example: "What are the top 5 products by revenue?"
                        timestamp:
                          type: string
                          format: date-time
                          description: When this message was created.
                          example: "2025-01-15T10:00:00.000Z"
                        jobId:
                          type: string
                          format: uuid
                          description: |
                            **Only present for assistant messages.** The ID of the AI job that generated this response. Use this with the [Get AI job status](/api/ai/get-ai-job-status) endpoint to retrieve job details.
                          example: "550e8400-e29b-41d4-a716-446655440000"
                        omniChatUrl:
                          type: string
                          format: uri
                          description: |
                            **Only present for assistant messages.** URL to view this conversation in the Omni chat interface. Opens the chat session where the job actions and results are visible.
                          example: "https://my-org.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
              examples:
                conversation:
                  summary: A conversation with multiple turns
                  value:
                    id: "660e8400-e29b-41d4-a716-446655440001"
                    userId: "990e8400-e29b-41d4-a716-446655440004"
                    organizationId: "880e8400-e29b-41d4-a716-446655440003"
                    createdAt: "2025-01-15T10:00:00.000Z"
                    updatedAt: "2025-01-15T10:05:00.000Z"
                    messages:
                      - role: "user"
                        content: "What are the top 5 products by revenue?"
                        timestamp: "2025-01-15T10:00:00.000Z"
                      - role: "assistant"
                        content: "### Top 5 Products by Revenue\n\n1. **Sunglasses** - $678,994\n2. **Jeans** - $475,072\n3. **T-Shirt** - $320,150\n4. **Sneakers** - $289,033\n5. **Hat** - $201,487"
                        timestamp: "2025-01-15T10:01:30.000Z"
                        jobId: "550e8400-e29b-41d4-a716-446655440000"
                        omniChatUrl: "https://my-org.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
                      - role: "user"
                        content: "Show me revenue by month for sunglasses"
                        timestamp: "2025-01-15T10:03:00.000Z"
                      - role: "assistant"
                        content: "### Sunglasses Revenue by Month\n\n- January: $52,430\n- February: $48,221\n- March: $61,992"
                        timestamp: "2025-01-15T10:05:00.000Z"
                        jobId: "550e8400-e29b-41d4-a716-446655440010"
                        omniChatUrl: "https://my-org.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid conversation ID format. Must be a valid UUID.`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: |
            Unauthorized

            Possible error messages:
            - `Missing or invalid API key`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:
            - `User does not have USE_AI permission`
            - `User-scoped token cannot access another user's conversation`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "404"
                detail: "Conversation not found"
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/credit-controls:
    get:
      description: |
        <Note>
          **Organization Admin** permissions are required for this endpoint.
        </Note>

        Get the organization's AI credit controls: the downgrade and shutoff thresholds, the default per-user and per-entity-group credit limits, plus read-only context (the credit limit, usage for the current billing period, and the period bounds). This endpoint can be used for embedded instances.
      operationId: aiCreditControlsGet
      summary: Get AI credit controls
      tags:
        - AI Credit Controls
      responses:
          '200':
            description: Current credit controls. Thresholds are `null` when the corresponding control is off.
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/AiCreditControlsResponse'
          '401':
            description: Missing or invalid API key.
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ApiError401'
          '403':
            description: Insufficient permissions or AI credit controls are not enabled for the organization.
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ApiError403'
          '429':
            $ref: '#/components/responses/TooManyRequests'
          '500':
            $ref: '#/components/responses/InternalServerError'

    patch:
      description: |
        <Note>
          **Organization Admin** permissions are required for this endpoint.
        </Note>

        Update the organization's AI credit controls. This endpoint can be used for embedded instances.
        
        All fields are optional, but at least one must be provided:

        - Omit a field to leave it unchanged
        - Send `null` to turn that control off (for `userDefaultCredits` and `entityGroupDefaultCredits`: unlimited by default)
        - Send a non-negative number to set it
      operationId: aiCreditControlsUpdate
      summary: Update AI credit controls
      tags:
        - AI Credit Controls
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                downgradeCredits:
                  type: number
                  nullable: true
                  minimum: 0
                  description: Credit usage at which AI downgrades to a cheaper model. Omit to leave unchanged, `null` to turn off, or a non-negative number to set. Must be less than or equal to `shutoffCredits`.
                  example: 800
                shutoffCredits:
                  type: number
                  nullable: true
                  minimum: 0
                  description: Credit usage at which AI shuts off entirely. Omit to leave unchanged, `null` to turn off, or a non-negative number to set.
                  example: 1200
                userDefaultCredits:
                  type: number
                  nullable: true
                  minimum: 0
                  description: Default per-user AI credit limit for the billing period. This limit is what what every user without an individual limit gets. Omit to leave unchanged, `null` for unlimited by default, or a non-negative number to set.
                  example: 100
                entityGroupDefaultCredits:
                  type: number
                  nullable: true
                  minimum: 0
                  description: Default per-entity-group AI credit limit for the billing period. This limit is what every embed entity group without an individual limit gets. Omit to leave unchanged, `null` for unlimited by default, or a non-negative number to set.
                  example: 100
      responses:
        '200':
          description: Thresholds updated. Returns the full current state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AiCreditControlsResponse'
        '400':
          description: |
            Invalid request. Common causes:

            - Empty body
            - A negative value
            - An unknown field
            - `downgradeCredits` greater than `shutoffCredits`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Insufficient permissions or AI credit controls are not enabled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/credit-controls/users:
    get:
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        List the organization's active individual user AI credit limits, ordered by `userId` ascending.

        Only users with an individual limit will be returned in the response.
      operationId: aiCreditControlsUsersList
      summary: List individual users' AI credit limits
      tags:
        - AI Credit Controls
      parameters:
        - schema:
            type: string
            description: Cursor for pagination from previous response `nextCursor`
            example: eyJpZCI6IjEyMzQ1In0
          required: false
          description: Cursor for pagination from previous response `nextCursor`
          name: cursor
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            description: Number of results per page
            example: 20
          required: false
          description: Number of results per page
          name: pageSize
          in: query
      responses:
        '200':
          description: One page of users' individual AI credit limits.
          content:
            application/json:
              schema:
                type: object
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    items:
                      type: object
                      properties:
                        creditLimit:
                          type: number
                          nullable: true
                          minimum: 0
                          description: The user's individual AI credit limit, or `null` for an explicit unlimited override.
                          example: 50
                        userId:
                          type: string
                          description: The user's id within this organization.
                          example: f4a2b3c8-0d1e-4f5a-9b6c-7d8e9f0a1b2c
                      required:
                        - creditLimit
                        - userId
                    description: Users with an individual AI credit limit, ordered by `userId` ascending.
                required:
                  - pageInfo
                  - records
              examples:
                users_with_limits:
                  summary: Example response with users
                  value:
                    pageInfo:
                      nextCursor: eyJpZCI6IjEyMzQ1In0
                      hasNextPage: true
                    records:
                      - userId: f4a2b3c8-0d1e-4f5a-9b6c-7d8e9f0a1b2c
                        creditLimit: 50
                      - userId: a7b8c9d0-e1f2-3a4b-5c6d-7e8f9a0b1c2d
                        creditLimit: null
        '400':
          description: Invalid cursor or pageSize.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Insufficient permissions, or per-user AI credit limits are not enabled for the organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    patch:
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        Set individual users' AI credit limits in bulk. Each entry names a user and either sets an individual limit or removes one so the user follows the organization default. All entries are applied in a single transaction.
      operationId: aiCreditControlsUsersUpdate
      summary: Set individual users' AI credit limits
      tags:
        - AI Credit Controls
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                users:
                  type: array
                  items:
                    type: object
                    properties:
                      creditLimit:
                        type: number
                        nullable: true
                        minimum: 0
                        description: The user's individual AI credit limit for the billing period, or `null` for unlimited. This will override the organization default. Mutually exclusive with `useDefaultLimit`.
                        example: 50
                      useDefaultLimit:
                        type: boolean
                        enum:
                          - true
                        description: Removes the user's individual limit so they follow the organization default. Mutually exclusive with `creditLimit`.
                      userId:
                        type: string
                        description: The user's ID within this organization.
                        example: f4a2b3c8-0d1e-4f5a-9b6c-7d8e9f0a1b2c
                    required:
                      - userId
                    additionalProperties: false
                  minItems: 1
                  maxItems: 1000
                  description: 'Users to update, at most 1000 per request. Each entry has a `userId` plus exactly one of `creditLimit` (number or `null`) or `useDefaultLimit: true`.'
              required:
                - users
              additionalProperties: false
            examples:
              set_limits:
                summary: Set limits for two users
                value:
                  users:
                    - userId: f4a2b3c8-0d1e-4f5a-9b6c-7d8e9f0a1b2c
                      creditLimit: 50
                    - userId: a7b8c9d0-e1f2-3a4b-5c6d-7e8f9a0b1c2d
                      creditLimit: null
              reset_to_default:
                summary: Remove individual limit
                value:
                  users:
                    - userId: f4a2b3c8-0d1e-4f5a-9b6c-7d8e9f0a1b2c
                      useDefaultLimit: true
      responses:
        '200':
          description: All entries applied. Returns each user's effective limit, in request order.
          content:
            application/json:
              schema:
                type: object
                properties:
                  users:
                    type: array
                    items:
                      type: object
                      properties:
                        creditLimit:
                          type: number
                          nullable: true
                          minimum: 0
                          description: The user's effective AI credit limit, or `null` for unlimited.
                          example: 50
                        userId:
                          type: string
                          description: The user's ID within this organization.
                          example: f4a2b3c8-0d1e-4f5a-9b6c-7d8e9f0a1b2c
                        usesDefaultLimit:
                          type: boolean
                          description: |
                            If `true`, the user has no individual limit and follows the organization default.
                      required:
                        - creditLimit
                        - userId
                        - usesDefaultLimit
                
        '400':
          description: |
            Invalid request. Common causes:

            - An empty users array
            - More than 1000 entries
            - An entry with both `creditLimit` and `useDefaultLimit` (or neither)
            - A negative `creditLimit`
            - A duplicated `userId`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Insufficient permissions, per-user AI credit limits are not enabled, or credit controls editing is disabled for the organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: Specified `userId` is not a member of the organization; the response names the first invalid ID. No limits are changed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/credit-controls/entity-groups:
    get:
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        List the organization's active individual embed entity-group AI credit limits, ordered by entity group id ascending.

        Only entity groups with an individual limit will be returned in the response. Entity groups that use the organization default are not included.
      operationId: aiCreditControlsEntityGroupsList
      summary: List individual entity groups' AI credit limits
      tags:
        - AI Credit Controls
      parameters:
        - schema:
            type: string
            description: Cursor for pagination from previous response `nextCursor`
            example: eyJpZCI6IjEyMzQ1In0
          required: false
          description: Cursor for pagination from previous response `nextCursor`
          name: cursor
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            description: Number of results per page
            example: 20
          required: false
          description: Number of results per page
          name: pageSize
          in: query
      responses:
        '200':
          description: One page of entity groups' individual AI credit limits.
          content:
            application/json:
              schema:
                type: object
                required:
                  - pageInfo
                  - records
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    description: Entity groups with an individual AI credit limit, ordered by entity group ID.
                    items:
                      type: object
                      properties:
                        creditLimit:
                          type: number
                          nullable: true
                          minimum: 0
                          description: The entity group's individual AI credit limit, or `null` for an explicit unlimited override.
                          example: 50
                        entity:
                          type: string
                          description: The embed entity's identifier (the SSO `entity` value).
                          example: blobsrus
                      required:
                        - creditLimit
                        - entity
              examples:
                entityGroupsWithLimits:
                  summary: Example response with entity groups
                  value:
                    pageInfo:
                      nextCursor: eyJpZCI6IjEyMzQ1In0
                      hasNextPage: true
                    records:
                      - entity: blobsrus
                        creditLimit: 50
                      - entity: blobsrus-eu
                        creditLimit: null
        '400':
          description: |
            Invalid cursor or `pageSize`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Insufficient permissions, or per-entity-group AI credit limits are not enabled for the organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    patch:
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        Set individual embed entity groups' AI credit limits in bulk. Each entry names an entity group and either sets an individual limit or removes one so the entity group follows the organization default. All entries are applied in a single transaction.
      x-mint:
        content: |
          <Tip>
            To set an unlimited override for an entity group, use `creditLimit: null`. This is distinct from having the entity group follow the organization default.
          </Tip>
      operationId: aiCreditControlsEntityGroupsUpdate
      summary: Set individual entity groups' AI credit limits
      tags:
        - AI Credit Controls
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - entityGroups
              additionalProperties: false
              properties:
                entityGroups:
                  type: array
                  minItems: 1
                  maxItems: 1000
                  description: |
                    Entity groups to update. Each entry has an `entity` plus exactly one of `creditLimit` (number or `null`) or `useDefaultLimit: true`.
                  items:
                    type: object
                    required:
                      - entity
                    additionalProperties: false
                    properties:
                      creditLimit:
                        type: number
                        nullable: true
                        minimum: 0
                        description: The entity group's individual AI credit limit for the billing period, or `null` for unlimited. This will override the organization default. Mutually exclusive with `useDefaultLimit`.
                        example: 50
                      entity:
                        type: string
                        description: The embed entity's identifier (the SSO `entity` value).
                        example: blobsrus
                      useDefaultLimit:
                        type: boolean
                        description: Removes the entity group's individual limit so it follows the organization default. Mutually exclusive with `creditLimit`.
            examples:
              'Set limits for 2 entity groups':
                summary: Set limits for two entity groups
                value:
                  entityGroups:
                    - entity: blobsrus
                      creditLimit: 50
                    - entity: blobsrus-eu
                      creditLimit: null
              'Remove individual limit':
                summary: Remove individual limit
                value:
                  entityGroups:
                    - entity: blobsrus
                      useDefaultLimit: true
      responses:
        '200':
          description: All entries applied. Returns each entity group's effective limit, in request order.
          content:
            application/json:
              schema:
                type: object
                required:
                  - entityGroups
                properties:
                  entityGroups:
                    type: array
                    items:
                      type: object
                      required:
                        - creditLimit
                        - entity
                        - usesDefaultLimit
                      properties:
                        creditLimit:
                          type: number
                          nullable: true
                          minimum: 0
                          description: The entity group's effective AI credit limit, or `null` for unlimited.
                          example: 50
                        entity:
                          type: string
                          description: The embed entity's identifier (the SSO `entity` value).
                          example: blobsrus
                        usesDefaultLimit:
                          type: boolean
                          description: |
                            If `true`, the entity group has no individual limit and follows the organization default.
        '400':
          description: |
            Invalid request. Common causes:

            - An empty `entityGroups` array
            - More than 1000 entries
            - An entry with both `creditLimit` and `useDefaultLimit`, or neither
            - A negative `creditLimit`
            - A duplicated `entity`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Insufficient permissions, per-entity-group AI credit limits are not enabled, or credit controls editing is disabled for the organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: An entity has no entity group in the organization; the response names the first invalid entity. No limits are changed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/credit-usage/users:
    post:
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        List individual users' (up to 1,000 unique users per request) AI credit usage for the current billing period. Each user ID must be a member of the organization. Unknown, invalid, and duplicate IDs will fail the request.
        
        Returns each user's credits used, in request order, plus the billing-period bounds. Users with no current usage will report `0`. 
      operationId: aiCreditUsageUsersRead
      summary: Read individual users' AI credit usage
      tags:
        - AI Credit Usage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - userIds
              additionalProperties: false
              properties:
                userIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                    example: f4a2b3c8-0d1e-4f5a-9b6c-7d8e9f0a1b2c
                  minItems: 1
                  maxItems: 1000
                  description: |
                    Users to read usage for, at most 1000 per request. Each entry is a user's ID within this organization; each ID must be included only once. Duplicated IDs will result in an error.
      responses:
        '200':
          description: Each requested user's credit usage for the current billing period, in request order.
          content:
            application/json:
              schema:
                type: object
                required:
                  - periodEnd
                  - periodStart
                  - users
                properties:
                  periodEnd:
                    type: integer
                    minimum: 0
                    description: End of the current billing period as a Unix ms timestamp (UTC calendar-month boundary).
                  periodStart:
                    type: integer
                    minimum: 0
                    description: Start of the current billing period as a Unix ms timestamp (UTC calendar-month boundary).
                  users:
                    type: array
                    items:
                      type: object
                      required:
                        - creditsUsed
                        - userId
                      properties:
                        creditsUsed:
                          type: number
                          minimum: 0
                          description: Credits the user consumed in the current billing period. `0` when there is no usage yet.
                          example: 42
                        userId:
                          type: string
                          format: uuid
                          description: The user's ID within this organization.
                          example: f4a2b3c8-0d1e-4f5a-9b6c-7d8e9f0a1b2c
        '400':
          description: |
            Invalid request. Common causes: an empty `userIds` array, more than 1000 IDs, or a duplicated `userId`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Insufficient permissions, or per-user AI credit limits are not enabled for the organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: A `userId` is not a member of the organization; the response names the first invalid ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'

  /v1/ai/credit-usage/entity-groups:
    post:
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        List individual embed entity groups' (up to 1,000 unique entities per request) AI credit usage for the current billing period. Each included `entity` must have an entity group in the organization. Unknown, invalid, and duplicate `entity` values will fail the request.

        Returns each entity group's credits used, in request order, plus the billing-period bounds. Entity groups with no current usage will report `0`.
      operationId: aiCreditUsageEntityGroupsRead
      summary: Read individual entity groups' AI credit usage
      tags:
        - AI Credit Usage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - entities
              additionalProperties: false
              properties:
                entities:
                  type: array
                  items:
                    type: string
                    example: blobsrus
                  minItems: 1
                  maxItems: 1000
                  description: |
                    Entity groups to read usage for, at most 1000 per request. Each entry is an embed `entity` (the SSO `entity` value) corresponding to an entity group in the organization. Each entity must be included only once, as duplicated entries will fail the request.
      responses:
        '200':
          description: Each requested entity group's credit usage for the current billing period, in request order.
          content:
            application/json:
              schema:
                type: object
                required:
                  - periodEnd
                  - periodStart
                  - entityGroups
                properties:
                  periodEnd:
                    type: integer
                    minimum: 0
                    description: End of the current billing period as a Unix ms timestamp (UTC calendar-month boundary).
                  periodStart:
                    type: integer
                    minimum: 0
                    description: Start of the current billing period as a Unix ms timestamp (UTC calendar-month boundary).
                  entityGroups:
                    type: array
                    items:
                      type: object
                      required:
                        - creditsUsed
                        - entity
                      properties:
                        creditsUsed:
                          type: number
                          minimum: 0
                          description: Credits the entity group consumed in the current billing period. `0` when it has no usage yet.
                          example: 42
                        entity:
                          type: string
                          description: The embed entity's identifier (the SSO `entity` value).
                          example: blobsrus
        '400':
          description: |
            Invalid request. Common causes: an empty `entities` array, more than 1000 `entities`, or a duplicated entity.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Insufficient permissions, or per-entity-group AI credit limits are not enabled for the organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: An entity has no entity group in the organization; the response names the first invalid entity.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'

  /v1/ai/jobs:
    post:
      tags:
        - AI
      summary: Create AI job
      description: |
        Submit a new AI job for asynchronous execution. The AI will analyze the prompt, generate and execute queries against the specified model, and produce a summarized answer. 
        
        Jobs are processed by a background worker and typically complete within 15–60 seconds. Use [Get AI job status](/api/ai/get-ai-job-status) to poll for status, or configure a `webhookUrl` to receive a notification when the job completes. Optionally continue an existing conversation by providing a `conversationId`.
      x-mint:
        content: |
          ## Webhook notifications

          If a `webhookUrl` is configured, it will receive a `POST` with the following body when the job reaches a terminal state:

          ```json
          {
            "event_id": "evt_550e8400_COMPLETE_1706123456",
            "jobId": "550e8400-...",
            "status": "COMPLETE",
            "result_summary": "Generated revenue report showing $1.2M total revenue",
            "metadata": { "slack_channel": "C123456" },
            "completed_at": "2025-01-24T10:30:00Z"
          }
          ```

          The request will also include the following signature headers:
          
          - `X-Omni-Signature-Timestamp` - `unix`
          - `X-Omni-Signature` - (`sha256=HMAC(timestamp.body, secret)`)

          <Note>
            The webhook payload includes `result_summary` which can contain actual data values from query results. Full result detail (CSV data, query definitions, actions) requires calling the [Stream AI job results endpoint](/api/ai/stream-ai-job-results) with credentials.
          </Note>
      security:
        - bearerAuth: []
      operationId: createAIJob
      parameters:
        - name: userId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: |
            **Requires an Organization API key.** The ID of the user to run the query as.

            Personal Access Tokens (PATs) cannot use this parameter to act on behalf of other users.
          example: 9e8719d9-276a-4964-9395-a493189a247c
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - modelId
                - prompt
              properties:
                modelId:
                  type: string
                  format: uuid
                  description: The ID of the model to run the AI job against
                prompt:
                  type: string
                  description: Natural language instruction describing the desired task
                conversationId:
                  type: string
                  format: uuid
                  description: Optional conversation ID to continue an existing conversation
                branchId:
                  type: string
                  format: uuid
                  description: |
                    Optional branch ID for the model. Must be a branch of the shared model specified by `modelId`.
                  
                    Queries run against the branch model, and if the Omni Agent makes model changes, they are written to this branch instead of a newly created one. If omitted and the Agent makes model changes, a new branch is created automatically.
                topicName:
                  type: string
                  description: |
                    Topic name to scope query generation.
                    
                    If not provided, the AI will automatically select the best topic based on the prompt. For multi-turn conversations, the AI will carry forward the topic from the previous turn.
                    
                    The topic must exist in the model and be allowed by any [`ai_chat_topics`](/modeling/models/ai-chat-topics) restrictions or the API will return a `404`. Use the [Pick topic endpoint](/api/ai/pick-topic) to programmatically determine the right topic.
                progressWebhookEnabled:
                  type: boolean
                  description: |
                    When `true`, real-time progress events are sent using `POST` to the defined `webhookUrl` during execution (e.g., _"Searching for revenue fields"_, _"Query returned 42 rows"_). **`webhookUrl` is required if enabled.** 
                    
                    Progress events are best-effort: single attempt, no retries, failures do not affect job execution.
                webhookUrl:
                  type: string
                  format: uri
                  description: |
                    An optional webhook URL to receive webhook updates. Always receives a terminal event (job.complete or job.failed) when the job finishes.
                    
                    When `progressWebhookEnabled: true`, also receives real-time progress events during execution. **Required if `progressWebhookEnabled: true`.**
                webhookSigningSecret:
                  type: string
                  description: An HMAC-SHA256 secret for signing webhook payloads
                webhookMetadata:
                  type: object
                  description: Opaque metadata passed back unchanged in webhook payloads
                attachments:
                  type: array
                  maxItems: 5
                  description: |
                    Optional image or PDF attachments giving the AI additional visual context alongside the prompt (e.g., a screenshot or export of a legacy Tableau/Power BI/Looker dashboard being migrated).

                    Image files are limited to 3MB each; text files such as CSVs have a ~50KB limit. All attachments in a single request share a combined size budget of roughly 15 MB, shared with your prompt text. Requests can be rejected if the attachments exceed this limit, whether on their own or combined with the prompt. In this case, try attaching fewer files or using smaller/lower-resolution exports.

                    <Note>
                      Attachments are stored internally and never exposed over the public API. They will not appear in job status responses, results, or webhook payloads.
                    </Note>
                  items:
                    type: object
                    required:
                      - data
                      - mimeType
                    properties:
                      data:
                        type: string
                        minLength: 1
                        description: Base64-encoded file content
                        example: "iVBORw0KGgoAAAANSUhEUgAA..."
                      mimeType:
                        type: string
                        description: MIME type of the attachment. Must be an image type (e.g., `image/png`, `image/jpeg`, `image/webp`) or `application/pdf`
                        example: "image/png"
                      name:
                        type: string
                        description: Optional filename, used for display and logging only
                        example: "legacy-dashboard-screenshot.png"
            examples:
              basic:
                summary: Basic AI job
                value:
                  modelId: "123e4567-e89b-12d3-a456-426614174000"
                  prompt: "Analyze sales trends by region for the past year"
              with_attachment:
                summary: AI job with visual context
                value:
                  modelId: "123e4567-e89b-12d3-a456-426614174000"
                  prompt: "Re-create this dashboard in Omni"
                  attachments:
                    - data: "iVBORw0KGgoAAAANSUhEUgAAAAUA..."
                      mimeType: "image/png"
                      name: "tableau-dashboard-export.png"
      responses:
        '201':
          description: AI job created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobId:
                    type: string
                    format: uuid
                    description: The unique identifier for the created job
                  conversationId:
                    type: string
                    format: uuid
                    description: The conversation ID associated with this job
                  omniChatUrl:
                    type: string
                    description: URL to view this conversation in the Omni chat interface. Opens the chat session where the job actions and results are visible.
              example:
                jobId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                conversationId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                omniChatUrl: "https://blobsrus.omni.co/chat/b2c3d4e5-f6a7-8901-bcde-f12345678901"
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid modelId/branchId/conversationId`
            - `Missing required field: prompt`
            - `Invalid webhook URL format`
            - `Invalid attachment MIME type (must be image or PDF)`
            - `Malformed base64 data in attachment`
            - `Too many attachments (maximum 5)`
            - `Attachment token budget exceeded (50,000 tokens shared with prompt)`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:
            - `Feature not enabled`
            - `Insufficient permissions`
            - `User-scoped API keys cannot act on behalf of other users`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - `Model not found`
            - `Topic not found or excluded by ai_chat_topics`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: |
            Conflict

            Possible error messages:
            - `Active job exists for conversation`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/jobs/{jobId}:
    get:
      tags:
        - AI
      summary: Get AI job status
      description: |
        Retrieve the current status and details of an AI job, including its state, progress information, and result summary.
        
        The response fields vary by `state` — for example, `progress` is only present during `EXECUTING`, and `resultSummary` is only present when `COMPLETE`.
        
        Poll this endpoint every 2–5 seconds until the job reaches a terminal state (`COMPLETE`, `FAILED`, or `CANCELLED`).
      security:
        - bearerAuth: []
      operationId: getAIJobStatus
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the AI job
      responses:
        '200':
          description: Job status retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: The unique identifier for this job.
                    example: "550e8400-e29b-41d4-a716-446655440000"
                  state:
                    type: string
                    enum: [CANCELLED, COMPLETE, DELIVERING, EXECUTING, FAILED, QUEUED]
                    description: Current state of the job. Terminal states are `COMPLETE`, `FAILED`, and `CANCELLED`. Poll until the job reaches a terminal state.
                    example: "QUEUED"
                  prompt:
                    type: string
                    description: The natural language prompt that was submitted.
                    example: "What are the top 5 products by revenue?"
                  conversationId:
                    type: string
                    format: uuid
                    description: The conversation this job is associated with. Use this to submit follow-up jobs in the same conversation thread.
                    example: "660e8400-e29b-41d4-a716-446655440001"
                  modelId:
                    type: string
                    format: uuid
                    nullable: true
                    description: The shared model ID used for query generation.
                    example: "770e8400-e29b-41d4-a716-446655440002"
                  branchId:
                    type: string
                    format: uuid
                    nullable: true
                    description: Branch ID used for model context, or null if querying the main shared model.
                  organizationId:
                    type: string
                    format: uuid
                    description: The organization that owns this job.
                    example: "880e8400-e29b-41d4-a716-446655440003"
                  userId:
                    type: string
                    format: uuid
                    description: The user ID who created (or is associated with) this job.
                    example: "990e8400-e29b-41d4-a716-446655440004"
                  topicName:
                    type: string
                    nullable: true
                    description: Topic name used to scope query generation, or null if the AI selected the topic automatically.
                    example: "order_items"
                  omniChatUrl:
                    type: string
                    format: uri
                    description: URL to view this conversation in the Omni chat interface. Opens the chat session where the job actions and results are visible.
                    example: "https://blobsrus.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
                  progress:
                    type: object
                    nullable: true
                    description: |
                      **Only present in `EXECUTING` state.** Real-time progress information. Null if no progress has been reported yet. Updated in real-time as the AI works through iterations.
                    properties:
                      iteration:
                        type: integer
                        description: Current iteration number. The AI may take multiple iterations to refine queries and generate a complete answer.
                        example: 2
                      message:
                        type: string
                        description: Human-readable status message describing what the AI is currently doing.
                        example: "Running query: Top products by revenue"
                      updatedAt:
                        type: string
                        format: date-time
                        description: When this progress update was recorded.
                        example: "2025-01-15T10:00:08.000Z"
                    required:
                      - iteration
                      - message
                      - updatedAt
                  resultSummary:
                    type: string
                    description: |
                      **Only present in COMPLETE state.** Markdown-formatted summary of the job result. For the full result with query details and data, use the [Stream AI job results endpoint](/api/ai/stream-ai-job-results).
                    example: "### Top 5 Products by Revenue\n\n1. **Sunglasses** - $678,994\n2. **Jeans** - $475,072"
                  error:
                    type: object
                    description: |
                      **Only present in `FAILED` state.** Error details explaining why the job failed.
                    properties:
                      code:
                        type: string
                        description: Machine-readable error code.
                        example: "QUERY_EXECUTION_ERROR"
                      message:
                        type: string
                        description: Human-readable error message.
                        example: "Column 'revenue' not found in table 'orders'"
                      detail:
                        type: string
                        description: Additional error detail or context.
                        example: "The query timed out after 300 seconds"
                    required:
                      - message
                  createdAt:
                    type: string
                    format: date-time
                    description: When the job was submitted.
                    example: "2025-01-15T10:00:00.000Z"
                  executionStartedAt:
                    type: string
                    format: date-time
                    description: When execution began. Present once the job transitions from `QUEUED` to `EXECUTING`. May be absent on jobs that failed or were cancelled before execution started.
                    example: "2025-01-15T10:00:05.000Z"
                  completedAt:
                    type: string
                    format: date-time
                    description: |
                      **Present in `COMPLETE` and `FAILED` states.** When the job finished (successfully or with error).
                    example: "2025-01-15T10:01:30.000Z"
                  cancelledAt:
                    type: string
                    format: date-time
                    description: |
                       When the job was cancelled.
                    example: "2025-01-15T10:00:12.000Z"
                  cancelledBy:
                    type: string
                    format: uuid
                    description: |
                      **Only present in `CANCELLED` state.** User ID of who cancelled the job.
                    example: "990e8400-e29b-41d4-a716-446655440004"
                  updatedAt:
                    type: string
                    format: date-time
                    description: When the job record was last modified.
                    example: "2025-01-15T10:00:05.000Z"
              examples:
                queued:
                  summary: QUEUED — job is waiting to be picked up
                  value:
                    id: "550e8400-e29b-41d4-a716-446655440000"
                    state: "QUEUED"
                    prompt: "What are the top 5 products by revenue?"
                    conversationId: "660e8400-e29b-41d4-a716-446655440001"
                    modelId: "770e8400-e29b-41d4-a716-446655440002"
                    branchId: null
                    organizationId: "880e8400-e29b-41d4-a716-446655440003"
                    userId: "990e8400-e29b-41d4-a716-446655440004"
                    topicName: null
                    omniChatUrl: "https://my-org.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
                    createdAt: "2025-01-15T10:00:00.000Z"
                    updatedAt: "2025-01-15T10:00:00.000Z"
                executing:
                  summary: EXECUTING — AI is working through iterations
                  value:
                    id: "550e8400-e29b-41d4-a716-446655440000"
                    state: "EXECUTING"
                    prompt: "What are the top 5 products by revenue?"
                    conversationId: "660e8400-e29b-41d4-a716-446655440001"
                    modelId: "770e8400-e29b-41d4-a716-446655440002"
                    branchId: null
                    organizationId: "880e8400-e29b-41d4-a716-446655440003"
                    userId: "990e8400-e29b-41d4-a716-446655440004"
                    topicName: "order_items"
                    omniChatUrl: "https://my-org.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
                    progress:
                      iteration: 2
                      message: "Running query: Top products by revenue"
                      updatedAt: "2025-01-15T10:00:08.000Z"
                    createdAt: "2025-01-15T10:00:00.000Z"
                    executionStartedAt: "2025-01-15T10:00:05.000Z"
                    updatedAt: "2025-01-15T10:00:08.000Z"
                complete:
                  summary: COMPLETE — job finished successfully
                  value:
                    id: "550e8400-e29b-41d4-a716-446655440000"
                    state: "COMPLETE"
                    prompt: "What are the top 5 products by revenue?"
                    conversationId: "660e8400-e29b-41d4-a716-446655440001"
                    modelId: "770e8400-e29b-41d4-a716-446655440002"
                    branchId: null
                    organizationId: "880e8400-e29b-41d4-a716-446655440003"
                    userId: "990e8400-e29b-41d4-a716-446655440004"
                    topicName: "order_items"
                    omniChatUrl: "https://my-org.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
                    resultSummary: "### Top 5 Products by Revenue\n\n1. **Sunglasses** - $678,994\n2. **Jeans** - $475,072"
                    createdAt: "2025-01-15T10:00:00.000Z"
                    executionStartedAt: "2025-01-15T10:00:05.000Z"
                    completedAt: "2025-01-15T10:01:30.000Z"
                    updatedAt: "2025-01-15T10:01:30.000Z"
                failed:
                  summary: FAILED — job encountered an error
                  value:
                    id: "550e8400-e29b-41d4-a716-446655440000"
                    state: "FAILED"
                    prompt: "What are the top 5 products by revenue?"
                    conversationId: "660e8400-e29b-41d4-a716-446655440001"
                    modelId: "770e8400-e29b-41d4-a716-446655440002"
                    branchId: null
                    organizationId: "880e8400-e29b-41d4-a716-446655440003"
                    userId: "990e8400-e29b-41d4-a716-446655440004"
                    topicName: "order_items"
                    omniChatUrl: "https://my-org.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
                    error:
                      code: "QUERY_EXECUTION_ERROR"
                      message: "Column 'revenue' not found in table 'orders'"
                      detail: "The query timed out after 300 seconds"
                    createdAt: "2025-01-15T10:00:00.000Z"
                    executionStartedAt: "2025-01-15T10:00:05.000Z"
                    completedAt: "2025-01-15T10:01:30.000Z"
                    updatedAt: "2025-01-15T10:01:30.000Z"
                cancelled:
                  summary: CANCELLED — job was cancelled by a user
                  value:
                    id: "550e8400-e29b-41d4-a716-446655440000"
                    state: "CANCELLED"
                    prompt: "What are the top 5 products by revenue?"
                    conversationId: "660e8400-e29b-41d4-a716-446655440001"
                    modelId: "770e8400-e29b-41d4-a716-446655440002"
                    branchId: null
                    organizationId: "880e8400-e29b-41d4-a716-446655440003"
                    userId: "990e8400-e29b-41d4-a716-446655440004"
                    topicName: null
                    omniChatUrl: "https://my-org.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
                    createdAt: "2025-01-15T10:00:00.000Z"
                    executionStartedAt: "2025-01-15T10:00:05.000Z"
                    cancelledAt: "2025-01-15T10:00:12.000Z"
                    cancelledBy: "990e8400-e29b-41d4-a716-446655440004"
                    updatedAt: "2025-01-15T10:00:12.000Z"
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid job ID format. Must be a valid UUID.`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: |
            Unauthorized

            Possible error messages:
            - `Missing or invalid API key`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "404"
                detail: "Job not found"
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/jobs/{jobId}/cancel:
    post:
      tags:
        - AI
      summary: Cancel AI job
      description: |
        Request cancellation of an AI job. This endpoint is idempotent — calling it on an already-cancelled or completed job returns success with the current state. 
        
        - For `QUEUED` jobs, cancellation is immediate
        - For `EXECUTING` jobs, the worker will stop after completing its current iteration
        - Jobs in a `DELIVERING` state cannot be cancelled as they are already finalizing results. **Only the job owner or organization admins can cancel jobs.**
      security:
        - bearerAuth: []
      operationId: cancelAIJob
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the AI job to cancel
      responses:
        '200':
          description: Job cancelled successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobId:
                    type: string
                    format: uuid
                    description: The unique identifier for the cancelled job
                  state:
                    type: string
                    enum: [CANCELLED]
                    description: The new state of the job
              example:
                jobId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                state: "CANCELLED"
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Job is not in a cancellable state`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: |
            Unauthorized

            Possible error messages:
            - `Missing or invalid API key`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:
            - `Permission denied. Only the job owner or organization admins can cancel jobs.`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job not found. The job may not exist or may belong to a different organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/jobs/{jobId}/result:
    get:
      tags:
        - AI
      summary: Stream AI job results
      description: |
        Retrieve the full result of a completed AI job, including all actions taken by the AI (queries generated, data retrieved) and the final summarized answer. 
        
        Results are only available for jobs in `COMPLETE` state and are retained for 14 days after completion. The response is streamed directly from storage.
      security:
        - bearerAuth: []
      operationId: getAIJobResult
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the AI job
      responses:
        '200':
          description: Job results retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  actions:
                    type: array
                    description: Ordered list of actions the AI took during execution. Each action represents a step such as generating a query, executing it, or synthesizing a final answer.
                    items:
                      type: object
                      required:
                        - message
                        - timestamp
                        - type
                      properties:
                        type:
                          type: string
                          description: The type of action. Common types include `generate_query` (query generation and execution) and `summarize` (final answer synthesis).
                          example: "generate_query"
                        message:
                          type: string
                          description: The AI's explanation of what it is doing in this step, written in natural language.
                          example: "I'll generate a query to find the top 5 products by total revenue."
                        timestamp:
                          type: string
                          description: ISO 8601 timestamp when this action occurred.
                          example: "2025-01-15T10:00:10.000Z"
                        result:
                          type: object
                          description: Query result data. Only present for `generate_query` action types.
                          required:
                            - csvResult
                            - csvResultWasTruncated
                            - hasResults
                            - query
                            - queryName
                            - status
                            - totalRowCount
                          properties:
                            resultId:
                              type: string
                              description: Stable, unique identifier for this query result within the job. Use it to reference a specific result — for example, to correlate or de-duplicate results across responses.
                              example: "928c5838-000d-4943-b305-f6242c1b4922"
                            queryName:
                              type: string
                              description: Human-readable name describing what this query retrieves.
                              example: "Top 5 Products by Revenue"
                            status:
                              type: string
                              enum: [success, error]
                              description: Whether the query executed successfully.
                              example: "success"
                            query:
                              type: object
                              description: The semantic query definition that was executed. This can be used with the [Run query endpoint](/api/queries/run-query) to re-run the query.
                            csvResult:
                              type: string
                              description: Query results formatted as CSV text.
                              example: "Name,Total Revenue\nRay-Ban Sunglasses,\"678,994.41\"\nLevi's 501 Jeans,\"475,072.00\""
                            csvResultWasTruncated:
                              type: boolean
                              description: Whether the CSV data was truncated due to size limits. If `true`, the full result set may contain additional rows not included in `csvResult`.
                              example: false
                            hasResults:
                              type: boolean
                              description: Whether the query returned any data rows.
                              example: true
                            totalRowCount:
                              type: integer
                              description: Total number of rows returned by the query.
                              example: 5
                  message:
                    type: string
                    description: The AI's final response message in Markdown format. This is the complete answer to the original prompt, incorporating data from all executed queries.
                    example: "### Top 5 Products by Revenue\n\n1. **Sunglasses** - $678,994\n2. **Jeans** - $475,072"
                  resultSummary:
                    type: string
                    description: Summary of the job result. Typically matches the final message content.
                    example: "### Top 5 Products by Revenue\n\n1. **Sunglasses** - $678,994\n2. **Jeans** - $475,072"
                  topic:
                    type: string
                    description: The topic name used for query generation.
                    example: "order_items"
                  omniChatUrl:
                    type: string
                    format: uri
                    description: URL to view this conversation in the Omni chat interface. Opens the chat session where the job actions and results are visible.
                    example: "https://my-org.omni.co/chat/660e8400-e29b-41d4-a716-446655440001"
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid job ID`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: |
            Unauthorized

            Possible error messages:
            - `Missing or invalid API key`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Job not found or results not available

            Possible error messages:
            - `Job not found`
            - `Job results not yet available`
            - `Result is no longer available`. Results are retained for 14 days.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  
  /v1/ai/eval/prompt-sets:
    get:
      description: List eval prompt sets, sorted alphabetically by name. When `model_ids` is omitted, returns prompt sets for every shared model the caller can access. Requires at least the Querier role on each requested model.
      operationId: aiEvalPromptSetsList
      summary: List eval prompt sets
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            enum:
              - 'true'
              - 'false'
            description: When `true`, returns archived prompt sets instead of active ones. Defaults to `false`.
            example: 'false'
          required: false
          description: When `true`, returns archived prompt sets instead of active ones. Defaults to `false`.
          name: archived
          in: query
        - schema:
            type: array
            items:
              type: string
              format: uuid
            description: Optional list of model IDs to filter prompt sets by. When omitted, returns prompt sets for every model the caller can access. Supply multiple times to filter by more than one model (e.g., `?model_ids=A&model_ids=B`).
          required: false
          description: Optional list of model IDs to filter prompt sets by. When omitted, returns prompt sets for every model the caller can access. Supply multiple times to filter by more than one model (e.g., `?model_ids=A&model_ids=B`).
          name: model_ids
          in: query
      responses:
        '200':
          description: List of prompt sets, sorted alphabetically by name.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalPromptSetsListResponse'
        '400':
          description: Invalid query params (e.g. `model_ids` contains a non-UUID, or `archived` is not `true`/`false`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: &id001
                detail: 'Bad Request: name: Required'
                status: 400
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: &id002
                detail: 'Unauthorized: Missing or invalid API key'
                status: 401
        '403':
          description: Insufficient permissions. The caller must have at least the Querier role on each requested model.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: &id003
                detail: AI eval requires at least Querier access on the model
                status: 403
        '404':
          description: No eval-accessible models for this caller.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: &id004
                detail: Prompt set not found
                status: 404
    post:
      description: Create a new eval prompt set bound to a shared model. Initial prompts can be supplied; additional prompts can be added later via PATCH.
      operationId: aiEvalPromptSetsCreate
      summary: Create an eval prompt set
      tags:
        - AI Eval
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EvalPromptSetsCreateBody'
      responses:
        '201':
          description: Prompt set created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalPromptSetsCreateResponse'
        '400':
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id001
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions. The caller must have at least the Querier role on the model.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
  /v1/ai/eval/prompt-sets/{promptSetId}:
    get:
      description: Get a single prompt set with all of its prompts.
      operationId: aiEvalPromptSetsGet
      summary: Get an eval prompt set
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            format: uuid
            description: The unique identifier of the eval prompt set.
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          description: The unique identifier of the eval prompt set.
          name: promptSetId
          in: path
      responses:
        '200':
          description: Prompt set details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalPromptSetsGetResponse'
        '400':
          description: Invalid `promptSetId` — must be a UUID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id001
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: Prompt set not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
    patch:
      description: Update a prompt set's name, description, and/or prompts. When `prompts` is supplied, it fully replaces the existing list — existing prompts omitted from the list are deleted, entries without an `id` are created, and entries with a matching `id` are updated in place.
      operationId: aiEvalPromptSetsUpdate
      summary: Update an eval prompt set
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            format: uuid
            description: The unique identifier of the eval prompt set.
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          description: The unique identifier of the eval prompt set.
          name: promptSetId
          in: path
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EvalPromptSetsUpdateBody'
      responses:
        '200':
          description: Prompt set updated successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalPromptSetsUpdateResponse'
        '400':
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id001
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: Prompt set not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
        '422':
          description: A `prompts[].id` in the request does not belong to this prompt set.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: &id005
                detail: A prompt being updated does not belong to this prompt set
                status: 422
    delete:
      description: 'Archive (soft-delete) a prompt set. Omni also attempts to cancel every in-flight agentic job associated with the set; the returned `cancelled_job_count` reports how many were cancelled. Cancellation is best-effort — the archive is committed first, so if a run-cancellation later fails the endpoint returns 500 even though the set is already archived. The call is idempotent: retrying drains any remaining runs.'
      operationId: aiEvalPromptSetsArchive
      summary: Archive an eval prompt set
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            format: uuid
            description: The unique identifier of the eval prompt set.
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          description: The unique identifier of the eval prompt set.
          name: promptSetId
          in: path
      responses:
        '200':
          description: Prompt set archived successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalPromptSetsDeleteResponse'
        '400':
          description: Invalid `promptSetId` — must be a UUID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id001
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: Prompt set not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
        '500':
          description: Archive committed but a run-cancellation failed; the set is already archived — safe to retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example:
                detail: Archive committed but a run-cancellation failed; retry to complete
                status: 500
  /v1/ai/eval/prompt-sets/{promptSetId}/unarchive:
    post:
      description: Restore an archived prompt set.
      operationId: aiEvalPromptSetsUnarchive
      summary: Restore an archived eval prompt set
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            format: uuid
            description: The unique identifier of the eval prompt set.
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          description: The unique identifier of the eval prompt set.
          name: promptSetId
          in: path
      responses:
        '200':
          description: Prompt set restored successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalPromptSetsUnarchiveResponse'
        '400':
          description: Invalid `promptSetId` — must be a UUID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id001
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: Prompt set not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
  /v1/ai/eval/runs:
    get:
      description: List runs for a prompt set, newest first, filtered to runs whose model the caller can access. The `prompt_set_id` query parameter is required.
      operationId: aiEvalRunsList
      summary: List eval runs
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            enum:
              - 'true'
              - 'false'
            description: When `true`, returns archived runs instead of active ones. Defaults to `false`.
            example: 'false'
          required: false
          description: When `true`, returns archived runs instead of active ones. Defaults to `false`.
          name: archived
          in: query
        - schema:
            type: string
            format: uuid
            description: Required — the prompt set whose runs should be listed.
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          description: Required — the prompt set whose runs should be listed.
          name: prompt_set_id
          in: query
      responses:
        '200':
          description: List of runs for the prompt set.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalRunsListResponse'
        '400':
          description: Missing or invalid `prompt_set_id`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id001
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: Prompt set not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
    post:
      description: Create and start a new run against an existing prompt set. The run enqueues one agentic job per prompt and begins executing immediately. Returns the newly created run with its initial per-prompt result rows.
      operationId: aiEvalRunsCreate
      summary: Start an eval run
      tags:
        - AI Eval
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EvalRunsCreateBody'
      responses:
        '201':
          description: Run created and jobs enqueued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalRunsCreateResponse'
        '400':
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id001
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions. The caller must have at least the Querier role on the prompt set's model.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: The prompt set was not found, or `run_config.branch_id` does not match an existing branch in the organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
        '422':
          description: '`run_config.branch_id` does not belong to the prompt set''s model.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id005
        '429':
          description: Per-user active-run cap reached.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example:
                detail: Too many active runs; wait for an in-flight run to finish
                status: 429
        '503':
          description: AI eval is paused for this organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example:
                detail: AI eval is paused for this organization
                status: 503
  /v1/ai/eval/runs/{runId}:
    get:
      description: Get an eval run with every per-prompt result row, including the underlying agentic job state and any scoring data.
      operationId: aiEvalRunsGet
      summary: Get an eval run
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            format: uuid
            description: The unique identifier of the eval run.
            example: 660e8400-e29b-41d4-a716-446655440001
          required: true
          description: The unique identifier of the eval run.
          name: runId
          in: path
      responses:
        '200':
          description: Run detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalRunsGetResponse'
        '400': &id006
          description: Invalid `runId` — must be a UUID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example:
                detail: 'Bad Request: runId: Invalid uuid'
                status: 400
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: Run not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
    delete:
      description: Archive (soft-delete) an eval run. Any non-terminal per-prompt agentic jobs are cancelled as part of the archive (best-effort), and a still-RUNNING run is flipped to CANCELLED before archival. The call is idempotent; archiving an already-terminal or already-archived run is a no-op.
      operationId: aiEvalRunsArchive
      summary: Archive an eval run
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            format: uuid
            description: The unique identifier of the eval run.
            example: 660e8400-e29b-41d4-a716-446655440001
          required: true
          description: The unique identifier of the eval run.
          name: runId
          in: path
      responses:
        '200':
          description: Run archived successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalRunsDeleteResponse'
        '400': *id006
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: Run not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
  /v1/ai/eval/runs/{runId}/cancel:
    post:
      description: 'Cancel an in-flight eval run. Marks the run cancelled first so no in-progress per-prompt job can flip it back to COMPLETE, then cancels every non-terminal agentic job associated with the run. The run is also archived as part of the cancel — the response returns the updated run inline (`status: CANCELLED`, `is_archived: true`); use `/unarchive` to surface it in the default `archived=false` list again.'
      operationId: aiEvalRunsCancel
      summary: Cancel an eval run
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            format: uuid
            description: The unique identifier of the eval run.
            example: 660e8400-e29b-41d4-a716-446655440001
          required: true
          description: The unique identifier of the eval run.
          name: runId
          in: path
      responses:
        '200':
          description: Cancellation processed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalRunsCancelResponse'
        '400': *id006
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: Run not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
  /v1/ai/eval/runs/{runId}/unarchive:
    post:
      description: Restore an archived eval run.
      operationId: aiEvalRunsUnarchive
      summary: Restore an archived eval run
      tags:
        - AI Eval
      parameters:
        - schema:
            type: string
            format: uuid
            description: The unique identifier of the eval run.
            example: 660e8400-e29b-41d4-a716-446655440001
          required: true
          description: The unique identifier of the eval run.
          name: runId
          in: path
      responses:
        '200':
          description: Run restored successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalRunsUnarchiveResponse'
        '400': *id006
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id002
        '403':
          description: Insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id003
        '404':
          description: Run not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvalApiError'
              example: *id004
  /v1/models/{modelId}/suggestions:
    get:
      tags:
        - AI Model Suggestions
      summary: List AI-generated suggestions
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

        Lists AI-generated suggestions for a shared model. Suggestions can be filtered by status and are returned with keyset-based pagination.

        Suggestions are sorted by priority, creation date, and ID for stable pagination.
      security:
        - bearerAuth: []
      operationId: listModelSuggestions
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the shared model to list suggestions for.
        - name: status
          in: query
          schema:
            type: string
            enum:
              - active
              - ignored
              - all
            default: active
          description: |
            Filter suggestions by status:

            - `active` - Only active suggestions (default)
            - `ignored` - Only ignored suggestions
            - `all` - All suggestions regardless of status
        - name: cursor
          in: query
          schema:
            type: string
          description: Cursor for keyset-based pagination. Use the `id` from the last suggestion in the previous page.
        - name: pageSize
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Number of suggestions to return per page. Defaults to 25, maximum 100.
      responses:
        '200':
          description: List of suggestions for the model
          content:
            application/json:
              schema:
                type: object
                required:
                  - records
                  - pageInfo
                properties:
                  records:
                    type: array
                    description: List of AI-generated suggestions
                    items:
                      $ref: '#/components/schemas/ModelSuggestion'
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
              example:
                records:
                  - id: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    category: "missing_context"
                    title: "Document the orders view"
                    rationale: "Users frequently ask what `status` means; documenting the view in ai_context improves AI answers."
                    priority: 1
                    proposedChanges:
                      kind: "context_edits"
                      edits:
                        - field: "ai_context"
                          target: "views.orders"
                          value: "The orders view contains one row per customer order."
                    evidence:
                      - type: "ai_chat"
                        chatAiSessionId: "a1b2c3d4-5717-4562-b3fc-2c963f66afa6"
                        capturedAt: "2026-06-24T09:12:33Z"
                    ignoreReason: null
                    ignoredAt: null
                    ignoredBy: null
                    aiModifiedAt: "2026-06-25T16:10:58Z"
                    createdAt: "2026-06-25T16:10:58Z"
                    updatedAt: "2026-06-25T16:10:58Z"
                  - id: "b2c3d4e5-6717-4562-b3fc-2c963f66afa6"
                    category: "missing_context"
                    title: "Clarify the revenue measure"
                    rationale: "The revenue measure lacks a description, leading to ambiguous AI responses."
                    priority: 3
                    proposedChanges:
                      kind: "context_edits"
                      edits:
                        - field: "ai_context"
                          target: "views.orders.fields.revenue"
                          value: "Revenue is recognized at order completion, net of refunds."
                    evidence: []
                    ignoreReason: null
                    ignoredAt: null
                    ignoredBy: null
                    aiModifiedAt: "2026-06-24T11:05:00Z"
                    createdAt: "2026-06-24T11:05:00Z"
                    updatedAt: "2026-06-24T11:05:00Z"
                pageInfo:
                  hasNextPage: true
                  nextCursor: "b2c3d4e5-6717-4562-b3fc-2c963f66afa6"
                  pageSize: 25
                  totalRecords: 47
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Invalid cursor value`
            - `modelId: Invalid UUID`
            - `pageSize must be between 1 and 100`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Bad Request: Invalid cursor value"
                status: 400
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden. Possible causes:

            - Requesting user lacks Organization Admin permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Forbidden: Organization admin permission required"
                status: 403
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Shared model with id <modelId> does not exist`
            - `Model not found or not accessible`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Shared model with id 550e8400-e29b-41d4-a716-446655440000 does not exist"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'
  
  /v1/models/{modelId}/suggestions/{suggestionId}:
    delete:
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        Permanently deletes an AI Model suggestion.
      operationId: modelSuggestionsDelete
      summary: Delete a suggestion
      tags:
        - AI Model Suggestions
      parameters:
        - name: modelId
          in: path
          schema:
            type: string
            format: uuid
            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
          required: true
          description: UUID of the shared model the suggestion belongs to
          
        - name: suggestionId
          in: path
          schema:
            type: string
            format: uuid
            example: b2c3d4e5-f6a7-8901-bcde-f12345678901
          required: true
          description: UUID of the suggestion
      responses:
        '200':
          description: The suggestion was deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: Malformed ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: AI disabled for organization, model is not a shared model, or caller lacks Organization Admin permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: Suggestion or model not found in this organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '405':
          description: Method not allowed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/models/{modelId}/suggestions/runs/latest:
    get:
      summary: Get the latest generation run
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        Retrieve the most recent generation run for the shared model (the active run if one is in flight, otherwise the last terminal run), or `null` if none exists.
      operationId: modelSuggestionsRunLatest
      security:
        - bearerAuth: []
      tags:
        - AI Model Suggestions
      parameters:
        - name: modelId
          in: path
          required: true
          description: UUID of the shared model the suggestions belong to
          schema:
            type: string
            format: uuid
            description: UUID of the shared model the suggestions belong to
            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
      responses:
        '200':
          description: The latest generation run, or null
          content:
            application/json:
              schema:
                type: object
                description: The most recent generation run or `null` if no runs exist.
                required:
                  - run
                properties:
                  run:
                    allOf:
                      - $ref: '#/components/schemas/SuggestionRun'
                      - type: object
                        nullable: true
                        description: The most recent run for this model or `null` if none exists.
              examples:
                withRun:
                  summary: Model has runs
                  value:
                    run:
                      id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                      status: "complete"
                      triggerSource: "manual"
                      triggeredBy:
                        userId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                        name: "Blob Ross"
                      createdAt: "2026-07-15T14:30:00Z"
                      executionStartedAt: "2026-07-15T14:30:05Z"
                      completedAt: "2026-07-15T14:32:30Z"
                      error: null
                noRuns:
                  summary: Model has never had a run
                  value:
                    run: null
        '400':
          description: Malformed `modelId`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Feature not enabled, AI disabled, model is not a shared model, or caller lacks Organization Admin permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: Model not found in this organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '405':
          description: Method not allowed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/models/{modelId}/suggestions/runs/{runId}:
    get:
      summary: Get status for a generation run
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        Retrieve the status of a specific generation run. Poll until `status` is terminal (`complete` or `failed`), then re-fetch the suggestions list.
      operationId: modelSuggestionsRunGet
      security:
        - bearerAuth: []
      tags:
        - AI Model Suggestions
      parameters:
        - name: modelId
          in: path
          required: true
          description: UUID of the shared model the run belongs to
          schema:
            type: string
            format: uuid
            description: UUID of the shared model the run belongs to
            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
        - name: runId
          in: path
          required: true
          description: UUID of the generation run
          schema:
            type: string
            format: uuid
            description: UUID of the generation run
            example: b2c3d4e5-f6a7-8901-bcde-f12345678901
      responses:
        '200':
          description: The generation run
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuggestionRun'
              examples:
                completed:
                  summary: Completed run
                  value:
                    id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    status: "complete"
                    triggerSource: "manual"
                    triggeredBy:
                      userId: "c3d4e5f6-a7b8-9012-cdef-123456789012"
                      name: "Blob Ross"
                    createdAt: "2026-07-15T14:30:00Z"
                    executionStartedAt: "2026-07-15T14:30:05Z"
                    completedAt: "2026-07-15T14:32:30Z"
                    error: null
                running:
                  summary: Run in progress
                  value:
                    id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    status: "running"
                    triggerSource: "scheduled"
                    triggeredBy:
                      userId: "c3d4e5f6-a7b8-9012-cdef-123456789012"
                      name: "Blob Ross"
                    createdAt: "2026-07-15T14:30:00Z"
                    executionStartedAt: "2026-07-15T14:30:05Z"
                    completedAt: null
                    error: null
        '400':
          description: Malformed `modelId` or `runId`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Feature not enabled, AI disabled, model is not a shared model, or caller lacks Organization Admin permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: Run not found, does not belong to this model, or does not belong to this organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '405':
          description: Method not allowed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/routines:
    get:
      summary: List AI Routines
      description: |
        List AI Routines for the calling user, newest first. Includes routines paused by the owner or disabled by Omni, but excludes deleted routines. 
        
        Use `pageInfo.nextCursor` from one response as the `cursor` query parameter on the next request. 
      operationId: aiRoutinesList
      tags:
        - AI Routines
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
            example: eyJpZCI6IjEyMzQ1In0
          required: false
          description: Cursor for pagination from previous response `nextCursor`
        - name: pageSize
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
          required: false
          description: Number of results per page (1-100, integer)
        - name: sortDirection
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
            example: desc
          required: false
          description: Sort direction for results
        - name: sortField
          in: query
          schema:
            type: string
          required: false
          description: Field to sort results by
        - name: userId
          in: query
          schema:
            type: string
            format: uuid
          required: false
          description: |
            **Requires an Organization API key**. Target user membership ID.
      responses:
        '200':
          description: List of AI Routines
          content:
            application/json:
              schema:
                type: object
                required:
                  - pageInfo
                  - records
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    items:
                      $ref: '#/components/schemas/Routine'
                    description: Routines returned for this request, newest first.
        '400':
          description: Invalid pagination cursor or `userId` value.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `User does not have USE_AI permission`
            - `AI Routines feature is not enabled (aiScheduledTasks flag required)`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not found.
            
            Possible causes:
            
            - The `userId` membership was not found in the organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      summary: Create an AI Routine
      description: |
        Create a new AI Routine that will run on the specified schedule.

        Each scheduled run executes once using the routine owner's permissions, and every recipient receives the same result.
      operationId: routineCreate
      tags:
        - AI Routines
      parameters:
        - name: userId
          in: query
          schema:
            type: string
            format: uuid
          required: false
          description: |
            **Requires an Organization API key**. Target user membership ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - modelId
                - prompt
                - schedule
                - timezone
                - destination
              properties:
                branchId:
                  type: string
                  nullable: true
                  format: uuid
                  description: Branch of the shared model the prompt runs against, or null.
                description:
                  type: string
                  nullable: true
                  description: Display-only notes about the routine, or null.
                destination:
                  $ref: '#/components/schemas/RoutineDestination'
                modelId:
                  type: string
                  format: uuid
                  description: The shared model the prompt runs against.
                name:
                  type: string
                  description: Customer-visible name of the routine, used as the email subject.
                prompt:
                  type: string
                  description: Natural language prompt Omni runs on each scheduled run.
                schedule:
                  type: string
                  description: Six-field cron expression (minute, hour, day-of-month, month, day-of-week, year; use `?` for an unspecified day field).
                timezone:
                  type: string
                  description: IANA timezone identifier used to evaluate the schedule.
                topicName:
                  type: string
                  nullable: true
                  description: Topic scoping query generation, or null.
      responses:
        '201':
          description: Routine created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: The unique identifier for the newly created routine.
                    example: "880e8400-e29b-41d4-a716-446655440003"
        '400':
          description: Invalid request body, recipient configuration, timezone, or schedule. Also returned when the schedule is more frequent than the organization allows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            AI routines or AI query generation are not enabled for the organization, or the API key cannot act on behalf of the requested user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Model, branch, or topic not found, or not accessible to the requested user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '429':
          description: The resolved user already has the maximum number of active routines.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError429'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/routines/{routineId}:
    get:
      summary: Get an AI Routine
      description: |
        Retrieve details for a specific AI Routine by ID, including the status of its most recent completed run.
      operationId: routineGet
      tags:
        - AI Routines
      parameters:
        - name: id
          in: path
          schema:
            type: string
            format: uuid
          required: true
          description: The UUID of the routine.
        - name: userId
          in: query
          schema:
            type: string
            format: uuid
          required: false
          description: |
            **Requires an Organization API key**. Target user membership ID.
      responses:
        '200':
          description: Routine details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Routine'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `User does not have USE_AI permission`
            - `AI Routines feature is not enabled (aiScheduledTasks flag required)`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Routine not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      summary: Update an AI Routine
      description: |
        Update an existing AI Routine. All request fields are optional, and only supplied fields are changed. Supplying `destination` replaces the full recipient configuration.
      operationId: routineUpdate
      tags:
        - AI Routines
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          description: The UUID of the routine.
          name: id
          in: path
        - schema:
            type: string
            format: uuid
          required: false
          description: |
            **Requires an Organization API key**. Target user membership ID.
          name: userId
          in: query
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - prompt
                - schedule
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 255
                  description: Display name for the routine
                description:
                  type: string
                  nullable: true
                  description: Optional description of what the routine does
                prompt:
                  type: string
                  minLength: 1
                  description: The AI prompt that will be executed when the routine runs
                schedule:
                  type: string
                  description: Cron expression or human-readable schedule (e.g., "daily", "weekly", "0 9 * * 1")
                modelId:
                  type: string
                  format: uuid
                  nullable: true
                  description: Optional model ID to scope the routine to a specific data model
                isActive:
                  type: boolean
                  description: Whether the routine is active and will execute on schedule
      responses:
        '200':
          description: Routine updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Routine'
        '400':
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:
            - `User does not have USE_AI permission`
            - `AI Routines feature is not enabled (aiScheduledTasks flag required)`
            - `Insufficient permissions to update this routine`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Routine not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      summary: Delete an AI Routine
      description: |
        Delete an AI Routine. This action cannot be undone.
      operationId: routineDelete
      tags:
        - AI Routines
      parameters:
        - name: id
          in: path
          schema:
            type: string
            format: uuid
            description: The UUID of the routine.
          required: true
          description: The UUID of the routine.
        - name: userId
          in: query
          schema:
            type: string
            format: uuid
          required: false
          description: |
            **Requires an Organization API key**. Target user membership ID.
      responses:
        '200':
          description: Routine deleted successfully.
          content:
            application/json:
              schema:
                type: object
                required:
                  - deleted
                  - id
                properties:
                  deleted:
                    type: boolean
                    enum:
                      - true
                    description: Always true on a successful delete.
                  id:
                    type: string
                    format: uuid
                    description: The deleted routine’s ID.
        '400':
          description: Invalid routine ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to delete another user's routine.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: Routine not found or has already been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/ai/routines/{routineId}/trigger:
    post:
      tags:
        - AI Routines
      summary: Trigger AI Routine
      description: |
        Run an existing routine immediately, in addition to its schedule — e.g. to get an off-cycle result or verify a routine produces the email you expect.

        Executes once using the routine owner's permissions and delivers the AI response to every recipient configured on the routine. Returns as soon as the run has started; the result arrives asynchronously via the routine's normal email delivery.

        If a run is already in progress, the endpoint returns `409` rather than starting a second one. Manual runs that hit broken configuration will return `400` but not pause the schedule.
      security:
        - bearerAuth: []
      operationId: triggerAIRoutine
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The UUID of the routine.
        - name: userId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: |
            **Requires an Organization API key**. Target user membership ID. 
      responses:
        '202':
          description: The run has started.
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                properties:
                  id:
                    type: string
                    format: uuid
                    description: The ID of the run (scheduled job) that was started.
                    example: 990e8400-e29b-41d4-a716-446655440004     
        '400':
          description: |
            Bad request. Possible causes:
            
            - Invalid routine ID
            - Routine cannot run as configured (e.g. its model, branch, or owner is no longer accessible)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to run another user's routine.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: The routine does not exist or cannot be triggered (deleted, paused, or disabled by Omni).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '409':
          description: A run is already in progress for this routine.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError409'

  /v1/api-keys:
    get:
      tags:
        - API Tokens
      summary: List API tokens
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

        List API tokens in the organization - including Organization API keys, Personal Access Tokens, and MCP OAuth grants - with optional filtering by type. Only metadata - not actual secret values - is returned.
      security:
        - bearerAuth: []
      operationId: listApiKeys
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum:
              - organization
              - personal
              - mcp
          description: |
            Filter by token type. Omit to return all types.

            - `organization` - Organization-level API key. Not tied to a specific user.
            - `personal` - Personal access token. Acts as a specific user.
            - `mcp` - MCP OAuth grant issued during the OAuth authorization flow
        - name: cursor
          in: query
          schema:
            type: string
            format: uuid
          description: Cursor for pagination.
        - name: pageSize
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of items to return per page.
        - name: sortField
          in: query
          schema:
            type: string
            enum:
              - createdAt
              - name
            default: createdAt
          description: Field to sort by.
        - name: sortDirection
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
          description: Sort direction.
      responses:
        '200':
          description: API tokens retrieved successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: Unique ID for the token.
                        name:
                          type: string
                          description: Token name.
                        type:
                          type: string
                          enum:
                            - organization
                            - personal
                            - mcp
                          description: Token type.
                        enabled:
                          type: boolean
                          description: Whether the token is enabled. `false` for disabled Organization API keys; PATs and MCP tokens are always `true`.
                        createdAt:
                          type: string
                          format: date-time
                          description: ISO 8601 timestamp of when the token was created.
                        membershipId:
                          type: string
                          format: uuid
                          nullable: true
                          description: Membership ID of the user who owns the token. `null` for Organization API keys.
              example:
                pageInfo:
                  hasNextPage: false
                  nextCursor: null
                  pageSize: 50
                  totalRecords: 2
                records:
                  - id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: CI deployment key
                    type: organization
                    enabled: true
                    createdAt: "2026-01-15T10:00:00.000Z"
                    membershipId: null
                  - id: b2c3d4e5-f6a7-8901-bcde-f12345678901
                    name: Personal token
                    type: personal
                    enabled: true
                    createdAt: "2026-02-10T14:30:00.000Z"
                    membershipId: c3d4e5f6-a7b8-9012-cdef-123456789012
        '400':
          description: |
            Bad Request. Validation errors include:

            - `type: Invalid option: expected one of "organization"|"personal"|"mcp"`
            - `cursor: Invalid UUID`
            - `pageSize: Page size must be at least 1`
            - `pageSize: Page size cannot exceed 100`
            - `pageSize: Invalid input: expected number, received NaN`
            - `sortField: Invalid option: expected one of "createdAt"|"name"`
            - `sortDirection: Invalid option: expected one of "asc"|"desc"`
            - `Bad authorization header, must be formatted as Bearer <token>`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
              example:
                detail: "Bad Request: type: Invalid option: expected one of \"organization\"|\"personal\"|\"mcp\""
                status: 400
        '403':
          description: |
            Forbidden. Requires Organization Admin permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
              example:
                detail: "Forbidden: Requires Organization Admin permissions"
                status: 403
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/api-keys/{id}:
    get:
      tags:
        - API Tokens
      summary: Retrieve an API token
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

          description: Token id. Must be a valid UUID — malformed values return `400`.
      responses:
        '200':
          description: Token found successfully.
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - name
                  - type
                  - enabled
                  - createdAt
                  - membershipId
                properties:
                  id:
                    type: string
                    format: uuid
                    description: The token's unique identifier.
                  name:
                    type: string
                    description: The name given when the token was created. Not unique within an organization.
                  type:
                    type: string
                    enum:
                      - organization
                      - personal
                      - mcp
                    description: |
                      Token type:
                      - `organization` - Organization API key
                      - `personal` - Personal Access Token (PAT)
                      - `mcp` - MCP OAuth PAT
                  enabled:
                    type: boolean
                    description: Whether the token can currently authenticate. Organization Admins can disable any token type; disabled tokens remain visible but cannot authenticate.
                  createdAt:
                    type: string
                    format: date-time
                    description: ISO 8601 timestamp of when the token was created (UTC).
                  membershipId:
                    type: string
                    format: uuid
                    nullable: true
                    description: The user membership the token is scoped to. `null` if `type` is `organization`.
              example:
                id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                name: CI deployment key
                type: organization
                enabled: true
                createdAt: "2026-04-20T10:00:00.000Z"
                membershipId: null
        '400':
          description: |
            Bad Request. The ID parameter is not a valid UUID, or the Authorization header is malformed.

            Possible error messages:
            - `Bad Request: id: Invalid UUID`
            - `Bad authorization header, must be formatted as Bearer <token>`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
              example:
                detail: "Bad Request: id: Invalid UUID"
                status: 400
        '401':
          description: |
            Unauthorized. The Authorization header is missing or malformed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
              example:
                detail: "Unauthorized: Missing or invalid API key"
                status: 401
        '403':
          description: |
            Forbidden. Authentication succeeded but the caller is not an organization admin, or the bearer token is invalid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
              example:
                detail: "Forbidden: Requires Organization Admin permissions"
                status: 403
        '404':
          description: |
            Not Found. No token with that ID exists in the caller's organization.

            The same `404` response is returned regardless of whether the token exists in a different organization - no tenant information is leaked.

            Example error message:
            - `Api key with id 00000000-0000-0000-0000-000000000000 does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
              example:
                detail: "Api key with id 00000000-0000-0000-0000-000000000000 does not exist"
                status: 404
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - API Tokens
      summary: Delete API token
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

        Permanently delete an API token. Works for all token types — Organization API keys, Personal Access Tokens (PATs), and MCP OAuth grants. This is the same as revoking a token in the Omni app.

        Concurrent deletes are safe — the first caller receives `200` and the second receives `404` consistently. An Organization Admin can delete the token they authenticated with, which is useful for self-service rotation.
      security:
        - bearerAuth: []
      operationId: deleteApiKey
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: UUID of the token to revoke. The token must belong to the same organization as the authenticating user.
      responses:
        '200':
          description: Token revoked successfully. The row is deleted; subsequent `GET` returns `404`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: Human-readable description of the outcome.
                  success:
                    type: boolean
                    description: Always `true` on a successful revocation.
              example:
                message: API token revoked
                success: true
        '400':
          description: |
            Bad Request. The `id` is not a UUID, or the `Authorization` header is missing/malformed.

            Possible error messages:
            - `Bad Request: id: Invalid UUID`
            - `Bad authorization header, must be formatted as Bearer <token>`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
              example:
                detail: "Bad Request: id: Invalid UUID"
                status: 400
        '403':
          description: |
            Forbidden. The caller is not an organization admin.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
              example:
                detail: "Requires Organization Admin permissions"
                status: 403
        '404':
          description: |
            Not Found. No token with that id exists in the caller's organization, or another admin revoked it first.

            Possible error messages:
            - `Api key with id <id> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
              example:
                detail: "Api key with id 00000000-0000-0000-0000-000000000000 does not exist"
                status: 404
        '405':
          description: |
            Method Not Allowed. HTTP method other than `GET`, `PUT`, or `DELETE`.

            Possible error messages:
            - `Method not allowed`
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                  status:
                    type: integer
              example:
                detail: Method not allowed
                status: 405
        '429':
          $ref: '#/components/responses/TooManyRequests'

    put:
      tags:
        - API Tokens
      summary: Enable or disable API token
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

        Disable or re-enable an API token. To permanently revoke a token, use the [Delete API token](/api/api-tokens/delete-api-token) endpoint.

        This operation is **idempotent**: calling it twice with the same body produces the same result.
      security:
        - bearerAuth: []
      operationId: updateApiKey
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: UUID of the API token. The token must exist in the authenticating user's organization.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - enabled
              properties:
                enabled:
                  type: boolean
                  description: |
                    Determines if the API token is enabled: `false` to disable the token, `true` to re-enable it.
              additionalProperties: false
            examples:
              disable:
                summary: Disable a token
                value:
                  enabled: false
              enable:
                summary: Enable a token
                value:
                  enabled: true
      responses:
        '200':
          description: Token updated successfully. Returns the current state of the token, or the same state if already in the requested state.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: Unique ID for the token.
                  name:
                    type: string
                    description: Token name.
                  type:
                    type: string
                    enum:
                      - organization
                      - personal
                      - mcp
                    description: Token type.
                  enabled:
                    type: boolean
                    description: Whether the token is enabled.
                  createdAt:
                    type: string
                    format: date-time
                    description: ISO 8601 timestamp of when the token was created.
                  membershipId:
                    type: string
                    format: uuid
                    nullable: true
                    description: Membership ID of the user who owns the token. `null` for Organization API keys.
              example:
                id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                name: CI deployment key
                type: organization
                enabled: false
                createdAt: "2026-04-20T10:00:00.000Z"
                membershipId: null
        '400':
          description: |
            Bad Request. Validation errors include:

            - `id: Invalid UUID` - The token ID is not a valid UUID format
            - `enabled: Invalid input: expected boolean, received string` - Non-boolean value provided for `enabled`
            - `Unrecognized key: "<field>"` - Unknown field in request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
              examples:
                invalidUuid:
                  summary: Malformed UUID
                  value:
                    detail: "Bad Request: id: Invalid UUID"
                    status: 400
                nonBooleanValue:
                  summary: Non-boolean enabled value
                  value:
                    detail: "Bad Request: enabled: Invalid input: expected boolean, received string"
                    status: 400
                unknownField:
                  summary: Unknown field in request body
                  value:
                    detail: "Bad Request: Unrecognized key: \"name\""
                    status: 400
        '403':
          description: |
            Forbidden. Requires Organization Admin permissions, or authentication failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
              example:
                detail: "Requires Organization Admin permissions"
                status: 403
        '404':
          description: |
            Not Found. The token does not exist or belongs to a different organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
              example:
                detail: "Api key with id 00000000-0000-0000-0000-000000000000 does not exist"
                status: 404
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/connections:
    post:
      tags:
        - Connections
      summary: Create connection
      description: |
        Creates a new database connection. See the **Parameters**
      security:
        - bearerAuth: []
      operationId: createConnection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - dialect
                - name
                - passwordUnencrypted
              properties:
                dialect:
                  $ref: '#/components/schemas/Dialect'
                name:
                  type: string
                  description: A descriptive name for the connection
                host:
                  type: string
                  description: |
                    The hostname or IP address of the database server.
                    - **MotherDuck** - Not required
                    - **Snowflake** - Provide only the account identifier (e.g., `myaccount` not `myaccount.snowflakecomputing.com`)
                    - **BigQuery** - Automatically determined from the service account
                port:
                  type: integer
                  description: |
                    The port number for the database connection.

                    Not required for Snowflake, MotherDuck, BigQuery, Databricks, and Athena.
                database:
                  type: string
                  description: |
                    The default database/catalog to connect to.

                    - **BigQuery** - Provide the project ID
                    - **Athena** - Provide the data catalog
                username:
                  type: string
                  description: |
                    The username to authenticate with.

                    - **MotherDuck** - Not required
                    - **BigQuery** - Provide client email from the service account
                passwordUnencrypted:
                  type: string
                  description: |
                    The password to authenticate with.
                    - **BigQuery** - This must be the JSON service account key file content
                    - **Snowflake** with keypair authentication - Can be omitted
                baseRole:
                  type: string
                  description: |
                    The default role for users accessing the connection. Available roles include:

                    - `VIEWER` - Can view the model
                    - `QUERIER` - Can view and query the model
                    - `QUERY_TOPICS` - Can query specific topics. Equivalent to **Restricted Querier.**
                    - `MODELER` - Can edit and model the data
                    - `CONNECTION_ADMIN` - Full administrative access to the connection
                    - `NO_ACCESS` - No access to the model
                    - [Custom roles](/administration/users/custom-roles) defined for your organization
                warehouse:
                  type: string
                  description: |
                    **Required for**:

                    - **Snowflake** - Specify the warehouse
                    - **Databricks** - Specify the HTTP path
                includeSchemas:
                  type: string
                  description: Comma-separated list of schemas to include. Leave empty to include all schemas.
                includeOtherCatalogs:
                  type: string
                  description: |
                    Comma-separated list of other catalogs/databases to include. **Only applicable for databases that support multi-catalog queries:** BigQuery, Snowflake, MotherDuck, Databricks, Trino, Athena.
                defaultSchema:
                  type: string
                  description: |
                    **Required for MSSQL.** The default schema to use. 
                queryTimeoutSeconds:
                  type: integer
                  description: The timeout in seconds for queries. Maximum value is `3600` (1 hour). Only applicable for databases that support query timeouts.
                  maximum: 3600
                  default: 900
                region:
                  type: string
                  description: |
                    **Required for BigQuery and Athena connections.**

                    - **BigQuery** - Specify a region like `us`
                    - **Athena** - Specify an AWS region like `us-east-1`
                maxBillingBytes:
                  type: string
                  description: |
                    **Applicable for BigQuery.** Maximum bytes that can be billed for a BigQuery query.
                scratchSchema:
                  type: string
                  description: Schema to use for data input (upload) tables. If not specified, a suitable default will be chosen.
                systemTimezone:
                  type: string
                  description: The timezone to use for the system.
                  default: "UTC"
                queryTimezone:
                  type: string
                  description: The timezone to use for queries.
                  default: "NONE"
                allowsUserSpecificTimezones:
                  type: boolean
                  description: Whether to allow users to specify their own timezones.
                  default: false
                alwaysScopeViewNames:
                  type: boolean
                  description: |
                    Whether to prefix generated view names with their schema and catalog, e.g. `my_catalog_public__orders` instead of `orders`. Defaults to `true` for dialects that support multiple catalogs, and `false` otherwise. See [View name generation](/connect-data/view-name-generation) for more information.
                trustServerCertificate:
                  type: boolean
                  description: |
                    **Applicable for MSSQL, Exasol, and ClickHouse.** Whether to trust the server certificate.
                  default: false
                privateKey:
                  type: string
                  description: |
                    **Applicable for Snowflake only.** An RSA key for keypair authentication. Omni will automatically add PEM headers if none are provided. 
                acceptsLicense:
                  type: boolean
                  description: |
                    **Applicable for Oracle.** Whether to accept the license terms. 
                authenticationType:
                  type: string
                  description: |
                    **Applicable for BigQuery, MSSQL, Snowflake, Databricks, and Athena.** The authentication method to use.
                awsRoleArn:
                  type: string
                  description: |
                    **Applicable for Athena.** The AWS role ARN to assume for the connection.
                enableDbSemanticLayerIntegration:
                  type: boolean
                  description: |
                    Whether to enable the database's semantic layer integration:

                    - **Snowflake** - [Snowflake semantic views](/connect-data/snowflake-semantic-views)
                    - **Databricks** - [Databricks Unity Catalog](/connect-data/databricks-unity-catalog-integration)
                enableDbSemanticLayerTopics:
                  type: boolean
                  description: |
                    **Applicable for Snowflake and Databricks.** Whether to enable database semantic layer topics.
                externalOauthAudience:
                  type: string
                  description: |
                    **Applicable for Snowflake.** The audience for [external OAuth authentication](/connect-data/oauth/snowflake/external-okta).
                externalOauthAuthorizationUrl:
                  type: string
                  description: |
                    **Applicable for Snowflake.** The authorization URL for [external OAuth authentication](/connect-data/oauth/snowflake/external-okta).
                externalOauthTokenUrl:
                  type: string
                  description: |
                    **Applicable for Snowflake.** The token URL for [external OAuth authentication](/connect-data/oauth/snowflake/external-okta).
                hostOverride:
                  type: string
                  description: |
                    **Applicable for Snowflake.** Override for the host value.
                inferRelationshipsFromColumnNames:
                  type: boolean
                  default: true
                  description: |
                    Whether to automatically infer relationships from column-name conventions during schema refresh.
                inferRelationshipsFromForeignKeys:
                  type: boolean
                  description: |
                    **Applicable for Postgres and Snowflake.** Whether to automatically infer relationships from foreign key constraints.
                oauthClientId:
                  type: string
                  description: |
                    **Applicable for [Snowflake](/connect-data/oauth/snowflake/native) and Databricks.** The OAuth client ID for native OAuth.
                oauthClientSecretUnencrypted:
                  type: string
                  description: |
                    **Applicable for Snowflake and Databricks.** The unencrypted OAuth client secret for [native OAuth](/connect-data/oauth/snowflake/native).
                offloadedSchemas:
                  type: string
                  description: |
                    **Applicable for all dialects.** Comma-separated list of schemas to offload. See [Offloading schemas](/connect-data/offloading-schemas) for more information.
                useMachineAuth:
                  type: boolean
                  description: |
                    **Applicable for Athena and Databricks.** Whether to use machine authentication.
            examples:
              BigQuery:
                summary: BigQuery
                value:
                  dialect: bigquery
                  name: My BigQuery Connection
                  region: us
                  passwordUnencrypted: "<SERVICE_ACCOUNT_JSON_FILE>"
                  defaultSchema: my_dataset
                  includeSchemas: dataset1,dataset2
                  includeOtherCatalogs: other_project1,other_project2
                  maxBillingBytes: "1000000000"
              MySQL:
                summary: MySQL
                value:
                  dialect: mysql
                  name: My MySQL Connection
                  host: mysql.example.com
                  port: 3306
                  database: mydb
                  username: dbuser
                  passwordUnencrypted: mypassword
                  includeSchemas: public,analytics
                  queryTimeoutSeconds: 900
              Postgres:
                summary: PostgreSQL
                value:
                  dialect: postgres
                  name: My Postgres Connection
                  host: postgres.example.com
                  port: 5432
                  database: mydb
                  username: dbuser
                  passwordUnencrypted: mypassword
                  includeSchemas: public,analytics
                  queryTimeoutSeconds: 900
              Snowflake:
                summary: Snowflake
                value:
                  dialect: snowflake
                  name: My Snowflake Connection
                  host: myaccount
                  database: MYDB
                  username: dbuser
                  passwordUnencrypted: mypassword
                  warehouse: COMPUTE_WH
                  includeSchemas: PUBLIC,ANALYTICS
                  includeOtherCatalogs: OTHER_DB1,OTHER_DB2
                  queryTimeoutSeconds: 900
      responses:
        '201':
          description: Connection created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: string
                    format: uuid
                    description: Connection ID
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - Connections
      summary: List connections
      description: Retrieves a list of database connections with optional filtering and sorting
      security:
        - bearerAuth: []
      operationId: listConnections
      parameters:
        - name: name
          in: query
          schema:
            type: string
          description: Filter connections by name (case-insensitive, partial matching)
        - name: database
          in: query
          schema:
            type: string
          description: Filter connections by database name (case-insensitive, partial matching)
        - name: dialect
          in: query
          schema:
            $ref: '#/components/schemas/Dialect'
          description: Filter connections by dialect(s). For multiple dialects, provide a comma-separated list (e.g., `postgres,mysql`).
        - name: sortField
          in: query
          schema:
            type: string
            enum: [database, dialect, name]
          description: Field to sort by
        - $ref: '#/components/parameters/sortDirection'
        - name: includeDeleted
          in: query
          schema:
            type: boolean
          description: When `true`, the response includes deleted connections. Defaults to `false`.
      responses:
        '200':
          description: Returns a list of connections objects
          content:
            application/json:
              schema:
                type: object
                properties:
                  connections:
                    type: array
                    items:
                      $ref: '#/components/schemas/Connection'
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `formErrors: Unrecognized key(s) in object: '<invalidParam>'`
            - `sortField: Invalid enum value. Expected 'database' | 'dialect' | 'name', received '<invalidField>'`
            - `Invalid dialect(s): <invalidDialect>`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/connections/{connectionId}:
    get:
      tags:
        - Connections
      summary: Get connection
      description: |
        <Note>
        This endpoint requires **Restricted Querier** permissions or higher.
        </Note>
        
        Fetch a single connection by ID.
      security:
        - bearerAuth: []
      operationId: getConnection
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Connection ID
      responses:
        '200':
          description: Connection retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  connection:
                    $ref: '#/components/schemas/Connection'
              example:
                connection:
                  id: "550e8400-e29b-41d4-a716-446655440000"
                  name: "Production Snowflake"
                  dialect: "snowflake"
                  database: "analytics_db"
                  defaultSchema: "public"
                  deletedAt: null
                  baseRole: "QUERIER"
                  branchConnectionEnvironmentOverridesUserAttr: false
                  environmentConnectionSwitchesSchemaModel: false
                  userAttributeNameForConnectionEnvironments: null
                  userAttributeValuesForDefaultEnvironment: null
        '400':
          description: Invalid connection ID format (not a valid UUID)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden. Possible error messages:
            
            - `Caller lacks `READ` permission on the connection` - Requesting user has insufficient permissions for the connection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      tags:
        - Connections
      summary: Update connection
      description: |
        Update connection properties including credentials. Credentials are encrypted at rest and never returned in API responses.

        This endpoint supports credential rotation for infrastructure-as-code workflows. Depending on the connection type, you can update:
        - **Password-based connections** (Postgres, MySQL, etc.): Use `passwordUnencrypted` to update the password
        - **BigQuery**: Use `passwordUnencrypted` to update the service account JSON
        - **Snowflake**: Use `privateKey` to update the RSA private key (PEM format, minimum 2048 bits)

        At least one field must be provided in the request body.
      security:
        - bearerAuth: []
      operationId: updateConnection
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Connection ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                passwordUnencrypted:
                  type: string
                  description: |
                    Password or service account JSON for credential rotation.

                    - For **password-based connections** such as Postgres, MySQL, etc. -  Provide the new password as a string
                    - For **BigQuery** - Provide the entire service account JSON file as a string
                  example: "new-password-here"
                privateKey:
                  type: string
                  description: |
                    **Only applicable to Snowflake connections**. RSA private key in PEM format for Snowflake connections. Must be at least 2048 bits.
                  example: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg..."
                baseRole:
                  type: string
                  description: |
                    The default role for users accessing the connection. Available roles include:

                    - `VIEWER` - Can view the model
                    - `QUERIER` - Can view and query the model
                    - `QUERY_TOPICS` - Can query specific topics. Equivalent to **Restricted Querier.**
                    - `MODELER` - Can edit and model the data
                    - `CONNECTION_ADMIN` - Full administrative access to the connection
                    - `NO_ACCESS` - No access to the model
                    - [Custom roles](/administration/users/custom-roles) defined for your organization
                environmentUserAttribute:
                  oneOf:
                    - type: object
                      properties:
                        attributeName:
                          type: string
                          description: The name of the user attribute to use for environments
                        defaultValues:
                          type: array
                          items:
                            type: string
                          description: Array of default values for the user attribute
                      required:
                        - attributeName
                        - defaultValues
                    - type: "null"
                  description: Configuration for environment user attributes. Set to `null` to remove environment user attribute settings.
            examples:
              rotatePassword:
                summary: Rotate password (Postgres, MySQL, etc.)
                value:
                  passwordUnencrypted: "new-password-here"
              rotateBigQueryKey:
                summary: Rotate BigQuery service account key
                value:
                  passwordUnencrypted: '{"type":"service_account","project_id":"my-gcp-project","private_key_id":"abc123...","private_key":"-----BEGIN PRIVATE KEY-----\nMIIEvgIBA...\n-----END PRIVATE KEY-----\n","client_email":"my-service@my-gcp-project.iam.gserviceaccount.com","client_id":"123456789","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/..."}'
              rotateSnowflakeKey:
                summary: Rotate Snowflake keypair
                value:
                  privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg..."
              updateRole:
                summary: Update base role
                value:
                  baseRole: "QUERIER"
              combinedUpdate:
                summary: Update credentials and role together
                value:
                  passwordUnencrypted: "new-password-here"
                  baseRole: "MODELER"
      responses:
        '200':
          description: Connection updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: "Updated connection credentials."
        '400':
          description: |
            Bad request. Common causes:
            - Empty or invalid request body
            - `privateKey` used with non-Snowflake connection
            - Invalid private key format or insufficient key length
            - Empty private key provided
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden. User does not have Connection Admin permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Connections
      summary: Delete connection
      description: |
        <Note>
          This endpoint requires **Connection Admin** permissions.
        </Note>
        
        Archives a connection (moves it to trash). Archived connections can be restored from the trash in the Omni app.

        This endpoint performs a soft delete. The connection and its associated models and content are moved to trash, and sensitive information such as passwords and keys is permanently deleted.
      security:
        - bearerAuth: []
      operationId: deleteConnection
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Connection ID
      responses:
        '200':
          description: Connection moved to trash successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                success: true
                message: "Connection moved to trash."
        '400':
          description: Invalid connection ID format (not a valid UUID)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden. Caller lacks Connection Admin permissions on the connection.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            The connection does not exist or belongs to another organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '410':
          description: |
            Gone

            The connection has already been archived.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/connection-environments:
    post:
      tags:
        - Connection environments
      summary: Create connection environments
      description: Creates connection environments by associating environment-specific connections with a base connection
      security:
        - bearerAuth: []
      operationId: createConnectionEnvironments
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - baseConnectionId
                - environmentConnectionIds
              properties:
                baseConnectionId:
                  type: string
                  format: uuid
                  description: The UUID of the base connection to associate environment connections with
                environmentConnectionIds:
                  type: string
                  description: |
                    A comma-separated list of connection UUIDs to use as environment connections. All connection IDs must be valid UUIDs that:

                    - Exist in your organization
                    - Have the same dialect as the base connection
                    - Aren't already used as environment connections for the base connection
                    - Aren't the same as the base connection
            example:
              baseConnectionId: "123e4567-e89b-12d3-a456-426614174000"
              environmentConnectionIds: "223e4567-e89b-12d3-a456-426614174001,323e4567-e89b-12d3-a456-426614174002"
      responses:
        '201':
          description: Connection environment(s) created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        environmentConnectionId:
                          type: string
                          format: uuid
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid method`
            - `Invalid JSON`
            - `baseConnectionId: Base connection ID must be a valid UUID`
            - `environmentConnectionIds: All environment connection IDs must be valid UUIDs`
            - `The following environment connection IDs are not valid for this connection: <ids>`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |
            Not Found

            Possible error messages:
            - `Connection with id <baseConnectionId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/connection-environments/{connectionEnvironmentId}:
    put:
      tags:
        - Connection environments
      summary: Update connection environment
      description: |
        Associates user attribute values with a specific connection environment, which will determine when it is used.
      security:
        - bearerAuth: []
      x-mint:
        content: |
          <Note>
            User attribute values for environment connections work in conjunction with the user attribute name and values set on the base connection, which can be defined using the [Update connection endpoint](/api/connections/update-connection).
            
            The base connection specifies which user attribute to evaluate, while each environment connection specifies which values of that attribute will trigger its use.
          </Note>
      operationId: updateConnectionEnvironment
      parameters:
        - name: connectionEnvironmentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the connection environment to update
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - userAttributeValues
              properties:
                userAttributeValues:
                  type: string
                  description: |
                    A comma-separated list of user attribute values to associate with the connection environment. For example: `"dev,staging"`
                    
                    **Note**: User attribute values must be unique across all environments for a base connection.
      responses:
        '200':
          description: Connection environment updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid JSON`
            - `Invalid method`
            - `Invalid uuid`
            - `User attribute value "<value>" is already in use`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Connection environment with id <id> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

    delete:
      tags:
        - Connection environments
      summary: Delete connection environment
      description: |
        Deletes a connection environment by ID. Requires Connection Admin or Organization Admin permissions.
      security:
        - bearerAuth: []
      operationId: deleteConnectionEnvironment
      parameters:
        - name: connectionEnvironmentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the connection environment to delete
      responses:
        '200':
          description: Connection environment deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '403':
          description: |
            Forbidden

            The authenticated user does not have sufficient permissions to delete this connection environment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Connection environment with id <id> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /unstable/documents/{dashboardId}/export:
    get:
      tags:
        - Content migration
      summary: Export dashboard
      description: Export dashboard for migration between Omni instances
      x-mint:
        content: |
          <Warning>
            This API is in beta and may have future breaking changes.
          </Warning>
      security: 
        - orgApiKey: []
      operationId: exportDashboard
      parameters:
        - name: dashboardId
          in: path
          required: true
          schema:
            type: string
          description: Dashboard identifier
      responses:
        '200':
          description: Dashboard export data
          content:
            application/json:
              schema:
                type: object
                properties:
                  dashboard:
                    type: object
                    description: Dashboard configuration
                  document:
                    type: object
                    description: Document metadata
                  exportVersion:
                    type: string
                    example: "0.1"
                  workbookModel:
                    type: object
                    description: Workbook model data
                  fileUploads:
                    type: array
                    description: Spreadsheet file data included in the export. Each item contains the file content and metadata needed to restore spreadsheet tiles during import.
                    items:
                      type: object
                      properties:
                        fileUploadId:
                          type: string
                          description: Unique identifier for the file upload
                        fileName:
                          type: string
                          description: Original file name
                        contentType:
                          type: string
                          description: MIME type of the file
                        data:
                          type: string
                          description: Base64-encoded file content
        '404':
          $ref: '#/components/responses/NotFound'

  /unstable/documents/import:
    post:
      tags:
        - Content migration
      summary: Import dashboard
      description: |
        Import dashboard to Omni instance.
      x-mint:
        content: |
          <Warning>
            This API is in beta and may have future breaking changes.
          </Warning>
      security: 
        - orgApiKey: []
      operationId: importDashboard
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - baseModelId
                - dashboard
                - document
                - workbookModel
                - exportVersion
              properties:
                baseModelId:
                  type: string
                  format: uuid
                  description: Target model ID
                dashboard:
                  type: object
                  description: Dashboard configuration from export
                document:
                  type: object
                  description: Document metadata from export
                workbookModel:
                  type: object
                  description: Workbook model from export
                exportVersion:
                  type: string
                  enum: ["0.1"]
                  description: Export format version (must be "0.1")
                identifier:
                  type: string
                  description: Custom identifier for the imported dashboard
                folderPath:
                  type: string
                  description: Destination folder path
                fileUploads:
                  type: array
                  description: Spreadsheet file data to restore during import. Include the `fileUploads` array from the export response to restore spreadsheet tiles in the imported dashboard.
                  items:
                    type: object
                    properties:
                      fileUploadId:
                        type: string
                        description: Unique identifier for the file upload (from export)
                      fileName:
                        type: string
                        description: Original file name
                      contentType:
                        type: string
                        description: MIME type of the file
                      data:
                        type: string
                        description: Base64-encoded file content
      responses:
        '200':
          description: Dashboard imported successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  dashboard:
                    type: object
                    properties:
                      dashboardId:
                        type: string
                        format: uuid
                  miniUuidMap:
                    type: object
                  workbook:
                    type: object
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          description: |
            Forbidden

            Possible error messages/causes:

            - `User-scoped API keys cannot act on behalf of other users`
            - Operations targeting personal/restricted scope require the user's **Personal content access** setting (`omni_allows_personal_content` system attribute) to be enabled for the user. If not enabled, the API will respond with `403 Forbidden`.

  /v1/content:
    get:
      tags:
        - Content
      summary: Retrieve content
      description: Retrieve paginated list of documents and folders
      security:
        - bearerAuth: []
      operationId: getContent
      parameters:
        - name: labels
          in: query
          schema:
            type: string
          description: Filter content by labels. Provide as a comma-separated list (e.g., `finance,marketing`).
        - name: scope
          in: query
          schema:
            type: string
            enum: [restricted, organization]
            default: organization
          description: Content scope filter
        - name: sortField
          in: query
          schema:
            type: string
            enum: [favorites, name]
            default: name
          description: Field to sort by
        - $ref: '#/components/parameters/sortDirection'
        - name: include
          in: query
          schema:
            type: string
          description: |
            Comma-separated list of additional fields to include in the response:
            - `_count` - Adds count metrics (folders: document and favorite counts; documents: favorite and view counts)
            - `labels` - Includes associated content labels
        - name: folderId
          in: query
          schema:
            type: string
            format: uuid
          description: Returns all content in the specified folder. **Cannot be used with `path`.**
        - name: path
          in: query
          schema:
            type: string
          description: |
            Filter content by path. **Cannot be used with `folderId`.** Examples:
            - `/folder/subfolder` - Returns the folder and any content it contains
            - `/folder/*` - Returns all folders and content recursively in the path
            - `/` - Returns all content in the organization
        - name: creatorId
          in: query
          schema:
            type: string
            format: uuid
          description: UUID of organization membership. **Required when `scope` is `restricted`.**
        - name: pageSize
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of records per page (1-100)
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor from previous response
      responses:
        '200':
          description: Paginated content list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Page size must be at least 1`
            - `Page size cannot exceed 100`
            - `Invalid sort field`
            - `creatorId required when scope is restricted`
            - `Unrecognized query parameters`
            - `folderId and path cannot be used together`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - `User with id <uuid> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'


  /v1/models/{modelId}/content-validator:
    get:
      tags:
        - Content validator
      summary: Validate content
      description: |
        Validate all content against the model and return documents with queries and any validation issues.

        This endpoint scans all documents associated with the model and identifies any fields, views, or other model elements referenced in queries that are no longer valid.

        You can optionally scope validation to only content that references a specific model element (view, field, or topic) using the `find` and `find_type` parameters. This is useful when you only need to validate content using a specific element, avoiding the overhead of validating all documents.
      security:
        - bearerAuth: []
      operationId: validateContent
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the model to validate content against.
        - name: branch_id
          in: query
          schema:
            type: string
            format: uuid
          description: The ID of the branch to validate against. If not provided, validates against the main model.
        - name: userId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            The user ID to act on behalf of. Only valid when using an organization API key.

            User-scoped API keys cannot use this parameter and will receive a 403 error if provided.
        - name: include_personal_folders
          in: query
          schema:
            type: boolean
          description: When enabled, include personal folders in the search.
        - name: find
          in: query
          schema:
            type: string
          description: |
            The name of the model element to find content references for. Must be used together with `find_type`.

            For `FIELD` type, the value must be fully qualified with the view name (e.g., `orders.status`).
        - name: find_type
          in: query
          schema:
            type: string
            enum:
              - VIEW
              - FIELD
              - TOPIC
          description: |
            The type of model element to search for. Must be used together with `find`.

            When both parameters are provided, only content that references the specified element will be validated and included in the response.
      responses:
        '200':
          description: Content validation results
          content:
            application/json:
              schema:
                type: object
                properties:
                  model_id:
                    type: string
                    format: uuid
                    description: The ID of the model that was validated.
                  branch:
                    type: object
                    nullable: true
                    description: Branch information if a branch was specified, null otherwise.
                    properties:
                      id:
                        type: string
                        format: uuid
                        description: The branch ID.
                      name:
                        type: string
                        description: The branch name.
                  content:
                    type: array
                    description: List of documents with their validation results.
                    items:
                      type: object
                      properties:
                        document_id:
                          type: string
                          description: The document ID.
                        identifier:
                          type: string
                          description: The document identifier (slug).
                        name:
                          type: string
                          description: The document name.
                        type:
                          type: string
                          description: The document type (e.g., "Published").
                        updated_at:
                          type: string
                          format: date-time
                          description: When the document was last updated.
                        folder:
                          type: object
                          nullable: true
                          description: Folder information if the document is in a folder.
                          properties:
                            name:
                              type: string
                              description: The folder name.
                            path:
                              type: string
                              description: The full folder path.
                        owner:
                          type: object
                          description: Information about the document owner.
                          properties:
                            email:
                              type: string
                              format: email
                              description: The owner's email address.
                            name:
                              type: string
                              description: The owner's display name.
                        require_pull_request_to_publish:
                          type: boolean
                          description: Whether the document requires a pull request to publish changes.
                        queries_and_issues:
                          type: array
                          description: List of queries and their validation issues.
                          items:
                            type: object
                            properties:
                              query_name:
                                type: string
                                description: The name of the query.
                              query_presentation_id:
                                type: string
                                description: The query presentation ID.
                              query_id_map_key:
                                type: string
                                description: The query ID map key.
                              issues:
                                type: array
                                items:
                                  type: string
                                description: List of validation issue messages for this query.
                        dashboard_filter_issues:
                          type: array
                          items:
                            type: string
                          description: List of validation issues for dashboard filters.
              examples:
                allContent:
                  summary: Validate all content
                  value:
                    model_id: "550e8400-e29b-41d4-a716-446655440000"
                    branch: null
                    content:
                      - document_id: "abc123"
                        identifier: "dashboard-1"
                        name: "Sales Dashboard"
                        type: "Published"
                        updated_at: "2025-01-15T10:00:00Z"
                        folder:
                          name: "Reports"
                          path: "/Reports"
                        owner:
                          email: "user@example.com"
                          name: "Jane Doe"
                        require_pull_request_to_publish: false
                        queries_and_issues:
                          - query_name: "Total Revenue"
                            query_presentation_id: "qp-123"
                            query_id_map_key: "1"
                            issues:
                              - "Field 'orders.old_field' not found in model"
                        dashboard_filter_issues: []
                filteredByField:
                  summary: Validate content using a specific field
                  description: When using find=orders.status&find_type=FIELD, only content that references the orders.status field is validated.
                  value:
                    model_id: "550e8400-e29b-41d4-a716-446655440000"
                    branch: null
                    content:
                      - document_id: "def456"
                        identifier: "order-analysis"
                        name: "Order Analysis"
                        type: "Published"
                        updated_at: "2025-01-15T11:30:00Z"
                        folder:
                          name: "Analytics"
                          path: "/Analytics"
                        owner:
                          email: "analyst@example.com"
                          name: "John Smith"
                        require_pull_request_to_publish: false
                        queries_and_issues:
                          - query_name: "Orders by Status"
                            query_presentation_id: "qp-456"
                            query_id_map_key: "1"
                            issues: []
                        dashboard_filter_issues: []
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `modelId: Invalid UUID`
            - `Both 'find' and 'find_type' parameters must be provided together`
            - `When find_type is FIELD, the find parameter must be scoped by view name (e.g., view_name.field_name)`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Bad Request: modelId: Invalid UUID"
                status: 400
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `User-scoped API keys cannot act on behalf of other users`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "User-scoped API keys cannot act on behalf of other users"
                status: 403
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Shared model with id <modelId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Shared model with id 550e8400-e29b-41d4-a716-446655440000 does not exist"
                status: 404
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    post:
      tags:
        - Content validator
      summary: Find and replace content
      description: |
        Performs find/replace operations on content using the specified model.

        This endpoint allows you to replace views or fields across all documents associated with the model. This is useful when renaming model elements and need to update all references.
      security:
        - bearerAuth: []
      operationId: findReplaceContent
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the model to perform find/replace against.
        - name: userId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            The user ID to act on behalf of. Only valid when using an organization API key.

            User-scoped API keys cannot use this parameter and will receive a 403 error if provided.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - find
                - replacement
                - find_or_replace_type
              properties:
                find:
                  type: string
                  description: |
                    The value to find. Used with `find_or_replace_type` to scope the search:

                    - `VIEW` - The name of the view to find
                    - `FIELD` - The fully qualified name of the field to find, specified as `view_name.field_name`
                    - `TOPIC` - The name of the topic to find
                replacement:
                  type: string
                  description: |
                    The replacement value.

                    - `VIEW` - The name of the replacement view
                    - `FIELD` - The fully qualified name of the replacement field, specified as `view_name.field_name`
                    - `TOPIC` - The name of the replacement topic
                find_or_replace_type:
                  type: string
                  enum:
                    - VIEW
                    - FIELD
                    - TOPIC
                  description: |
                    The type of find/replace operation.

                    - `VIEW` - Replace view references
                    - `FIELD` - Replace field references. `find` and `replacement` values must be the fully qualified name of the field, e.g. `view_name.field_name`.
                    - `TOPIC` - Replace topic references
                branch_id:
                  type: string
                  format: uuid
                  description: The branch ID to perform the operation on. If not provided, operates on the main model.
                include_personal_folders:
                  type: boolean
                  description: When enabled, include personal folders.
                only_in_workbook_id:
                  type: string
                  format: uuid
                  description: |
                    When provided, scopes the find/replace action to the specified workbook.

                    Must be the workbook model's internal **UUID** — visible in the model IDE URL as `/model/{uuid}`. This is **not** the same as the document's URL identifier (slug) that appears in the dashboard or workbook URL (for example, `/dashboards/abc123`). **Passing a document identifier / slug will silently return 0 replacements instead of raising an error.**
                  example: "550e8400-e29b-41d4-a716-446655440000"
            examples:
              replaceView:
                summary: Replace a view
                value:
                  find: "old_view"
                  replacement: "new_view"
                  find_or_replace_type: "VIEW"
              replaceField:
                summary: Replace a field
                value:
                  find: "orders.old_field"
                  replacement: "orders.new_field"
                  find_or_replace_type: "FIELD"
              replaceViewInBranch:
                summary: Replace a view in a branch
                value:
                  find: "old_view"
                  replacement: "new_view"
                  find_or_replace_type: "VIEW"
                  branch_id: "550e8400-e29b-41d4-a716-446655440001"
      responses:
        '200':
          description: Find/replace operation completed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  replaced_queries_count:
                    type: integer
                    description: The number of queries that had replacements made.
                  replaced_documents_count:
                    type: integer
                    description: The number of documents that had replacements made.
                  replaced_workbook_models_count:
                    type: integer
                    description: The number of workbook models that had replacements made.
                  replaced_dashboard_filters_count:
                    type: integer
                    description: The number of dashboard filters that had replacements made.
                  skipped_pr_required_count:
                    type: integer
                    description: The number of documents skipped because they require a pull request to publish.
              example:
                replaced_queries_count: 15
                replaced_documents_count: 5
                replaced_workbook_models_count: 3
                replaced_dashboard_filters_count: 2
                skipped_pr_required_count: 0
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `modelId: Invalid UUID`
            - `Find field must be scoped by view name.`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalidModelId:
                  summary: Invalid model ID
                  value:
                    detail: "Bad Request: modelId: Invalid UUID"
                    status: 400
                fieldNotScoped:
                  summary: Field not scoped by view name
                  value:
                    detail: "Find field must be scoped by view name."
                    status: 400
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `User-scoped API keys cannot act on behalf of other users`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "User-scoped API keys cannot act on behalf of other users"
                status: 403
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Shared model with id <modelId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Shared model with id 550e8400-e29b-41d4-a716-446655440000 does not exist"
                status: 404
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/dashboards/{dashboardId}/download:
    post:
      tags:
        - Dashboard downloads
      summary: Initiate dashboard or tile download
      description: |
        Start an asynchronous download job for a dashboard or single tile with support for multiple output formats. After starting a job, poll [Check download status](/api/dashboard-downloads/check-download-status) for completion, then retrieve the file from [Download file](/api/dashboard-downloads/download-file).

        <Note>
          Only one download per dashboard per user is allowed at a time. If a download is already in progress for the specified dashboard, the request will return a `409 Conflict` response with the existing job ID.
        </Note>
      x-mint:
        href: /api/dashboard-downloads/initiate-download
      security:
        - bearerAuth: []
      operationId: initiateDashboardDownload
      parameters:
        - name: dashboardId
          in: path
          required: true
          schema:
            type: string
          description: The dashboard identifier (ID or document UUID)
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            The user ID to run the download as. Only valid when authenticating with an organization API key. Use the [List users](/api/users/list-users) or [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - format
              properties:
                format:
                  type: string
                  enum: [pdf, png, csv, xlsx, json]
                  description: |
                    Output format:

                    | Format | Full dashboard | Single tile | Notes |
                    |--------|----------------|-------------|-------|
                    | PDF    | Yes            | Yes         |       |
                    | PNG    | Yes            | Yes         |       |
                    | XLSX   | Yes            | Yes         |       |
                    | CSV    | Yes (as ZIP)   | Yes         | For full dashboards, delivery will be a zip file containing one CSV per tile |
                    | JSON   | No             | Yes         | Only single tile is supported. Requires `queryIdentifierMapKey` to be specified. |

                filename:
                  type: string
                  maxLength: 255
                  description: Custom filename for the downloaded file. Defaults to the dashboard name.
                queryIdentifierMapKey:
                  type: string
                  description: |
                    Tile identifier to download a single tile instead of the full dashboard. Required for XLSX and JSON formats if `overrideRowLimit=true`.
                filterConfig:
                  type: object
                  description: Dashboard filter values to apply before rendering
                paperFormat:
                  type: string
                  enum: [fit_page, letter, legal, tabloid, a3, a4]
                  default: fit_page
                  description: |
                    **Applicable to PDF and PNG formats**. Page size.
                paperOrientation:
                  type: string
                  enum: [portrait, landscape]
                  description: | 
                    **Applicable to PDF and PNG formats**. Page orientation.
                hideTitle:
                  type: boolean
                  default: false
                  description: |
                    **Applicable to PDF and PNG formats**. If `true`, hide the dashboard title in the output.
                showFilters:
                  type: boolean
                  default: true
                  description: |
                    **Applicable to PDF and PNG formats**. If `true`, display applied filter values in the output.
                expandTablesToShowAllRows:
                  type: boolean
                  description: |
                    **Applicable to PDF and PNG formats**. If `true`, expand table tiles to display all rows.
                singleColumnLayout:
                  type: boolean
                  description: |
                    **Applicable to PDF and PNG formats**. If `true`, render tiles in a single column layout.
                enableFormatting:
                  type: boolean
                  default: false
                  description: |
                    **Applicable to CSV, XLSX, and JSON formats**. If `true`, preserve number and date formatting.
                hideHiddenFields:
                  type: boolean
                  default: false
                  description: |
                    **Applicable to CSV and XLSX formats**. If `true`, exclude hidden fields from the output.
                overrideRowLimit:
                  type: boolean
                  default: false
                  description: |
                    **Applicable to CSV, XLSX, and JSON formats**. Used with `maxRowLimit`. If `true`, remove the default row limit.

                    If `true` for XLSX and JSON formats, a `queryIdentifierMapKey` is required.
                maxRowLimit:
                  type: integer
                  minimum: 1
                  maximum: 1000000
                  description: |
                    **Applicable to CSV, XLSX, and JSON formats**. Maximum number of rows to export. Can be used with `overrideRowLimit` to export more rows than the default row limit.
            examples:
              fullDashboardPdf:
                summary: Download full dashboard as PDF
                value:
                  format: pdf
                  paperFormat: letter
                  paperOrientation: landscape
              singleTileJson:
                summary: Download single tile as JSON
                value:
                  format: json
                  queryIdentifierMapKey: "1"
              csvWithOptions:
                summary: Download as CSV with formatting
                value:
                  format: csv
                  enableFormatting: true
                  overrideRowLimit: true
                  maxRowLimit: 50000
      responses:
        '200':
          description: Download initiated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                    format: uuid
                    description: The job ID to use for checking status and downloading the file
                  message:
                    type: string
                    description: Success message
              example:
                job_id: "550e8400-e29b-41d4-a716-446655440000"
                message: "Download initiated successfully"
        '400':
          description: |
            Bad Request. Possible causes:

            - Missing required `format` field
            - Invalid format value
            - Invalid options for the specified format
            - Malformed JSON body
            - Invalid UUID format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to download the specified dashboard
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Dashboard not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '409':
          description: A download is already in progress for the specified dashboard
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                    description: Error message
                  existing_job_id:
                    type: string
                    format: uuid
                    description: The ID of the existing in-progress job
                  status:
                    type: string
                    description: The status of the existing job
              example:
                detail: "A download is already in progress for this dashboard. Please wait for it to complete or check its status."
                existing_job_id: "550e8400-e29b-41d4-a716-446655440000"
                status: "EXECUTING"
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/dashboards/{dashboardId}/download/{jobId}/status:
    get:
      tags:
        - Dashboard downloads
      summary: Check download status
      x-mint: 
        content: |
          Retrieve the current status of a dashboard or tile download job. Poll this endpoint to determine when the file is ready.

          The response will contain one of the following statuses:

          | Status        | Description                                    | Next Step                        |
          |---------------|------------------------------------------------|----------------------------------|
          | `in_progress` | Job is still processing                    | Continue polling                 |
          | `complete`    | File is ready                              | Call the [Download endpoint](/api/dashboard-downloads/download-file)       |
          | `error`       | Job failed - see `error` field for details | Review error and retry if needed |

          <Tip>
            We recommend the following when polling:

            - Use a reasonable polling interval (2-5 seconds)
            - Avoid polling more frequently than once per second
          </Tip>
      security:
        - bearerAuth: []
      operationId: getDashboardDownloadStatus
      parameters:
        - name: dashboardId
          in: path
          required: true
          schema:
            type: string
          description: The dashboard identifier. This must match the ID of the original download request.
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The job ID returned from the [Initiate dashboard download endpoint](/api/dashboard-downloads/initiate-download)
      responses:
        '200':
          description: Job status retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                    format: uuid
                    description: The job ID
                  status:
                    type: string
                    enum: [in_progress, complete, error]
                    description: Current status of the download job
                  format:
                    type: string
                    enum: [pdf, png, csv, xlsx, json]
                    description: The requested output format
                  created_at:
                    type: string
                    format: date-time
                    description: When the job was created
                  error:
                    type: string
                    description: Error message. Only present when status is `error`.
              examples:
                inProgress:
                  summary: Job in progress
                  value:
                    job_id: "550e8400-e29b-41d4-a716-446655440000"
                    status: "in_progress"
                    format: "pdf"
                    created_at: "2024-01-15T10:30:00Z"
                complete:
                  summary: Job complete
                  value:
                    job_id: "550e8400-e29b-41d4-a716-446655440000"
                    status: "complete"
                    format: "pdf"
                    created_at: "2024-01-15T10:30:00Z"
                error:
                  summary: Job failed
                  value:
                    job_id: "550e8400-e29b-41d4-a716-446655440000"
                    status: "error"
                    format: "pdf"
                    created_at: "2024-01-15T10:30:00Z"
                    error: "All queries failed."
        '400':
          description: Invalid job ID format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job not found or does not belong to the specified dashboard
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/dashboards/{dashboardId}/download/{jobId}:
    get:
      tags:
        - Dashboard downloads
      summary: Download file
      x-mint: 
        content: |
          <Note>
            Only call this endpoint when the [Check download status endpoint](/api/dashboard-downloads/check-download-status) returns a `complete` status.
          </Note>

          Retrieve the completed dashboard or tile download file.

          The response will include appropriate headers for the file type:

          - `Content-Type` -  MIME type based on format (e.g., `application/pdf`)
          - `Content-Disposition` - Attachment with filename (e.g., `attachment; filename="Dashboard Name.pdf"`)
          - `Content-Length` - File size in bytes (when available)
      security:
        - bearerAuth: []
      operationId: downloadDashboardFile
      parameters:
        - name: dashboardId
          in: path
          required: true
          schema:
            type: string
          description: The dashboard identifier. This must match the ID of the original download request.
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The job ID returned from the [Initiate dashboard download endpoint](/api/dashboard-downloads/initiate-download)
      responses:
        '200':
          description: File download successful
          content:
            application/pdf:
              schema:
                type: string
                format: binary
            image/png:
              schema:
                type: string
                format: binary
            text/csv:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                type: object
                description: Query result data
            application/zip:
              schema:
                type: string
                format: binary
                description: |
                  **Applicable to full dashboard downloads in CSV format.** ZIP file containing CSV files. 
        '202':
          description: Job still in progress. Poll the [Check download status endpoint](/api/dashboard-downloads/check-download-status) first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '400':
          description: Invalid job ID format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job not found or does not belong to the specified dashboard
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '410':
          description: Job failed. Call the [Check download status endpoint](/api/dashboard-downloads/check-download-status) for error details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/dashboards/{dashboardId}/filters:
    get:
      tags:
        - Dashboard filters
      summary: Get dashboard filters and controls
      description: |
        Retrieve the filter and control configuration for a dashboard, including IDs, types, current default values, and metadata.
      security:
        - bearerAuth: []
      operationId: getDashboardFilters
      parameters:
        - name: dashboardId
          in: path
          required: true
          schema:
            type: string
          description: The dashboard identifier
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Optional user ID that attributes the action to the specified user.

            Personal Access Tokens (PATs) cannot use this parameter.
      responses:
        '200':
          description: Dashboard filter and control configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DashboardFiltersResponse'
              example:
                identifier: "abc123"
                filters:
                  order_status:
                    type: "string"
                    kind: "EQUALS"
                    values: ["completed", "pending"]
                    label: "Order Status"
                    description: "Filter by order status"
                    required: false
                    requiredScope: "dashboard"
                    hidden: false
                  order_date:
                    type: "date"
                    kind: "WITHIN_RANGE"
                    left_side: "2024-01-01"
                    right_side: "2024-12-31"
                    label: "Order Date"
                    requiredScope: "tiles"
                controls:
                  - id: "field_selector"
                    type: "FIELD_SELECTION"
                    kind: "FIELD"
                    label: "Metric Selector"
                    field: "orders.revenue"
                    options:
                      - label: "Revenue"
                        value: "orders.revenue"
                      - label: "Quantity"
                        value: "orders.quantity"
                filterOrder: ["order_status", "order_date", "field_selector"]
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to view the specified dashboard
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Dashboard not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      tags:
        - Dashboard filters
      summary: Update dashboard filters/controls
      x-mint:
        content: |
          Set or reset values for filters and/or controls on a dashboard. Update specific filters or controls by providing an ID.

          <Note>
            Updates to published dashboards go through a [draft/publish workflow](/content/develop). If a draft already exists for the dashboard, you must set `clearExistingDraft: true` to discard it and proceed with the update.
          </Note>
      security:
        - bearerAuth: []
      operationId: updateDashboardFilters
      parameters:
        - name: dashboardId
          in: path
          required: true
          schema:
            type: string
          description: The dashboard identifier
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Membership ID to act on behalf of.

            Personal Access Tokens (PATs) cannot use this parameter.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DashboardFiltersUpdateRequest'
            examples:
              updateFilterValues:
                summary: Update filter values
                value:
                  filters:
                    order_status:
                      values: ["shipped", "delivered"]
              updateWithClearDraft:
                summary: Update with existing draft (clear draft)
                value:
                  clearExistingDraft: true
                  filters:
                    order_status:
                      values: ["shipped"]
              updateControlLabel:
                summary: Update control label
                value:
                  controls:
                    field_selector:
                      label: "Choose Metric"
              updateDisplayOrder:
                summary: Update display order
                value:
                  filterOrder: ["order_date", "field_selector", "order_status"]
      responses:
        '200':
          description: Updated filter and control configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DashboardFiltersResponse'
        '400':
          description: |
            Bad Request. Possible error messages:

            - `Invalid filter IDs: <ids>. Available filter IDs: <ids>`
            - `Invalid control IDs: <ids>. Available control IDs: <ids>`
            - `Invalid IDs in filterOrder: <ids>. Available IDs: <ids>`
            - `Invalid filter update for <id>: <validation error>`
            - `Invalid control update for <id>: <validation error>`
            - `Request must include at least one filter, control, or filterOrder to update`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to edit the specified dashboard
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Dashboard not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '409':
          description: A draft already exists for this document. Set `clearExistingDraft` to `true` to discard and proceed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/connections/{connectionId}/dbt:
    get:
      operationId: getDbtConfiguration
      summary: Get dbt configuration
      description: |
        <Note>
          **Connection Admin** permissions are required to use this endpoint.
        </Note>

        Retrieve the dbt configuration for the specified connection.
      tags:
        - dbt
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
            description: ID of the connection where dbt is configured
            example: "550e8400-e29b-41d4-a716-446655440000"
      responses:
        '200':
          description: 'dbt configuration successfully retrieved'
          content:
            application/json:
              schema:
                type: object
                required:
                  - autogenRelationships
                  - branch
                  - dbtVersion
                  - enableSemanticLayer
                  - enableVirtualSchemas
                  - projectRootPath
                  - sshUrl
                  - supportsDbt
                properties:
                  authMethod:
                    type: string
                    enum: [ssh, https_token, github_app]
                    description: |
                      The authentication method used for git operations:

                      - `ssh` - Deploy key
                      - `https_token` - Deploy token/PAT
                      - `github_app` - GitHub App installation
                    example: "ssh"
                  autogenRelationships:
                    type: boolean
                    description: Whether relationships are auto-generated from dbt
                    example: true
                  branch:
                    type: string
                    description: Git branch name
                    example: "main"
                  committerEmail:
                    type: [string, null]
                    description: Email address used for commit signing. Only returned when a commit signer is configured for GitHub App auth.
                    example: "bot@example.com"
                  committerName:
                    type: [string, null]
                    description: Name used for commit signing. Only returned when a commit signer is configured for GitHub App auth.
                    example: "Omni Bot"
                  commitSigningPublicKey:
                    type: [string, null]
                    description: SSH public key used to sign commits. Only returned when a commit signer is configured for GitHub App auth.
                    example: "ssh-ed25519 AAAA..."
                  dbtVersion:
                    type: string
                    description: dbt version being used
                    example: "Auto"
                  enableSemanticLayer:
                    type: boolean
                    description: Whether the [dbt semantic layer integration](/integrations/dbt/semantic-layer) is enabled
                    example: false
                  enableVirtualSchemas:
                    type: boolean
                    description: Whether virtual schemas are enabled
                    example: false
                  githubAppInstallationId:
                    type: [string, null]
                    description: GitHub App installation ID. Only returned when `authMethod` is `github_app`.
                    example: "12345678"
                  projectRootPath:
                    type:
                      - string
                      - "null"
                    description: Path to dbt project root
                    example: "dbt_project"
                  sshUrl:
                    type: string
                    description: SSH URL for git repository
                    example: "git@github.com:org/repo.git"
                  supportsDbt:
                    type: boolean
                    description: |
                      Whether the connection dialect supports dbt.
                    example: true
                  message:
                    type: string
                    description: Message explaining dbt status. Returned if dbt is not configured.
                    example: "dbt not configured for this connection"
              examples:
                dbtConfigured:
                  summary: dbt is configured with SSH
                  value:
                    authMethod: "ssh"
                    autogenRelationships: true
                    branch: "main"
                    dbtVersion: "Auto"
                    enableSemanticLayer: false
                    enableVirtualSchemas: false
                    projectRootPath: "dbt_project"
                    sshUrl: "git@github.com:org/repo.git"
                    supportsDbt: true
                dbtConfiguredGitHubApp:
                  summary: dbt is configured with GitHub App
                  value:
                    authMethod: "github_app"
                    autogenRelationships: true
                    branch: "main"
                    committerEmail: "bot@example.com"
                    committerName: "Omni Bot"
                    commitSigningPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKey123"
                    dbtVersion: "Auto"
                    enableSemanticLayer: false
                    enableVirtualSchemas: false
                    githubAppInstallationId: "12345678"
                    projectRootPath: "dbt_project"
                    sshUrl: "https://github.com/org/repo.git"
                    supportsDbt: true
                dbtNotConfigured:
                  summary: dbt is not configured
                  value:
                    supportsDbt: true
                    message: "dbt not configured for this connection"

        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Permission denied - connection admin role required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      operationId: updateDbtConfiguration
      summary: Update dbt configuration
      description: |
        <Note>
          **Connection Admin** permissions are required to use this endpoint.
        </Note>

        Update the dbt configuration for the specified connection.
      tags:
        - dbt
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
            description: ID of the connection where dbt is configured
            example: "550e8400-e29b-41d4-a716-446655440000"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - autogenRelationships
                - branch
                - enableVirtualSchemas
                - sshUrl
              properties:
                authMethod:
                  type: string
                  enum: [ssh, https_token, github_app]
                  description: |
                    The authentication method to use for git operations:

                    - `ssh` - Deploy key
                    - `https_token` - Deploy token/PAT
                    - `github_app` - GitHub App installation (github.com only)

                    When omitted, the existing auth method is retained. Field/method compatibility is validated against the effective auth method (the request's `authMethod`, or the connected repo's if omitted).
                  example: "ssh"
                autogenRelationships:
                  type: boolean
                  description: Automatically generate relationships from dbt
                  example: true
                branch:
                  type: string
                  minLength: 1
                  description: Git branch name
                  example: "main"
                committerEmail:
                  type: string
                  description: |
                    **Required when `authMethod: github_app` and commit signing is enabled**. Email address used for commit signing. Must match the email the signing key is registered against in GitHub. When blank on an update, preserves the existing committer email rather than clearing it.
                  example: "bot@example.com"
                committerName:
                  type: string
                  description: |
                    **Required when `authMethod: github_app` and commit signing is enabled**. Name used for commit signing. When blank on an update, preserves the existing committer name rather than clearing it.
                  example: "Omni Bot"
                dbtVersion:
                  type:
                    - string
                    - "null"
                  enum: ["Auto","1.10","1.11"]
                  description: "dbt version to use"
                  example: "1.11"
                enableSemanticLayer:
                  type: boolean
                  default: false
                  description: Enable [dbt semantic layer integration](/integrations/dbt/semantic-layer)
                  example: false
                enableVirtualSchemas:
                  type: boolean
                  description: Enable virtual schemas from dbt
                  example: false
                githubAppInstallationId:
                  type: string
                  pattern: ^[0-9]+$
                  description: |
                    **Required when `authMethod: github_app`**. GitHub App installation ID (numeric). Updates without an installation ID keep the existing repo config for metadata-only saves, but changing the URL or switching auth methods without one is rejected.
                  example: "12345678"
                projectRootPath:
                  anyOf:
                    - type: string
                      pattern: "^(?!\\/)(?!.*\\.\\.)[\\w ./-]+$"
                    - type: string
                      enum: [""]
                    - type: "null"
                  default: null
                  description: Path to dbt project root within repository
                  example: "dbt_project"
                rotateKeys:
                  type: boolean
                  default: false
                  description: |
                    Rotate SSH deploy keys. Only applicable when `authMethod: ssh`.
                  example: false
                sshUrl:
                  type: string
                  minLength: 1
                  description: |
                    Git repository URL. Varies depending on the `authMethod`:

                    - `ssh` - A string starting with `git@...`
                    - `https_token` - A string starting with `https://...`
                    - `github_app` - A string starting with `https://github.com/...`
                  example: "git@github.com:org/repo.git"
                token:
                  type: string
                  maxLength: 1000
                  pattern: ^[a-zA-Z0-9_\-.]+$
                  description: |
                    **Required when `authMethod: https_token`**. HTTPS token for authentication (deploy token, PAT, etc.). Write-only; never returned in GET responses.
      responses:
        '200':
          description: dbt configuration updated successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - autogenRelationships
                  - branch
                  - dbtVersion
                  - enableSemanticLayer
                  - enableVirtualSchemas
                  - projectRootPath
                  - sshUrl
                  - supportsDbt
                properties:
                  authMethod:
                    type: string
                    enum: [ssh, https_token, github_app]
                    description: |
                      The authentication method used for git operations:

                      - `ssh` - Deploy key
                      - `https_token` - Deploy token/PAT
                      - `github_app` - GitHub App installation
                    example: "ssh"
                  autogenRelationships:
                    type: boolean
                    description: Whether relationships are auto-generated from dbt
                    example: true
                  branch:
                    type: string
                    description: Git branch name
                    example: "main"
                  committerEmail:
                    type: [string, null]
                    description: Email address used for commit signing. Only returned when a commit signer is configured for GitHub App auth.
                    example: "bot@example.com"
                  committerName:
                    type: [string, null]
                    description: Name used for commit signing. Only returned when a commit signer is configured for GitHub App auth.
                    example: "Omni Bot"
                  commitSigningPublicKey:
                    type: [string, null]
                    description: SSH public key used to sign commits. Only returned when a commit signer is configured for GitHub App auth.
                    example: "ssh-ed25519 AAAA..."
                  dbtVersion:
                    type: string
                    description: dbt version being used
                    example: "Auto"
                  enableSemanticLayer:
                    type: boolean
                    description: Whether the [dbt semantic layer integration](/integrations/dbt/semantic-layer) is enabled
                    example: false
                  enableVirtualSchemas:
                    type: boolean
                    description: Whether virtual schemas are enabled
                    example: false
                  githubAppInstallationId:
                    type: [string, null]
                    description: GitHub App installation ID. Only returned when `authMethod` is `github_app`.
                    example: "12345678"
                  projectRootPath:
                    type:
                      - string
                      - "null"
                    description: Path to dbt project root
                    example: "dbt_project"
                  sshUrl:
                    type: string
                    description: Git repository URL
                    example: "git@github.com:org/repo.git"
                  supportsDbt:
                    type: boolean
                    description: Whether the connection dialect supports dbt
                    example: true
        '400':
          description: Invalid request body or validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Permission denied - connection admin role required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      operationId: connectionsDbtDelete
      summary: Delete dbt configuration
      description: |
        <Note>
          **Connection Admin** permissions are required to use this endpoint.
        </Note>

        Remove the dbt configuration for the specified connection by removing the link to the dbt repository.
      tags:
        - dbt
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
            description: ID of the connection where dbt is configured
            example: "550e8400-e29b-41d4-a716-446655440000"
      responses:
        '200':
          description: dbt configuration deleted successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - message
                  - success
                properties:
                  message:
                    type: string
                    description: Success message
                    example: "dbt repository unlinked successfully"
                  success:
                    type: boolean
                    description: Whether the operation succeeded
                    example: true
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Permission denied - Connection Admin role required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Connection not found or dbt not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/connections/{connectionId}/dbt/environments:
    post:
      tags:
        - dbt
      summary: Create dbt environment
      description: |
        <Note>
          This endpoint requires **Connection Admin** permissions for the connection.
        </Note>

        Create a new dbt environment on the specified connection, enabling programmatic environment management.
        
        A dbt environment allows you to connect Omni to a specific dbt project and manage dbt-related functionality within the connection context.
      security:
        - bearerAuth: []
      operationId: createDbtEnvironment
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the connection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - targetSchema
              properties:
                name:
                  type: string
                  minLength: 1
                  description: The name of the dbt environment
                  example: "Production dbt"
                targetSchema:
                  type: string
                  minLength: 1
                  description: The target schema for the environment
                  example: "analytics"
                isDeferralEnabled:
                  type: boolean
                  default: false
                  description: |
                    Whether deferral is enabled for this environment. Defaults to `false`. Ignored (forced to `false`) for the default (production) environment.
                  example: false
                ownerId:
                  type: string
                  format: uuid
                  nullable: true
                  description: |
                    **Only valid with Organization API keys.** The user ID of the owner for the dbt environment. Personal Access Tokens (PATs) automatically use the authenticated user as the owner.
                  example: "550e8400-e29b-41d4-a716-446655440000"
                targetDatabase:
                  type: string
                  nullable: true
                  description: Target database override
                  example: "analytics_db"
                targetName:
                  type: string
                  nullable: true
                  description: Target name override
                  example: "my_project"
                targetRole:
                  type: string
                  nullable: true
                  description: Target role override
                  example: "transformer"
                variables:
                  type: array
                  default: []
                  description: |
                    Environment variables for the dbt environment. **Note**: While `variables` is not required, at least one variable object must be defined if `variables` is included in the request body.
                  items:
                    type: object
                    required:
                      - name
                      - value
                      - isSecret
                    properties:
                      name:
                        type: string
                        minLength: 1
                        description: Variable name
                        example: "DBT_TARGET"
                      value:
                        type: string
                        description: Variable value
                        example: "production"
                      isSecret:
                        type: boolean
                        description: Whether the variable value is secret
                        example: false
            examples:
              basic:
                summary: Basic dbt environment
                value:
                  name: "Production dbt"
                  targetSchema: "analytics"
              withOwner:
                summary: dbt environment with specific owner
                value:
                  name: "Production dbt"
                  targetSchema: "analytics"
                  ownerId: "550e8400-e29b-41d4-a716-446655440000"
              withVariables:
                summary: dbt environment with variables
                value:
                  name: "Production dbt"
                  targetSchema: "analytics"
                  targetDatabase: "analytics_db"
                  variables:
                    - name: "DBT_TARGET"
                      value: "production"
                      isSecret: false
                    - name: "DBT_API_KEY"
                      value: "sk-..."
                      isSecret: true
      responses:
        '201':
          description: dbt environment created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DbtEnvironment'
              example:
                id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                name: "Production dbt"
                isDefaultEnvironment: false
                isDeferralEnabled: false
                ownerId: "550e8400-e29b-41d4-a716-446655440000"
                targetDatabase: "analytics_db"
                targetName: null
                targetRole: null
                targetSchema: "analytics"
                variables:
                  - id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    name: "DBT_TARGET"
                    value: "production"
                    isSecret: false
                  - id: "c3d4e5f6-a7b8-9012-cdef-123456789012"
                    name: "DBT_API_KEY"
                    value: null
                    isSecret: true
        '400':
          description: |
            Bad Request

            Possible error messages:
            - Invalid request body
            - User specified in `ownerId` is not a member of the organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible causes:
            - Insufficient permissions. **Connection Admin** permissions for the connection are required.
            - Non-admin user attempting to set `ownerId` for another user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - dbt
      summary: List dbt environments
      description: |
        <Note>
          This endpoint requires **Connection Admin** permissions for the connection.
        </Note>
        
        Retrieves a list of all dbt environments configured for the specified connection.
      security:
        - bearerAuth: []
      operationId: listDbtEnvironments
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the connection
      responses:
        '200':
          description: List of dbt environments retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DbtEnvironment'
              examples:
                multipleEnvironments:
                  summary: Multiple environments
                  value:
                    - id: "247dc6dc-2a58-4688-9521-c5ed3e99c1e8"
                      name: "Production"
                      isDefaultEnvironment: true
                      isDeferralEnabled: false
                      ownerId: null
                      targetDatabase: null
                      targetName: null
                      targetRole: null
                      targetSchema: "public"
                      variables: []
                    - id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                      name: "Development"
                      isDefaultEnvironment: false
                      isDeferralEnabled: true
                      ownerId: "550e8400-e29b-41d4-a716-446655440000"
                      targetDatabase: "dev_analytics_db"
                      targetName: null
                      targetRole: null
                      targetSchema: "analytics_dev"
                      variables:
                        - id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                          name: "DBT_TARGET"
                          value: "dev"
                          isSecret: false
                        - id: "c3d4e5f6-a7b8-9012-cdef-123456789012"
                          name: "DBT_API_KEY"
                          value: null
                          isSecret: true
                emptyList:
                  summary: No environments configured
                  value: []
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible causes:
            - Insufficient permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    
  /v1/connections/{connectionId}/dbt/environments/{environmentId}:
    put:
      tags:
        - dbt
      summary: Update dbt environment
      description: |
        <Note>
          This endpoint requires **Connection Admin** permissions for the connection.
        </Note>
        
        Update an existing dbt environment on the specified connection. All fields are optional; only the provided fields will be updated.
      security:
        - bearerAuth: []
      operationId: updateDbtEnvironment
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the connection
        - name: environmentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the dbt environment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  minLength: 1
                  description: The name of the dbt environment
                  example: "Development"
                targetSchema:
                  type: string
                  minLength: 1
                  description: The target schema for the environment
                  example: "dev_schema"
                isDeferralEnabled:
                  type: boolean
                  description: |
                    Whether deferral is enabled for this environment. Ignored (forced to `false`) for the default (production) environment.
                  example: true
                ownerId:
                  type: string
                  format: uuid
                  nullable: true
                  description: |
                    **Only valid with Organization API keys.** The user ID of the owner for the dbt environment. Personal Access Tokens (PATs) automatically use the authenticated user as the owner.
                  example: "550e8400-e29b-41d4-a716-446655440000"
                targetDatabase:
                  type: string
                  nullable: true
                  description: Target database override
                  example: "analytics_dev"
                targetName:
                  type: string
                  nullable: true
                  description: Target name override
                  example: "custom_target"
                targetRole:
                  type: string
                  nullable: true
                  description: Target role override
                  example: "developer"
                variables:
                  type: array
                  description: |
                    Environment variables for the dbt environment. To update existing variables, include the variable `id` field. New variables should omit the `id` field.
                  items:
                    type: object
                    required:
                      - name
                      - value
                      - isSecret
                    properties:
                      id:
                        type: string
                        format: uuid
                        description: Variable identifier. Required for updating existing variables, otherwise omit.
                        example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                      name:
                        type: string
                        minLength: 1
                        description: Variable name. Required for new variables, otherwise omit.
                        example: "DBT_TARGET"
                      value:
                        type: string
                        description: Variable value. Required for new variables, otherwise omit or set to `null` to keep the existing value for secrets.
                        example: "production"
                      isSecret:
                        type: boolean
                        description: Whether the variable value is secret. Required for new and updating existing variables.
                        example: false
            examples:
              basic:
                summary: Update name and schema
                value:
                  name: "Development"
                  targetSchema: "dev_schema"
              withOwner:
                summary: Update ownership
                value:
                  name: "Production"
                  ownerId: "550e8400-e29b-41d4-a716-446655440000"
              withVariables:
                summary: Update with variables
                value:
                  name: "Production"
                  targetSchema: "dev_schema"
                  variables:
                    - id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                      name: "DBT_TARGET"
                      value: "staging"
                      isSecret: false
                    - name: "NEW_VAR"
                      value: "new_value"
                      isSecret: false
      responses:
        '200':
          description: dbt environment updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DbtEnvironment'
              example:
                id: "247dc6dc-2a58-4688-9521-c5ed3e99c1e8"
                name: "Development"
                isDefaultEnvironment: false
                isDeferralEnabled: true
                ownerId: 550e8400-e29b-41d4-a716-446655440000
                targetDatabase: "analytics_dev"
                targetName: "custom_target"
                targetRole: "developer"
                targetSchema: "dev_schema"
                variables:
                  id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                  name: "DBT_TARGET"
                  value: "staging"
                  isSecret: false
        '400':
          description: |
            Bad Request

            Possible error messages:
            - Invalid request body
            - User specified in `ownerId` is not a member of the organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible causes:
            - Insufficient permissions. **Connection Admin** permissions for the connection are required.
            - Non-admin user attempting to set `ownerId` for another user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Connection or dbt environment not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - dbt
      summary: Delete dbt environment
      description: |
        <Note>
          This endpoint requires **Connection Admin** permissions for the connection.
        </Note>

        Delete an existing dbt environment from the specified connection. This action cannot be undone.
      security:
        - bearerAuth: []
      operationId: deleteDbtEnvironment
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the connection
        - name: environmentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the dbt environment to delete
      responses:
        '200':
          description: dbt environment deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible causes:
            - Insufficient permissions. **Connection Admin** permissions for the connection are required.
            - Connection dialect does not support dbt environments
            - Attempting to access a resource in a different organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Connection or dbt environment not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents:
    post: 
      tags:
        - Documents
      summary: Create document
      deprecated: true
      description: |
        <Warning>
          This endpoint is deprecated. Use the [Documents v2 API](/api/documents-v2/create-document) instead.
        </Warning>

        Create a new document
      security:
        - bearerAuth: []
      operationId: createDocument
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - modelId
                - name
              properties:
                modelId:
                  type: string
                  format: uuid
                  description: The ID of the model to build the document on
                name:
                  type: string
                  description: The name of the document
                description:
                  type: string
                  minLength: 1
                  maxLength: 1024
                  description: Description of the document
                identifier:
                  type: string
                  description: |
                    Custom identifier for the document. If not provided, an identifier will be auto-generated.

                    The identifier is used in the document's URL and must be unique across all documents in your organization.

                    **Format requirements:**
                    
                    - Must match the pattern: lowercase letters, numbers, hyphens, and underscores only
                    - Cannot start or end with a hyphen or underscore

                    **Note:** Once created, the identifier can be changed using the [Rename document identifier endpoint](/api/documents-v2/rename-document-identifier).
                  example: "sales-dashboard-2026"
                queryPresentations:
                  type: array
                  description: An array of query presentation objects, each representing a query in the document's workbook
                  items:
                    type: object
                    properties:
                      name:
                        type: string
                        description: The name of the query
                      description:
                        type: string
                        description: A description of the query
                      query:
                        type: object
                        description: The query definition object
                      visConfig:
                        type: object
                        description: Visualization configuration for the query
            examples:
              basicDocument:
                summary: Create document with auto-generated identifier
                value:
                  modelId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  name: "Sales Dashboard"
                  description: "Q1 2026 sales performance metrics"
              customIdentifier:
                summary: Create document with custom identifier
                value:
                  modelId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  name: "Sales Dashboard"
                  identifier: "sales-dashboard-2026"
                  description: "Q1 2026 sales performance metrics"
      responses:
        '200':
          description: Document created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  dashboard:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                        description: The dashboard ID
                      createdAt:
                        type: string
                        format: date-time
                        description: Timestamp when the dashboard was created
                      updatedAt:
                        type: string
                        format: date-time
                        description: Timestamp when the dashboard was last updated
                      metadata:
                        type: object
                        description: Dashboard metadata including layouts and tile settings
                        properties:
                          layouts:
                            type: object
                            description: Layout configurations for different screen sizes
                          textTiles:
                            type: array
                            items:
                              type: object
                          hiddenTiles:
                            type: array
                            items:
                              type: string
                          tileSettings:
                            type: object
                          tileFilterMap:
                            type: object
                          tileControlMap:
                            type: object
                      metadataVersion:
                        type: integer
                        description: Version of the metadata schema
                      refreshInterval:
                        type: integer
                        description: Auto-refresh interval in seconds
                      facetFilters:
                        type: boolean
                        description: Whether facet filters are enabled
                      organizationId:
                        type: string
                        format: uuid
                        description: The organization ID
                      workbookId:
                        type: string
                        format: uuid
                        description: The associated workbook ID
                      creatorId:
                        type: string
                        format: uuid
                        description: The ID of the user who created the dashboard
                      updaterId:
                        type: string
                        format: uuid
                        description: The ID of the user who last updated the dashboard
                      queryPresentationCollectionId:
                        type: string
                        format: uuid
                        description: The query presentation collection ID
                      dashboardId:
                        type: string
                        format: uuid
                        description: The dashboard ID (same as id)
                  workbook:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                        description: The workbook ID
                      createdAt:
                        type: string
                        format: date-time
                        description: Timestamp when the workbook was created
                      updatedAt:
                        type: string
                        format: date-time
                        description: Timestamp when the workbook was last updated
                      deletedAt:
                        type: string
                        format: date-time
                        description: Timestamp when the workbook was deleted, if applicable
                      publishedAt:
                        type: string
                        format: date-time
                        description: Timestamp when the workbook was published
                      isDraft:
                        type: boolean
                        description: Whether the workbook is a draft
                      identifier:
                        type: string
                        description: The unique identifier for the document
                      name:
                        type: string
                        description: The name of the document
                      lastItemIndex:
                        type: integer
                        description: Index of the last item in the workbook
                      ephemeral:
                        type: string
                        description: Ephemeral state identifier
                      organizationRole:
                        type: string
                        description: Organization-level role for the document
                      organizationAccessBoost:
                        type: boolean
                      publicRole:
                        type: string
                      publicAccessBoost:
                        type: boolean
                      canAnalyze:
                        type: boolean
                        description: Whether users can analyze data in the document
                      canDownload:
                        type: boolean
                        description: Whether users can download data from the document
                      canDrill:
                        type: boolean
                        description: Whether users can drill into data in the document
                      canSchedule:
                        type: boolean
                        description: Whether users can create schedules in the document
                      canUpload:
                        type: boolean
                        description: Whether users can upload data in the document's workbook
                      canViewWorkbook:
                        type: boolean
                        description: Whether users can view the workbook in the document
                      canUseTimezoneOverride:
                        type: boolean
                        description: Whether users can use the [timezone picker](/visualize-present/dashboards/timezone-override) to change the query timezone for this document
                      organizationId:
                        type: string
                        format: uuid
                        description: The organization ID
                      ownerId:
                        type: string
                        format: uuid
                        description: The ID of the document owner
                      updaterId:
                        type: string
                        format: uuid
                        description: The ID of the user who last updated the document
                      folderId:
                        type: string
                        format: uuid
                        description: The folder ID, if the document is in a folder
                      originDocumentId:
                        type: string
                        format: uuid
                        description: The origin document ID, if duplicated
                      documentId:
                        type: string
                        format: uuid
                        description: The document ID
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `modelId: Required`
            - `name: Required`
            - Identifier format is invalid (must be lowercase letters, numbers, hyphens, and underscores; cannot start/end with hyphen or underscore)
            - Identifier is already in use by another document
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - Documents
      summary: List documents
      description: List documents with pagination and filtering
      security:
        - bearerAuth: []
      operationId: listDocuments
      parameters:
        - name: include
          in: query
          schema:
            type: string
          description: |
            Comma-separated list of additional fields to include in the response:
            - `_count` - Adds favorite and view count metrics
            - `labels` - Includes associated document labels
            - `includeDeleted` - Include deleted documents
            - `onlyFavorites` - **Must be used with `userId`.** Include only documents that the specified user has favorited.
            - `onlySharedWithMe` - Returns only documents explicitly shared with the specified user via documents permissions. Excludes documents owned by the user. Cannot be combined with `ownerId` or `path`.

               Additionally, depending on the type of API key being used:

               - **Organization API keys** require the `userId` parameter
               - **Personal Access Tokens** automatically infer the `userId` from the token
        - name: labels
          in: query
          schema:
            type: string
          description: |
            Comma-separated list of labels to filter results. For example: `finance,marketing`
        - name: folderId
          in: query
          schema:
            type: string
            format: uuid
          description: ID of the folder to filter results. Returns only documents within the specified folder.
        - name: pageSize
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of records per page
        - name: sortField
          in: query
          schema:
            type: string
            enum: [favorites, name, updatedAt, visits]
            default: name
          description: |
            Field to sort by:
            - `favorites` - Sort by the number of favorites
            - `name` - Sort by document name
            - `updatedAt` - Sort by last update time
            - `visits` - Sort by view count
        - name: sortDirection
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: desc
          description: Sort direction (`asc` for ascending, `desc` for descending)
        - name: cursor
          in: query
          schema:
            type: string
          description: Cursor for pagination. Used with `sortField`/`sortDirection` for relative positioning.
        - name: userId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            The ID of a standard or embed user to filter results, returning only documents the specified user can view based on their permissions.

            If `include=onlyFavorites` is specified:

            - **And using an Organization API key**, this parameter is **required**
            - **And using a Personal Access Token**, the `userId` will be automatically inferred from the token
        - name: creatorId
          in: query
          schema:
            type: string
            format: uuid
          description: ID of the user who created the document(s), returning only documents that the specified user created.
      responses:
        '200':
          description: Paginated document list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentsListResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `pageSize: Page size must be at least 1`
            - `pageSize: Page size cannot exceed 100`
            - `sortField: Invalid enum value. Expected 'favorites' | 'name' | 'updatedAt' | 'visits', received '<invalidField>'`
            - `creatorId: Invalid uuid`
            - `userId: Invalid uuid`
            - `formErrors: Unrecognized key(s) in object: '<unknownParameter>'`
            - `onlySharedWithMe requires userId` - When using `include=onlySharedWithMe` without providing a `userId` parameter
            - `onlySharedWithMe cannot be combined with onlyFavorites` - When using both `include=onlySharedWithMe` and `include=onlyFavorites` together
            - `onlySharedWithMe cannot be combined with folderId` - When using `include=onlySharedWithMe` with a `folderId` parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - `User with id <uuid> does not exist`

            **Note**: An invalid UUID format will result in a 400 error. This 404 error occurs when the provided ID is a valid UUID format but the user cannot be found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/move:
    put:
      tags:
        - Documents
      summary: Move document
      description: |
        Move a document to new folder or change scope.
      security:
        - bearerAuth: []
      operationId: moveDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The ID of the document to move. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - folderPath
              properties:
                folderPath:
                  oneOf:
                    - type: string
                    - type: "null"
                  description: The path of the destination folder. Use `null` to move the document to the root level (no folder).
                scope:
                  type: string
                  enum: [organization, restricted]
                  description: |
                    Optional sharing scope for the document:
                    - `organization` - Organization-wide access
                    - `restricted` - Limited access

                    If not provided, the scope will be computed based on the document or the destination folder.

                    **Note**: When providing a scope, it must match the destination folder's scope.
      responses:
        '200':
          description: Document moved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Scope "<scope>" and folder scope "<folderScope>" do not match`
            - `Invalid method`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages/causes:

            - `User-scoped API keys cannot act on behalf of other users`
            - Operations targeting personal/restricted scope require the user's **Personal content access** setting (`omni_allows_personal_content` system attribute) to be enabled for the user. If not enabled, the API will respond with `403 Forbidden`.
        '404':
          description: |
            Not Found

            Possible error messages:
            - `Document with identifier "<documentId>" not found`
            - `Folder with path <path> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/duplicate:
    post:
      tags:
        - Documents
      summary: Duplicate published document
      description: Duplicate a published document.
      x-mint:
        content: |
          <Warning>
            Only published documents can be duplicated. Attempting to duplicate a draft document will return a 404 error.
          </Warning>
      security:
        - bearerAuth: []
      operationId: duplicateDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Document identifier
        - name: userId
          in: query
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Optional user ID that attributes the action to the specified user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 255
                  description: The name for the duplicated document
                folderPath:
                  oneOf:
                    - type: string
                    - type: "null"
                  description: Path to the destination folder. If not provided, the document will be duplicated to the root.
                scope:
                  type: string
                  enum: [organization, restricted]
                  description: |
                    The visibility scope for the duplicated document.

                    If not provided, inherits from the destination folder or defaults to `restricted`.
            example:
              name: "My Dashboard Copy"
              folderPath: "/reports/2024/q4"
              scope: "organization"
      responses:
        '201':
          description: Document duplicated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  dashboardId:
                    type: string
                    description: The dashboard ID, if the duplicated document has a dashboard
                  identifier:
                    type: string
                    description: Unique identifier for the newly created document
                  name:
                    type: string
                    description: The name of the duplicated document
                  workbookId:
                    type: string
                    description: The workbook ID of the duplicated document
              example:
                dashboardId: "abc123-dash-id"
                identifier: "xyz789-new-identifier"
                name: "My Dashboard Copy"
                workbookId: "def456-workbook-id"
        '400':
          description: |
            Bad Request. Possible error messages:

            - `name: Name cannot be empty`
            - `name: Name must be 255 characters or less`
            - `Scope "organization" and folder scope "restricted" do not match`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Insufficient permissions. Possible error messages/causes:

            - `User does not have access to create content on the specified connection`
            - `User-scoped API keys can only be used to act on behalf of the authenticated user.`
            - `Document duplication is disabled`. The document's duplication permission is disabled.
            - Operations targeting personal/restricted scope require the user's **Personal content access** setting (`omni_allows_personal_content` system attribute) to be enabled for the user. If not enabled, the API will respond with `403 Forbidden`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible error messages:

            - `Document with identifier "<documentId>" not found`
            - `Published document with id "<documentId>" does not exist`. This will surface when attempting to duplicate a draft.
            - `Folder with path "<folderPath>" does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/upgrade:
    post:
      tags:
        - Documents
      summary: Upgrade dashboard layout
      description: |
        Upgrade a document to the advanced dashboard layout — the same action available in the UI under **File > Upgrade layout**.

        <Note>
          This operation is idempotent. If the document already uses the advanced layout, the request is a no-op and returns `upgraded: false`.
        </Note>

        For published documents the upgrade runs through a draft-and-publish workflow automatically. If a draft already exists, the request returns `409` unless `clearExistingDraft` is set to `true`, which discards the existing draft before upgrading.
      operationId: documentsUpgradeLayout
      parameters:
        - name: identifier
          in: path
          required: true
          schema:
            type: string
          description: Document identifier (either document ID or identifier slug)
          example: abc123
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentsUpgradeLayoutBody'
            example:
              clearExistingDraft: true
      responses:
        '200':
          description: Layout upgraded, or no-op if the document already had advanced layout.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentsUpgradeLayoutResponse'
              example:
                identifier: abc123
                upgraded: true
        '400':
          description: |
            Bad Request

            - The document does not have a dashboard to upgrade.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '409':
          description: A draft already exists for the published document; set `clearExistingDraft` to `true` to override.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError409'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/transfer-ownership:
    put:
      tags:
        - Documents
      summary: Transfer document ownership
      deprecated: true
      description: |
        <Warning>
          This endpoint is deprecated. Use the [Grant document permissions](/api/document-permissions/grant-document-permissions) and [Update document permissions](/api/document-permissions/update-document-permissions) endpoints to assign document ownership.
        </Warning>

        Transfer ownership of a document to another user in the organization.

        To successfully transfer a document:

        - The user performing the transfer must have `MANAGER` permissions or higher on the document
        - The new owner must be a member of the organization AND have explicit access to the document

        Upon success, the following occurs:

        - The named user is granted `OWNER` role on the document
        - All other users with `OWNER` role are demoted to `MANAGER` permissions on the document
        - Explicit permits for the new owner are revoked, as they now have owner access
        - Content search index is updated

        **Note:** This operation does not modify the document's creator, and does not move a restricted document out of its current folder. Transferring to the current owner is a no-op and returns success.
      security:
        - bearerAuth: []
      operationId: transferDocumentOwnership
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The ID of the document to transfer. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - userId
              properties:
                userId:
                  type: string
                  description: The membership ID of the user to transfer ownership to. This must be a valid organization member who has explicit permission on the document.
            example:
              userId: "9e8719d9-276a-4964-9395-a493189a247c"
      responses:
        '200':
          description: |
            Document ownership transferred successfully.

            **Note:** This response is also returned when transferring to the current owner.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                success: true
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid JSON`
            - `userId is required`
            - `New owner must have explicit document permission`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalidJson:
                  summary: Invalid JSON body
                  value:
                    error: "Invalid JSON"
                missingUserId:
                  summary: Missing userId field
                  value:
                    error: "userId is required"
                noPermission:
                  summary: New owner lacks document permission
                  value:
                    error: "New owner must have explicit document permission"
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Insufficient permissions` - Acting user does not have `MANAGER` or higher permissions on the document
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "Insufficient permissions"
        '404':
          description: |
            Not Found

            Possible error messages:
            
            - `Document with identifier "<documentId>" not found`
            - `User not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                documentNotFound:
                  summary: Document not found
                  value:
                    error: "Document with identifier \"doc-123\" not found"
                userNotFound:
                  summary: User not found
                  value:
                    error: "User not found"
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/draft:
    post:
      tags:
        - Documents
      summary: Create draft
      deprecated: true
      description: |
        <Warning>
          This endpoint is deprecated. Use the [Documents v2 API](/api/documents-v2/create-draft-and-patch-document) instead.
        </Warning>

        <Note>
          This endpoint requires **Editor** permissions or higher on the specified document.
        </Note>

        Create a [draft](/content/develop/drafts) of the specified document. Drafts are isolated copies of a document - including its workbook and dashboard - that can be modified without impacting the published version.

        To create a draft on a [branch](/content/develop/branch-mode), include a `branchId` in the request body. Each branch can have one draft per document.

        If a draft already exists for the document (or branch, if specified), the existing draft is returned instead of creating a new one.
      security:
        - bearerAuth: []
      operationId: createDraft
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The ID of the document to create a draft for. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                branchId:
                  type: string
                  format: uuid
                  description: The UUID of the branch to create the draft on. Required when creating a draft on a branch. To retrieve branch IDs, use the [List models](/api/models/list-models) endpoint with `modelKind=BRANCH`.
      responses:
        '200':
          description: Draft created successfully (or existing draft returned)
          content:
            application/json:
              schema:
                type: object
                properties:
                  identifier:
                    type: string
                    description: "Identifier of the new draft. If a draft already exists for the document or branch specified, this will be the identifier of the existing draft."
                    example: "93874a87"
        '400':
          description: |
            Bad request

            Possible error messages:
            - Document is not eligible for publishing workflow
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:
            - Permission denied - EDITOR role required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - `Document with identifier <documentId> not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

    delete:
      tags:
        - Documents
      summary: Archive draft
      description: |
        Archive the current draft of the specified document. Archived drafts are placed in the [**Archived** section of the **Drafts** drawer](/content/develop/drafts#archiving-drafts) in the document's workbook and retained for 30 days.

        If a branch is specified, the current draft on the branch is archived.
      security:
        - bearerAuth: []
      operationId: archiveDraft
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The ID of the document to archive the draft from. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                branchId:
                  type: string
                  format: uuid
                  description: The UUID of the branch the draft is attached to. To retrieve branch IDs, use the [List models](/api/models/list-models) endpoint with `modelKind=BRANCH`.
      responses:
        '200':
          description: Draft archived successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "Draft deleted successfully"
        '404':
          description: |
            Not Found

            Possible error messages:
            - `Document with identifier <documentId> not found`
            - `Draft with id <documentId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/drafts:
    get:
      tags:
        - Documents
      summary: List document drafts
      description: |
        <Note>
          This endpoint requires **Viewer** permissions or higher on the specified document.
        </Note>

        List all drafts attached to a published document. A document in Omni can have up to one draft per branch plus one draft not attached to a branch.
        
        By default, returns active drafts only. Pass `?include=archived` to include soft-deleted drafts retained for approximately 7 days.
      security:
        - bearerAuth: []
      operationId: listDocumentDrafts
      parameters:
        - name: identifier
          in: path
          required: true
          schema:
            type: string
          description: |
            The identifier of the published document. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
        - name: include
          in: query
          required: false
          schema:
            type: string
            enum:
              - archived
          description: |
            Pass `archived` to include soft-deleted drafts in the response. By default, only active drafts are returned.
          example: archived
      responses:
        '200':
          description: |
            List of drafts retrieved successfully. Results are sorted in descending order using the draft's `updatedAt`.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    identifier:
                      type: string
                      description: The identifier of the draft.
                      example: "d56b7f56"
                    publishedIdentifier:
                      type: string
                      description: The identifier of the published document this draft is attached to.
                      example: "ecd01fe5"
                    workbookModelId:
                      type: string
                      format: uuid
                      description: The UUID of the workbook model.
                      example: "d61034e2-05fe-4268-a3a7-d4cdb8d650e9"
                    branch:
                      type: object
                      nullable: true
                      description: The branch this draft is attached to, or `null` for draft not attached to a branch. A document can only have one draft not attached to a branch.
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: The UUID of the branch.
                        name:
                          type: string
                          nullable: true
                          description: The name of the branch. Can be `null` if the branch is unnamed.
                      example:
                        id: "a9bc51d2-1234-5678-9abc-def012345678"
                        name: "governance/relabel-age-average"
                    status:
                      type: string
                      enum:
                        - active
                        - archived
                      description: The status of the draft.
                      example: "active"
                    draftOutOfDate:
                      type: boolean
                      description: Indicates whether the draft is [out of date](/content/develop/branch-mode#working-with-out-of-date-drafts) compared to the published document.
                      example: false
                    createdAt:
                      type: string
                      format: date-time
                      description: Timestamp when the draft was created.
                      example: "2026-05-06T13:44:00.000Z"
                    updatedAt:
                      type: string
                      format: date-time
                      description: Timestamp when the draft was last updated.
                      example: "2026-05-06T13:44:26.000Z"
                    createdBy:
                      type: object
                      description: User who created the draft.
                      properties:
                        name:
                          type: string
                          description: Display name of the user who created the draft.
                      example:
                        name: "Blob Ross"
                    lastEditedBy:
                      type: object
                      description: User who last edited the draft.
                      properties:
                        name:
                          type: string
                          description: Display name of the user who last edited the draft.
                      example:
                        name: "Blob Ross"
              example:
                - identifier: "d56b7f56"
                  publishedIdentifier: "ecd01fe5"
                  workbookModelId: "d61034e2-05fe-4268-a3a7-d4cdb8d650e9"
                  branch:
                    id: "a9bc51d2-1234-5678-9abc-def012345678"
                    name: "governance/relabel-age-average"
                  status: "active"
                  draftOutOfDate: false
                  createdAt: "2026-05-06T13:44:00.000Z"
                  updatedAt: "2026-05-06T13:44:26.000Z"
                  createdBy:
                    name: "Blob Ross"
                  lastEditedBy:
                    name: "Blob Ross"
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Invalid include parameter value`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:
            - `You do not have permission to view this document.` - Authenticating user must have **Viewer** permissions or higher on the document
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - `Document with identifier <identifier> not found`
            - `Published document with id <identifier> does not exist` - Document exists but has not been published
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/queries:
    get:
      tags:
        - Documents
      summary: Get document queries
      description: |
        Retrieve queries associated with a document by its identifier.
      x-mint:
        content: |
          <Tip>
            Check out the [Running document queries with APIs guide](/guides/api/run-document-queries) to learn how to programmatically run the queries you retrieve with this API.
          </Tip>
      security:
        - bearerAuth: []
      operationId: getDocumentQueries
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The unique identifier of the document. This can be retrieved by:
            - **Opening the document settings**: Navigate to **File > Document settings** and click **Settings**. The **Identifier** field contains the document ID.
            - **Using the dashboard's URL**: The string after `/dashboards/` is the identifier (e.g., `https://myorg.omniapp.co/dashboards/12db1a0a`)
      responses:
        '200':
          description: Queries retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  queries:
                    type: array
                    description: An array of queries used in the document
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: The UUID of the query
                        name:
                          type: string
                          description: The name of the query
                        url:
                          type: string
                          description: URL to view this specific query/sheet in the workbook
                          example: https://org.omni.co/w/abc123?key=1
                        query:
                          type: object
                          description: The query JSON structure that can be used with the Query API
        '403':
          description: |
            Forbidden

            Possible error messages:
            - `User cannot view document`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - `Document with id <identifier> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}:
    get:
      tags:
        - Documents
      summary: Get dashboard document
      deprecated: true
      description: |
        <Warning>
          This endpoint is deprecated. Use the [Documents v2 API](/api/documents-v2/get-document-state) instead.
        </Warning>

        Retrieve dashboard document configuration in a format compatible with PUT for round-trip editing. This endpoint returns all fields needed to modify and update a dashboard document.

        **Note:** This endpoint only supports dashboard documents. Workbook-only documents are not supported and will return a `400 Bad Request` error.
      security:
        - bearerAuth: []
      operationId: getDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The document identifier. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      responses:
        '200':
          description: Document retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                    description: The name of the document
                  modelId:
                    type: string
                    format: uuid
                    description: The ID of the model the document is built on
                  facetFilters:
                    type: boolean
                    description: Whether facet filters are enabled for the document
                  description:
                    type: string
                    description: The description of the document
                  refreshInterval:
                    type: integer
                    description: Auto-refresh interval in seconds
                  filterConfig:
                    type: object
                    description: Filter configuration for the document
                  filterOrder:
                    type: array
                    items:
                      type: string
                    description: Array of filter field names defining the order of filters
                  queryPresentations:
                    type: array
                    description: Array of query presentation objects representing tabs in the document's workbook
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                          description: Name of the query tab
                        query:
                          type: object
                          description: Full OmniQuery object with all query fields
                          properties:
                            fields:
                              type: array
                              items:
                                type: string
                              description: Array of field names to include in the query
                            table:
                              type: string
                              description: The table to query from
                            limit:
                              type: integer
                              description: Maximum number of rows to return
                            filters:
                              type: object
                              description: Filter conditions for the query
                            sorts:
                              type: array
                              items:
                                type: object
                              description: Sort order for the query results
                            modelId:
                              type: string
                              format: uuid
                              description: The model ID for the query
                        chartType:
                          type: string
                          description: Chart type (e.g., "bar", "line", "area", "table")
                        visualization:
                          type: object
                          description: Visualization configuration
                          properties:
                            visType:
                              type: string
                              description: The visualization type (e.g., "basic")
                            config:
                              type: object
                              description: Visualization-specific configuration
                            fields:
                              type: array
                              items:
                                type: string
                              description: Fields used in the visualization
                        prefersChart:
                          type: boolean
                          description: Whether the query prefers chart view over table view
                        id:
                          type: string
                          format: uuid
                          description: Query presentation ID
                        queryIdentifierMapKey:
                          type: string
                          description: Query identifier map key
                        description:
                          type: string
                          description: Description of the query (if set)
                        subTitle:
                          type: string
                          description: Subtitle for the query (if set)
                        topicName:
                          type: string
                          description: Topic name associated with the query (if set)
                        aiConfig:
                          type: object
                          description: AI configuration for the query
                        resultConfig:
                          type: object
                          description: Result table configuration
              example:
                name: "Sales Dashboard"
                modelId: "abc123de-f456-7890-abcd-ef1234567890"
                facetFilters: true
                refreshInterval: 300
                filterConfig:
                  date_range:
                    type: "date"
                    defaultValue: "last_7_days"
                filterOrder:
                  - "date_range"
                  - "region"
                queryPresentations:
                  - name: "Revenue by Region"
                    query:
                      fields:
                        - "order_items.total_revenue"
                        - "orders.region"
                      table: "order_items"
                      limit: 1000
                      filters: {}
                      sorts: []
                      modelId: "abc123de-f456-7890-abcd-ef1234567890"
                    chartType: "bar"
                    prefersChart: true
                    visualization:
                      visType: "basic"
                      config:
                        xAxis: "orders.region"
                        yAxis: "order_items.total_revenue"
                      fields:
                        - "order_items.total_revenue"
                        - "orders.region"
                    id: "cd54494d-318f-4105-af1c-10af59e62b2e"
                    queryIdentifierMapKey: "1"
                    aiConfig: {}
                    resultConfig: {}
        '400':
          description: |
            Bad Request

            Possible error messages:
            - `Analysis documents are not supported`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Analysis documents are not supported"
                status: 400
        '403':
          description: |
            Forbidden. User does not have permission to view the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "User does not have permission to view this document"
                status: 403
        '404':
          description: |
            Not Found

            Possible error messages:
            - `Document with identifier "<documentId>" not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Document with identifier \"054518c8\" not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Documents
      summary: Delete document
      description: Delete a document (move to Trash)
      security:
        - bearerAuth: []
      operationId: deleteDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The ID of the document to delete. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      responses:
        '200':
          description: Document deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '404':
          description: |
            Not Found

            Possible error messages:
            - `Document with identifier "<documentId>" not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      tags:
        - Documents
      summary: Update document
      deprecated: true
      description: |
        <Warning>
          This endpoint has been removed as of July 31, 2026 and will return a `410` error unless your organization has been granted a migration extension. Contact Omni support if you need one.
          
          Use the [Documents v2 API](/api/documents-v2/patch-draft) instead.
        </Warning>

        Update the name, description, and/or identifier of a document. **Note**: At least one of `name`, `description`, or `identifier` must be provided or the API will return a `Bad Request` error.

        Renaming a document follows the app's draft/publish workflow:

        | Document Type | Behavior |
        |---------------|----------|
        | **Draft** | Renamed directly |
        | **Published (no existing draft)** | Creates draft > Renames draft > Publishes draft |
        | **Published (existing draft, `clearExistingDraft: true`)** | Discards existing draft > Creates new draft > Renames > Publishes |
        | **Published (existing draft, `clearExistingDraft` not set)** | Returns `409 Conflict` |
      security:
        - bearerAuth: []
      operationId: renameDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Document identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 255
                  description: New name for the document.
                description:
                  type: string
                  maxLength: 1024
                  description: Updated description of the document. Set as `null` to clear the description.
                clearExistingDraft:
                  type: boolean
                  description: |
                    If `true`, discards any existing draft before renaming.

                    **Note**: This parameter is required when a draft exists for the document.
                identifier:
                  type: string
                  description: |
                    New identifier for the document. Renaming the identifier works with published documents and maintains old URLs via automatic redirects.

                    When you change a document's identifier:
                    - Both the workbook and document identifiers are updated in one transaction
                    - A `DocumentIdentifierHistory` entry is created so requests to the old identifier automatically redirect to the new one
                    - Dashboard and document history entries are updated

                    **Format requirements:**
                    - Must match the pattern: lowercase letters, numbers, hyphens, and underscores only
                    - Cannot start or end with a hyphen or underscore
                    - Must be unique across all documents in your organization

                    **Common use case:** Migrating content between environments (e.g., staging → production) while maintaining specific identifiers.
                  example: "sales-dashboard-archived"
            examples:
              renameIdentifier:
                summary: Rename document identifier only
                value:
                  identifier: "sales-dashboard-archived"
              updateNameAndIdentifier:
                summary: Update both name and identifier
                value:
                  name: "Q2 2026 Sales Report"
                  identifier: "q2-sales-report"
              renameName:
                summary: Rename document name only
                value:
                  name: "Updated Dashboard Name"
      responses:
        '200':
          description: Document renamed successfully
          headers:
            Deprecation:
              description: Indicates that this endpoint is deprecated
              schema:
                type: string
                example: "true"
            Sunset:
              description: The date this endpoint will be removed (RFC 9110)
              schema:
                type: string
                format: date
                example: "2026-07-31"
            Link:
              description: Link to the replacement API (RFC 8288)
              schema:
                type: string
                example: "</api/documents-v2/create-draft-and-patch-document>; rel=\"successor-version\""
          content:
            application/json:
              schema:
                type: object
                properties:
                  identifier:
                    type: string
                    description: The document identifier
                  name:
                    type: string
                    description: The new document name
              examples:
                renameIdentifier:
                  summary: Rename document identifier
                  value:
                    identifier: "sales-dashboard-archived"
                    name: "Sales Dashboard"
                updateNameAndIdentifier:
                  summary: Update both name and identifier
                  value:
                    identifier: "q2-sales-report"
                    name: "Q2 2026 Sales Report"
        '400':
          description: |
            Bad Request. Possible causes:

            - Request body must have `name`, `description`, or `identifier`
            - Name exceeds 255 characters
            - Description exceeds 1024 characters
            - Identifier format is invalid (must be lowercase letters, numbers, hyphens, and underscores; cannot start/end with hyphen or underscore)
            - Identifier is already in use by another document
            - Draft already exists - set clearExistingDraft to true to discard it and proceed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: User has insufficient permissions to update document
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '409':
          description: |
            Conflict. Published document has an existing draft and `clearExistingDraft` is not set to `true`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '410':
          $ref: '#/components/schemas/ApiError410'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      tags:
        - Documents
      summary: Update dashboard document
      deprecated: true
      description: |
        <Warning>
          This endpoint has been removed as of July 31, 2026 and will return a `410` error unless your organization has been granted a migration extension. Contact Omni support if you need one.
          
          Use the [Documents v2 API](/api/documents-v2/patch-draft) instead.
        </Warning>

        Update a document with the specified identifier. This endpoint performs a full resource replacement — all required fields must be provided and existing query presentations are replaced entirely.
      x-mint:
        content: |
          The update operation follows the Omni's [draft/publish workflow](/content/develop):

          | Document Type | Behavior |
          |---------------|----------|
          | **Draft** | Updated directly |
          | **Published (no existing draft)** | Creates draft > Updates draft > Publishes draft |
          | **Published (existing draft, `clearExistingDraft: true`)** | Discards existing draft > Creates new draft > Updates > Publishes |
          | **Published (existing draft, `clearExistingDraft` not set)** | Returns `409 Conflict` |

          This endpoint follows Omni's draft/publish workflow. Updates to published documents create a draft, apply changes, then publish. Use the `clearExistingDraft` parameter to discard any existing draft before updating.
      security:
        - bearerAuth: []
      operationId: updateDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Document identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - modelId
                - name
                - facetFilters
                - refreshInterval
                - filterConfig
                - filterOrder
                - queryPresentations
              properties:
                modelId:
                  type: string
                  description: Model ID for query transformation
                name:
                  type: string
                  minLength: 1
                  maxLength: 254
                  description: Document name
                description:
                  type: string
                  maxLength: 1024
                  description: Optional description of the document. Set as `null` to clear the description.
                  example: "Total monthly sales. Used in sales review meetings."
                facetFilters:
                  type: boolean
                  description: When `true`, enable facet filters on the dashboard
                refreshInterval:
                  oneOf:
                    - type: integer
                      minimum: 60
                    - type: "null"
                  description: Auto-refresh interval in seconds. Minimum value must be `60`. Use `null` to disable.
                filterConfig:
                  type: object
                  description: |
                    Dashboard filter configuration. Pass `{}` for no filters.

                    Each key is a field name and the value is a filter definition object.
                  additionalProperties:
                    type: object
                    properties:
                      type:
                        type: string
                        description: Field type (e.g., "string", "number")
                      kind:
                        type: string
                        description: Filter kind (e.g., "EQUALS", "GREATER_THAN")
                      values:
                        type: array
                        items: {}
                        description: Filter values
                      is_negative:
                        type: boolean
                        description: When `true`, negate the filter condition
                filterOrder:
                  type: array
                  items:
                    type: string
                  description: Order of filters in the filter bar. Pass `[]` if no filters.
                queryPresentations:
                  type: array
                  minItems: 1
                  description: Array of query presentations (at least one required)
                  items:
                    type: object
                    required:
                      - name
                      - query
                    properties:
                      name:
                        type: string
                        description: Name of the query tab
                      query:
                        type: object
                        required:
                          - fields
                          - table
                        properties:
                          fields:
                            type: array
                            items:
                              type: string
                            description: Array of field names to include in the query
                          table:
                            type: string
                            description: Table name for the query
                        description: Query definition
                      description:
                        type: string
                        description: Query description
                      subTitle:
                        type: string
                        description: Subtitle for the query
                      prefersChart:
                        type: boolean
                        description: When `true`, show chart by default
                      topicName:
                        type: string
                        description: Topic name for the query
                      chartType:
                        oneOf:
                          - type: string
                          - type: "null"
                        description: Chart type (line, bar, etc.)
                      visConfig:
                        type: object
                        description: Full visualization configuration
                      resultConfig:
                        type: object
                        description: Result display configuration
                      aiConfig:
                        type: object
                        description: AI configuration
                documentMetadata:
                  type: object
                  description: Document metadata for presentation settings
                  properties:
                    presentation:
                      type: object
                      properties:
                        filters:
                          type: object
                          properties:
                            collapsible:
                              type: boolean
                              description: Whether filters are collapsible
                            defaultExpanded:
                              type: boolean
                              description: Whether filters are expanded by default
                clearExistingDraft:
                  type: boolean
                  description: |
                    If `true`, discards any existing draft before updating.

                    **Note**: This parameter is required when a draft exists for the document.
            example:
              modelId: "abc123"
              name: "Monthly Sales Dashboard"
              description: "Total monthly sales. Used in sales review meetings."
              facetFilters: true
              refreshInterval: null
              filterConfig:
                order_items.category:
                  type: "string"
                  kind: "EQUALS"
                  values:
                    - "Electronics"
                  is_negative: false
              filterOrder:
                - "order_items.category"
              queryPresentations:
                - name: "Sales Trend"
                  description: "Monthly sales over time"
                  prefersChart: true
                  topicName: "order_items"
                  query:
                    fields:
                      - "order_items.created_at[month]"
                      - "order_items.sale_price_sum"
                    table: "order_items"
                  visConfig:
                    visType: "basic"
                    spec:
                      mark:
                        type: "line"
              documentMetadata:
                presentation:
                  filters:
                    collapsible: true
                    defaultExpanded: false
      responses:
        '200':
          description: Document updated successfully
          headers:
            Deprecation:
              description: Indicates that this endpoint is deprecated
              schema:
                type: string
                example: "true"
            Sunset:
              description: The date this endpoint will be removed (RFC 9110)
              schema:
                type: string
                format: date
                example: "2026-07-31"
            Link:
              description: Link to the replacement API (RFC 8288)
              schema:
                type: string
                example: "</api/documents-v2/create-document>; rel=\"successor-version\""
          content:
            application/json:
              schema:
                type: object
                properties:
                  identifier:
                    type: string
                    description: The document identifier
                  name:
                    type: string
                    description: The updated document name
                  description:
                    type: string
                    description: The updated document description
              example:
                identifier: "my-dashboard"
                name: "Q4 Sales Dashboard"
                description: "Total monthly sales. Used in sales review meetings."
        '400':
          description: |
            Bad Request. Possible causes:

            - Missing required fields (`modelId`, `name`, `facetFilters`, `refreshInterval`, `filterConfig`, `filterOrder`, `queryPresentations`)
            - Invalid field values (`name` too long, `refreshInterval` less than `60`)
            - Empty `queryPresentations` array (at least one required)
            - Document has no dashboard (workbook-only document)
            - Analysis documents (workbooks or documents without a dashboard) are not supported
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: User has insufficient permissions to update document
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: |
            Conflict. Draft already exists - set clearExistingDraft to true to discard it and proceed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '410':
          $ref: '#/components/schemas/ApiError410'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/access-list:
    get:
      tags:
        - Document permissions
      summary: all users and groups with document access
      description: |
        List all users and groups with access to a document. 
        
        The paginated response includes a list of `principal` objects, where each entry represents a distinct access grant with its own role and settings. A `principal` may appear twice in the response if they have both `direct` access and `folder`-based access to the same document. 
      security:
        - bearerAuth: []
      operationId: listDocumentAccessList
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The document identifier. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
        - name: pageSize
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of results per page (1-100).
        - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: Pagination cursor from a previous response's `pageInfo.nextCursor`.
        - name: sortField
          in: query
          required: false
          schema:
            type: string
            enum: [name, email, role]
            default: name
          description: Field to sort results by.
        - name: sortDirection
          in: query
          required: false
          schema:
            type: string
            enum: [asc, desc]
            default: asc
          description: Sort order.
        - name: accessSource
          in: query
          required: false
          schema:
            type: string
            enum: [direct, folder]
          description: |
            Filter by how access was granted:
            - `direct` — Only principals with explicit document permissions
            - `folder` — Only principals with inherited folder permissions
        - name: type
          in: query
          required: false
          schema:
            type: string
            enum: [user, userGroup]
          description: |
            Filter by principal type:
            - `user` — Only individual users
            - `userGroup` — Only user groups
      responses:
        '200':
          description: Successfully retrieved access list
          content:
            application/json:
              schema:
                type: object
                properties:
                  principals:
                    type: array
                    items:
                      $ref: '#/components/schemas/DocumentAccessPrincipal'
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
              example:
                principals:
                  - id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                    name: "Jane Smith"
                    email: "jane@example.com"
                    type: "user"
                    role: "EDITOR"
                    accessBoost: false
                    accessSource: "direct"
                    isOwner: false
                  - id: "b2c3d4e5-f6a7-8901-bcde-f23456789012"
                    name: "John Doe"
                    email: "john@example.com"
                    type: "user"
                    role: "VIEWER"
                    accessBoost: false
                    accessSource: "folder"
                    isOwner: false
                    folderInfo:
                      id: "c3d4e5f6-a7b8-9012-cdef-345678901234"
                      name: "Marketing Reports"
                      path: "/Shared/Marketing Reports"
                  - id: "d4e5f6a7-b8c9-0123-def0-456789012345"
                    name: "Data Analysts"
                    type: "userGroup"
                    role: "VIEWER"
                    accessBoost: false
                    accessSource: "direct"
                pageInfo:
                  hasNextPage: true
                  nextCursor: "eyJuYW1lIjoiSm9obiIsImlkIjoiMTIzIn0="
                  pageSize: 20
                  totalRecords: 47
        '400':
          description: |
            Bad Request. Possible causes:

            - Invalid `pageSize` value (must be 1-100)
            - Invalid `sortField` value
            - Invalid `sortDirection` value
            - Invalid `accessSource` value
            - Invalid `type` value
            - Invalid `cursor` value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalidPageSize:
                  summary: Invalid pageSize value
                  value:
                    detail: "pageSize: Must be between 1 and 100"
                    status: 400
                invalidSortField:
                  summary: Invalid sortField value
                  value:
                    detail: "sortField: Must be one of: name, email, role"
                    status: 400
        '403':
          description: |
            Forbidden. The user sending the API request must have **Manager** permissions for the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "User does not have permission to manage document permissions"
                status: 403
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Document with identifier \"<documentId>\" not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/favorite:
    put:
      tags:
        - Document favorites
      summary: Favorite document
      description: |
        Add a document to a user's favorites. Only published documents can be favorited.

        **Note**: Successful requests will return `204` regardless of whether the document is newly favorited or already in the user's favorites.
      security:
        - bearerAuth: []
      operationId: favoriteDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: The document identifier
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Membership ID of the user to favorite the document on behalf of. 

            Personal Access Tokens (PATs) cannot use this parameter to act on behalf of other users.
      responses:
        '204':
          description: |
            Document favorited successfully. No response body.

            This response is returned whether the document was newly favorited or already in favorites.
        '403':
          description: |
            Forbidden. Possible causes:

            - Personal Access Token attempted to act on behalf of another user. PATs cannot use the `userId` parameter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible causes:

            - Document does not exist
            - Document is not published
            - Specified `userId` not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Document favorites
      summary: Unfavorite document
      description: |
        Remove a document from a user's favorites. Only published documents can be unfavorited.

        **Note**: Successful requests will return `204` regardless of whether the document was previously favorited or not.
      security:
        - bearerAuth: []
      operationId: unfavoriteDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: The document identifier
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Membership ID of the user to unfavorite the document on behalf of.

            Personal Access Tokens (PATs) cannot use this parameter to act on behalf of other users.
      responses:
        '204':
          description: |
            Document unfavorited successfully. No response body.

            This response is returned whether the document was previously favorited or not.
        '400':
          description: Invalid HTTP method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden. Possible causes:

            - Personal Access Token attempted to act on behalf of another user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible causes:

            - Document does not exist
            - Document is not published
            - Specified `userId` not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/favorites:
    get:
      tags:
        - Document favorites
      summary: List document favoriters
      description: |
        <Note>
          This endpoint requires **Manager** or **Owner** permissions on the requested document.
        </Note>
        
        Lists users who have favorited a published document, paginated and sorted by `favoritedAt`.

        Use this endpoint when you need to know "who favorited document X" — for example, a content-migration script that preserves favorites when replacing a document needs to call this once per document, rather than iterating every user in the organization.
      security:
        - orgApiKey: []
      operationId: listDocumentFavoriters
      parameters:
        - name: identifier
          in: path
          required: true
          schema:
            type: string
          description: Document identifier (either document ID or slug).
        - name: cursor
          in: query
          required: false
          schema:
            type: string
            nullable: true
          description: Page cursor from a previous response's `nextCursor`. Omit for the first page.
        - name: pageSize
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of items per page (min 1, max 100).
        - name: sortDirection
          in: query
          required: false
          schema:
            type: string
            enum: [asc, desc]
            default: asc
          description: Sort direction by `favoritedAt`. `asc` returns oldest favorites first; `desc` returns newest first.
      responses:
        '200':
          description: Successfully retrieved list of users who favorited the document
          content:
            application/json:
              schema:
                type: object
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    items:
                      type: object
                      properties:
                        userId:
                          type: string
                          description: Membership ID of the favoriting user.
                        name:
                          type: string
                          description: User's display name.
                        email:
                          type: string
                          description: User's email address.
                        favoritedAt:
                          type: string
                          format: date-time
                          description: ISO 8601 timestamp recording when the user favorited the document.
                      required:
                        - userId
                        - name
                        - email
                        - favoritedAt
                required:
                  - pageInfo
                  - records
              examples:
                basicResponse:
                  summary: Basic request with 2 favoriters
                  value:
                    pageInfo:
                      hasNextPage: false
                      nextCursor: null
                      pageSize: 20
                      totalRecords: 2
                    records:
                      - userId: f1c2a3e4-1111-1111-1111-111111111111
                        name: Blob Ross
                        email: blob.ross@eblobsrus.com
                        favoritedAt: "2026-04-12T10:14:02.000Z"
                      - userId: f1c2a3e4-2222-2222-2222-222222222222
                        name: Blob the Builder
                        email: blob.the.builder@blobsrus.com
                        favoritedAt: "2026-05-01T17:33:21.000Z"
                emptyResponse:
                  summary: Document with no favoriters
                  value:
                    pageInfo:
                      hasNextPage: false
                      nextCursor: null
                      pageSize: 20
                      totalRecords: 0
                    records: []
        '400':
          description: |
            Bad Request. Invalid query parameters.

            Possible causes:
            - Unparseable cursor
            - Out-of-range `pageSize` (must be 1-100)
            - Invalid `sortDirection` (must be `asc` or `desc`)
            - Unknown query parameter
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                  status:
                    type: integer
              examples:
                invalidCursor:
                  summary: Invalid cursor
                  value:
                    detail: "Invalid cursor: not-a-number"
                    status: 400
                invalidPageSize:
                  summary: Page size out of range
                  value:
                    detail: "pageSize must be between 1 and 100"
                    status: 400
                unknownParam:
                  summary: Unknown query parameter
                  value:
                    detail: "Unrecognized key: random_bogus_param"
                    status: 400
        '401':
          description: Missing authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden. Possible causes:
            - Caller lacks `MANAGER` permission on the document
            - Used a user-scoped (personal access token) API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                  status:
                    type: integer
              examples:
                insufficientPermission:
                  summary: Insufficient permission on document
                  value:
                    detail: "You do not have permission to manage document access."
                    status: 403
                userScopedKey:
                  summary: User-scoped API key not allowed
                  value:
                    detail: "User-scoped API keys are not allowed to list document favoriters"
                    status: 403
        '404':
          description: Document not found (or unpublished)
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                  status:
                    type: integer
              examples:
                notFound:
                  summary: Document not found
                  value:
                    detail: 'Document with identifier "does-not-exist" not found'
                    status: 404
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/labels:
    patch:
      tags:
        - Document labels
      summary: Bulk update document labels
      description: Add and/or remove multiple labels from a document in a single atomic operation.
      x-mint:
        content: |
          When using this endpoint, keep in mind that:

          - **All changes succeed or fail together.** No partial updates occur.
          - **Label matching is case-insensitive**.
          - **Requests must have at least one operation.** Either `add` or `remove` must contain at least one label.
          - **Labels must already exist to be added to or removed from a document.** Create labels via the [Create label API](/api/labels/create-label).
          - **Labels cannot be included in both `add` and `remove` in the same request.**
          - **Organization Admin permissions are required to**:
             - Add or remove **Verified** labels
             - Add or remove **Homepage** labels
      security:
        - bearerAuth: []
      operationId: bulkUpdateDocumentLabels
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The document identifier (UUID)
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Optional user ID that attributes the action to the specified user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                add:
                  type: array
                  items:
                    type: string
                    minLength: 2
                    maxLength: 25
                  default: []
                  description: Label names to add to the document
                remove:
                  type: array
                  items:
                    type: string
                    minLength: 2
                    maxLength: 25
                  default: []
                  description: Label names to remove from the document
            examples:
              addLabels:
                summary: Add labels
                value:
                  add:
                    - production
                    - reviewed
              removeLabels:
                summary: Remove labels
                value:
                  remove:
                    - draft
                    - needs-review
              addAndRemove:
                summary: Add and remove labels
                value:
                  add:
                    - approved
                  remove:
                    - pending-review
      responses:
        '200':
          description: Labels updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  labels:
                    type: array
                    items:
                      type: string
                    description: The updated list of labels on the document
              example:
                labels:
                  - label-one
                  - label-two
                  - new-label
        '400':
          description: |
            Bad Request. Possible causes:

            - Empty request - Neither `add` nor `remove` contains any labels
            - Label appears in both `add` and `remove` arrays
            - Invalid label name (less than 2 or more than 25 characters)
            - Unrecognized field in the request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                emptyRequest:
                  summary: Empty request
                  value:
                    detail: "Bad Request: At least one label must be specified in add or remove"
                    status: 400
                    title: Bad Request
                conflictingLabels:
                  summary: Label in both arrays
                  value:
                    detail: "Bad Request: Labels cannot appear in both add and remove arrays"
                    status: 400
                    title: Bad Request
                unrecognizedField:
                  summary: Unrecognized field
                  value:
                    detail: "Bad Request: Unrecognized field in request body"
                    status: 400
                    title: Bad Request
        '403':
          description: |
            Forbidden. Possible causes:

            - User does not have `canLabel` permission on the document
            - User lacks Organization Admin permissions to modify Verified labels
            - User lacks Organization Admin permissions to modify Homepage labels
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                permissionDenied:
                  summary: Permission denied
                  value:
                    detail: You do not have permission to modify labels on this document.
                    status: 403
                    title: Forbidden
                verifiedLabelDenied:
                  summary: Verified label permission denied
                  value:
                    detail: You do not have permission to modify Verified labels on this document
                    status: 403
                    title: Forbidden
        '404':
          description: |
            Not Found. Possible causes:

            - Document does not exist
            - Label does not exist globally. Create the label first with the [Create label API](/api/labels/create-label).
            - Label in `remove` array does not exist on the document
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                documentNotFound:
                  summary: Document not found
                  value:
                    detail: Document with identifier "abc123" not found
                    status: 404
                    title: Not Found
                labelNotFound:
                  summary: Label not found globally
                  value:
                    detail: Label "my-label" not found
                    status: 404
                    title: Not Found
                labelNotOnDocument:
                  summary: Label not on document
                  value:
                    detail: |
                      Labels not found on this document: "label-one", "label-two"
                    status: 404
                    title: Not Found
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/labels/{labelName}:
    put:
      tags:
        - Document labels
      summary: Apply label to document
      description: |
        Apply an existing label to a document. Labels must be created first via the [Create label](/api/labels/create-label) endpoint.

        Documents can have multiple labels. When a new label is applied using this endpoint, the API adds it to the document's existing labels. Labels are not replaced.
      security:
        - bearerAuth: []
      operationId: applyLabelToDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: The document identifier
        - name: labelName
          in: path
          required: true
          schema:
            type: string
            minLength: 2
            maxLength: 25
          description: |
            The label name to apply:
            
            - Must be 2-25 characters
            - Labels are **case insensitive**. For example, adding `BlobSales` when `blobsales` exists will be treated as a duplicate. 
            - Special characters must be URL-encoded (e.g., `Q1%202024` for "Q1 2024").

            Additionally, adding **Verified** or **Homepage** labels require Organization Admin permissions.
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Optional user ID that attributes the action to the specified user.
      responses:
        '204':
          description: |
            Label applied successfully. No response body.

            This response is returned whether the label was newly applied or already existed on the document.
        '400':
          description: |
            Bad Request. Possible causes:

            - Label name too short (less than 2 characters)
            - Label name too long (more than 25 characters)
            - Invalid HTTP method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden. Possible causes:

            - User does not have Manager role on the specified document
            - User lacks Organization Admin permissions for Verified labels
            - User lacks Organization Admin permissions for Homepage labels
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible causes:

            - Document does not exist
            - Label does not exist. Must be created first via the [Create label](/api/labels/create-label).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Document labels
      summary: Remove label from document
      description: Remove a label from a document.
      x-mint: 
        content: |
          <Note>
            This endpoint is not idempotent. If the label does not exist on the document, the API returns a `404` error.
          </Note>
      security:
        - bearerAuth: []
      operationId: removeLabelFromDocument
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: The document identifier
        - name: labelName
          in: path
          required: true
          schema:
            type: string
            minLength: 2
            maxLength: 25
          description: |
            The label name to remove:

            - Must be 2-25 characters
            - Labels are **case insensitive**. For example, removing `BlobSales` will remove `blobsales` if it exists on the document.
            - Special characters must be URL-encoded (e.g., `Q1%202024` for "Q1 2024").
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Optional user ID that attributes the action to the specified user.
      responses:
        '204':
          description: Label removed successfully. No response body.
        '403':
          description: |
            Forbidden. User does not have permission to modify labels on this document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible causes:

            - Document does not exist
            - Label does not exist on this document
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: Label "production" not found on this document
                status: 404
                title: Not Found
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/documents/{documentId}/permissions:
    post:
      tags:
        - Document permissions
      summary: Grant document permissions
      description: Grant document permissions to users or groups.
      security:
        - bearerAuth: []
      operationId: grantDocumentPermissions
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The document identifier. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - role
              properties:
                role:
                  type: string
                  enum: [NO_ACCESS, VIEWER, EDITOR, MANAGER]
                  description: |
                    The content role to assign. Must be one of:
                    - `NO_ACCESS` - No access. Document won't appear in content system or search results.
                    - `VIEWER` - View dashboard
                    - `EDITOR` - Edit dashboard and workbook
                    - `MANAGER` - Edit dashboard and workbook and manage permissions
                accessBoost:
                  type: boolean
                  default: false
                  description: If `true`, AccessBoost will be enabled for the document.
                userIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: |
                    The list of user IDs to assign permissions to. Use the [List users](/api/users/list-users) and [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs. **Either `userIds` or `userGroupIds` is required.**
                userGroupIds:
                  type: array
                  items:
                    type: string
                  description: |
                    The list of user group IDs to assign permissions to. Use the [List user groups](/api/user-groups/list-user-groups) endpoint to retrieve user group IDs. **Either `userIds` or `userGroupIds` is required.**
      responses:
        '200':
          description: Permissions granted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request. Possible causes:

            - Missing `userIds` or `userGroupIds` parameter
            - Invalid `userId` value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingIds:
                  summary: Missing userIds or userGroupIds
                  value:
                    detail: "userIds.userGroupIds: userIds or userGroupIds must be provided"
                    status: 400
                invalidUuid:
                  summary: Invalid userId value
                  value:
                    detail: "userIds.0: Invalid uuid"
                    status: 400
        '403':
          description: |
            Forbidden. The user sending the API request must have **Manager** permissions for the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "User does not have permission to manage document permissions"
                status: 403
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Document with identifier \"<documentId>\" not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      tags:
        - Document permissions
      summary: Update document permissions
      description: Update existing document permissions for users or groups.
      security:
        - bearerAuth: []
      operationId: updateDocumentPermissions
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The document identifier. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - role
              properties:
                role:
                  type: string
                  enum: [NO_ACCESS, VIEWER, EDITOR, MANAGER]
                  description: |
                    The content role to assign. Must be one of:
                    - `NO_ACCESS` - No access. Document won't appear in content system or search results.
                    - `VIEWER` - View dashboard
                    - `EDITOR` - Edit dashboard and workbook
                    - `MANAGER` - Edit dashboard and workbook and manage permissions
                accessBoost:
                  type: boolean
                  default: false
                  description: If `true`, AccessBoost will be enabled for the document.
                userIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: |
                    The list of user IDs to update permissions for. Use the [List users](/api/users/list-users) and [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs. **Either `userIds` or `userGroupIds` is required.**
                userGroupIds:
                  type: array
                  items:
                    type: string
                  description: |
                    The list of user group IDs to update permissions for. Use the [List user groups](/api/user-groups/list-user-groups) endpoint to retrieve user group IDs. **Either `userIds` or `userGroupIds` is required.**
      responses:
        '200':
          description: Permissions updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request. Possible causes:

            - Missing `userIds` or `userGroupIds` parameter
            - Invalid `userId` value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingIds:
                  summary: Missing userIds or userGroupIds
                  value:
                    detail: "userIds.userGroupIds: userIds or userGroupIds must be provided"
                    status: 400
                invalidUuid:
                  summary: Invalid userId value
                  value:
                    detail: "userIds.0: Invalid uuid"
                    status: 400
        '403':
          description: |
            Forbidden. The user sending the API request must have **Manager** permissions for the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "User does not have permission to manage document permissions"
                status: 403
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Document with identifier \"<documentId>\" not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      tags:
        - Document permissions
      summary: Update document permission settings
      description: |
        Update the permission and [interactivity settings](/share#controlling-document-interactivity) for a document. For example, the ability to allow users to schedule or download the document's content.

        **Note**: Only the fields included in the request will be updated.
      security:
        - bearerAuth: []
      operationId: updateDocumentSettings
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The document identifier. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                organizationRole:
                  type: string
                  enum: [NO_ACCESS, VIEWER, EDITOR, MANAGER]
                  description: |
                    The default content role for the organization. Must be one of:
                    - `NO_ACCESS` - No access. Document won't appear in content system or search results.
                    - `VIEWER` - View dashboard
                    - `EDITOR` - Edit dashboard and workbook
                    - `MANAGER` - Edit dashboard and workbook and manage permissions
                canAnalyze:
                  type: boolean
                  description: If `true`, users with required permissions can explore from this document by creating new workbooks. In the UI, this is the **Analyze in new workbook** setting in the document's settings.
                canDownload:
                  type: boolean
                  description: If `true`, users with required permissions will be able to [download the document's query results](/analyze-explore/point-click-queries#downloading-results) or [dashboards](/visualize-present/dashboards/download). In the UI, this is the **Download** setting in the document's settings.
                canDrill:
                  type: boolean
                  description: If `true`, users with required permissions will be able to drill into data points in the document's content. In the UI, this is the **Drill** setting in the document's settings.
                canDuplicate:
                  type: boolean
                  description: If `true`, users with required permissions can duplicate the document. In the UI, this is the **Duplicate** setting in the document's Share settings.
                canRequestAccess:
                  type: boolean
                  description: If `true`, users can request access to the document when they don't have permissions to view it. In the UI, this is the **Request access** setting in the document's settings.
                canSaveSpreadsheets:
                  type: boolean
                  description: If `true`, users with required permissions can create spreadsheets from query results. In the UI, this is the **Save as spreadsheet** setting in the document's settings.
                canSchedule:
                  type: boolean
                  description: If `true`, users with required permissions will be able to create [deliveries (schedules and alerts)](/share/deliveries) on the document. In the UI, this is the **Schedule** setting in the document's settings.
                canUpload:
                  type: boolean
                  description: |
                    If `true`, users with required permissions can [upload data](/analyze-explore/data-input-csvs) (ex: CSVs) into the document to create data input tables. In the UI, this is the **Upload data** setting in the document's settings.
                canUseDashboardAi:
                  type: boolean
                  description: If `true`, users with required permissions can use AI features within the dashboard. In the UI, this is the **Dashboard AI** setting in the document's settings.
                canUseTimezoneOverride:
                  type: boolean
                  description: If `true`, users can override the document's timezone setting. In the UI, this is the **Timezone override** setting in the document's settings.
                canViewWorkbook:
                  type: boolean
                  description: If `true`, users with required permissions can view a read-only version of the workbook. In the UI, this is the **Viewers can see workbook** setting in the document's settings.
                requirePullRequestToPublish:
                  type: boolean
                  description: If `true`, publishing changes to this document requires a pull request through the git integration. In the UI, this is the **Require pull request to publish** setting in the document's settings.
            examples:
              updateMultipleAbilities:
                summary: Update multiple ability settings
                value:
                  canSaveSpreadsheets: false
                  canRequestAccess: false
                  canDownload: true
              updateOrganizationRole:
                summary: Update organization-wide access
                value:
                  organizationRole: VIEWER
      responses:
        '200':
          description: Settings updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request. Possible causes:

            - Invalid parameter value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "<parameter>: Invalid <parameter>"
                status: 400
        '403':
          description: |
            Forbidden. The user sending the API request must have **Manager** permissions for the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "User does not have permission to manage document permissions"
                status: 403
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Document with identifier \"<documentId>\" not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - Document permissions
      summary: Get document permissions
      description: |
        Retrieve the document-level ability values (the **Abilities** toggles in the document's settings), plus the resolved permits for a specific user when `userId` is provided.
      security:
        - bearerAuth: []
      operationId: getDocumentPermissions
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The document identifier. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
        - name: userId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: |
            User membership ID to check permissions for. When omitted, only the document-level abilities are returned. Use the [List users](/api/users/list-users) and [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs.
      responses:
        '200':
          description: Document abilities, plus user permits when `userId` is provided
          content:
            application/json:
              schema:
                type: object
                required:
                  - abilities
                properties:
                  abilities:
                    $ref: '#/components/schemas/DocumentAbilities'
                  permits:
                    type: array
                    description: User permits for the document. Present only when `userId` is provided.
                    items:
                      type: object
                      properties:
                        description:
                          type: string
                          description: Description of the permission type. For example, `Organization`
                        id:
                          type: string
                          description: ID of the user
                        name:
                          type: string
                          description: Name of the user
                        type:
                          type: string
                          description: The type of the permission holder (e.g., "user")
                        direct:
                          type: object
                          description: Direct permissions assigned to the user
                          properties:
                            accessBoost:
                              type: boolean
                              description: If `true`, AccessBoost is enabled for the user
                            isOwner:
                              type: boolean
                              description: If `true`, the user is the owner of the document
                            role:
                              type: string
                              description: The content role assigned to the user
                        folder:
                          type: object
                          description: Permissions inherited from a folder
                          properties:
                            accessBoost:
                              type: boolean
                              description: If `true`, AccessBoost is enabled via folder permissions
                            isOwner:
                              type: boolean
                              description: If `true`, the user is the owner via folder permissions
                            role:
                              type: string
                              description: The content role inherited from folder permissions
              examples:
                withAbilitiesOnly:
                  summary: Abilities only (no userId provided)
                  value:
                    abilities:
                      canAnalyze: true
                      canDownload: false
                      canDrill: false
                      canDuplicate: true
                      canRequestAccess: true
                      canSaveSpreadsheets: false
                      canSchedule: true
                      canUpload: false
                      canUseDashboardAi: false
                      canUseTimezoneOverride: false
                      canViewWorkbook: false
                      requirePullRequestToPublish: false
                withAbilitiesAndPermits:
                  summary: Abilities and permits (userId provided)
                  value:
                    abilities:
                      canAnalyze: true
                      canDownload: false
                      canDrill: false
                      canDuplicate: true
                      canRequestAccess: true
                      canSaveSpreadsheets: false
                      canSchedule: true
                      canUpload: false
                      canUseDashboardAi: false
                      canUseTimezoneOverride: false
                      canViewWorkbook: false
                      requirePullRequestToPublish: false
                    permits:
                      - description: "Organization"
                        direct:
                          accessBoost: false
                          isOwner: false
                          role: "VIEWER"
                        id: "df290ed4-b721-4efe-914b-95d30ce1c5f2"
                        name: "Organization"
                        type: "user"
        '400':
          description: |
            Bad Request. Possible causes:

            - Invalid `userId` value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "userId: Invalid userId"
                status: 400
        '403':
          description: |
            Forbidden. The user sending the API request must have **Manager** permissions for the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "User does not have permission to manage document permissions"
                status: 403
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Document with identifier \"<documentId>\" not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Document permissions
      summary: Revoke document permissions
      description: Revoke document permissions for users or user groups.
      security:
        - bearerAuth: []
      operationId: revokeDocumentPermissions
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: |
            The document identifier. To retrieve the ID, navigate to **File > Document settings** in the document and then click **Settings**. The **Identifier** field contains the document ID.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                userIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: |
                    The list of user IDs to revoke permissions from. Use the [List users](/api/users/list-users) and [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs. **Either `userIds` or `userGroupIds` is required.**
                userGroupIds:
                  type: array
                  items:
                    type: string
                  description: |
                    The list of user group IDs to revoke permissions from. Use the [List user groups](/api/user-groups/list-user-groups) endpoint to retrieve user group IDs. **Either `userIds` or `userGroupIds` is required.**
      responses:
        '200':
          description: Permissions revoked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request. Possible causes:

            - Missing `userIds` or `userGroupIds` parameter
            - Invalid `userId` value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingIds:
                  summary: Missing userIds or userGroupIds
                  value:
                    detail: "userId: userId must be provided"
                    status: 400
                invalidUserId:
                  summary: Invalid userId value
                  value:
                    detail: "userId: Invalid userId"
                    status: 400
        '403':
          description: |
            Forbidden. The user sending the API request must have **Manager** permissions for the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "User does not have permission to manage document permissions"
                status: 403
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Document with identifier \"<documentId>\" not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/folders:
    post:
      tags:
        - Folders
      summary: Create folder
      description: |
        Create a new folder. Folders can be nested up to 7 levels.

        Folder paths are automatically generated based on hierarchy. Child folder paths include the paths of their parents.
      security:
        - bearerAuth: []
      operationId: createFolder
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  description: Name of the folder.
                parentFolderId:
                  type: string
                  format: uuid
                  description: |
                    ID of the parent folder. Use the [List folders](/api/folders/list-folders) endpoint to retrieve folder IDs.

                    Maximum nesting depth is 7 levels. A `400` error is returned when the limit is reached.
                scope:
                  type: string
                  enum: [organization, restricted]
                  default: organization
                  description: |
                    Scope of the folder.

                    - If no scope is provided and no parent folder exists, defaults to `organization`
                    - If no scope is provided but a parent folder exists, inherits the parent folder's scope
                    - Child folder scope must match the parent folder's scope
                userId:
                  type: string
                  format: uuid
                  description: ID of the folder owner. Use the [List users](/api/users/list-users) or [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs.
                breadcrumbRoot:
                  type: boolean
                  description: |
                    When `true`, breadcrumbs for this folder and its descendants start at this folder, hiding ancestor folders and the scope crumb (`Shared`, `<name>'s Documents`).

                    This is useful for scoping navigation to a subtree, for example in embed use cases built around a specific `contentPath` folder.
      responses:
        '201':
          description: Folder created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: Unique identifier for the folder
                  name:
                    type: string
                    description: Display name of the folder
                  path:
                    type: string
                    description: Full path to the folder
                  scope:
                    type: string
                    enum: [organization, restricted]
                    description: |
                      Visibility scope of the folder
                  ownerId:
                    type: string
                    description: ID of the user that owns the folder
                  breadcrumbRoot:
                    type: boolean
                    description: Whether breadcrumbs start at this folder
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: Invalid JSON`
            - `Bad Request: name: Required`
            - `Bad Request: Maximum folder nesting depth reached`
            - `Bad Request: Child folder scope must match parent folder scope`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages/causes:

            - `User-scoped API keys cannot act on behalf of other users`
            - Operations targeting personal/restricted scope require the user's **Personal content access** setting (`omni_allows_personal_content` system attribute) to be enabled for the user. If not enabled, the API will respond with `403 Forbidden`.
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Parent folder with id <parentFolderId> does not exist`
            - `User with id <userId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - Folders
      summary: List folders
      description: |
        Retrieve a paginated list of folders within an organization. Supports filtering, sorting, and cursor-based pagination.

        The folders endpoint behaves differently based on the type of API key used to authenticate the request:

        - **Organization-scoped API keys:** Full access to all folders. Can view any user's restricted folders with `ownerId`. Must provide `ownerId` for `scope=restricted`.
        - **Personal Access Tokens (PAT):** Permission-filtered access matching the UI.
           - For `scope=organization` - Returns all organization-shared folders the user can access
           - For `scope=restricted` - Returns only the user's own restricted folders. An `ownerId` is optional; if not provided, it will be auto-inferred and the API will return only the folders belonging to PAT owner.
      security:
        - bearerAuth: []
      operationId: listFolders
      parameters:
        - name: include
          in: query
          schema:
            type: string
          description: |
            Comma-separated list of additional fields to include:

            - `_count` - Include document and favorite counts
            - `labels` - Include folder labels
            - `onlySharedWithMe` - Returns only folders explicitly shared with the specified user via folder permissions. Excludes folders owned by the user. Cannot be combined with `ownerId` or `path`.

               Additionally, depending on the type of API key being used:

               - **Organization API keys** require the `userId` parameter
               - **Personal Access Tokens** automatically infer the `userId` from the token
        - name: path
          in: query
          schema:
            type: string
          description: |
            Filter folders by path. Wildcards are supported and must appear at the end of the path:

            - `*` - Include direct children only (e.g., `blob-sales/*`)
            - `**` - Include all descendants recursively (e.g., `blob-sales/**`)
        - name: labels
          in: query
          schema:
            type: array
            items:
              type: string
          description: Comma-separated list of labels to filter by.
        - name: scope
          in: query
          schema:
            type: string
            enum: [organization, restricted]
            default: organization
          description: |
            Scope of folders to retrieve.

            - When `include=onlySharedWithMe` is specified without a `scope` parameter, returns shared folders of all scopes with each folder's actual scope preserved
            - Otherwise, defaults to `organization`

            When set to `restricted`, the `ownerId` parameter behavior depends on the API key type:

            - **Organization-scoped API keys:** `ownerId` is required
            - **Personal Access Tokens (PAT):** `ownerId` is optional and auto-inferred to the token owner
        - name: sortField
          in: query
          schema:
            type: string
            enum: [favorites, name, path]
            default: name
          description: |
            Field to sort by:

            - `favorites` - Sort by number of favorites
            - `name` - Sort by folder name
            - `path` - Sort by folder path
        - name: sortDirection
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: desc
          description: Sort direction.
        - name: cursor
          in: query
          schema:
            type: string
          description: Cursor for pagination positioning.
        - name: pageSize
          in: query
          schema:
            type: integer
            minimum: 1
            default: 20
          description: Number of items per page.
        - name: ownerId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            UUID of organization membership.

            How the API behaves depends on the type of API key used to authenticate the request and the current `scope` value.

            **Personal Access Tokens (PAT)**

            | Scope | ownerId | Behavior |
            |-------|---------|----------|
            | `organization` | Not provided | All organization-shared folders user has permission to access |
            | `organization` | own ID | Own organization folders only |
            | `restricted` | Not provided | Infers owner's ID, returns own restricted folders |
            | `restricted` | Own ID | Own restricted folders |
            | `restricted` | Other's ID | 403 Forbidden |

            **Organization API key**

            | Scope | ownerId | Behavior |
            |-------|---------|----------|
            | `organization` | Not provided | All organization folders |
            | `organization` | Any ID | That user's organization folders |
            | `restricted` | Not provided | 400 Bad Request (`ownerId` required) |
            | `restricted` | Any ID | That user's restricted folders |
        - name: userId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            The ID of a standard or embed user to filter results, returning only folders the specified user can view based on their permissions.

            If `include=onlyFavorites` is specified:

            - **And using an Organization API key**, this parameter is **required**
            - **And using a Personal Access Token**, the `userId` will be automatically inferred from the token
      responses:
        '200':
          description: Paginated folder list
          content:
            application/json:
              schema:
                type: object
                properties:
                  records:
                    type: array
                    description: Array of folder objects matching the query.
                    items:
                      $ref: '#/components/schemas/Folder'
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
              example:
                records:
                  - id: "21db26b3-466c-4791-90e7-b9ce9375426d"
                    name: "Blob Sales"
                    path: "/blob-sales"
                    scope: "organization"
                    url: "https://blobsrus.omni.co/f/blob-sales-reports"
                    owner:
                      id: "f4df8d6e-7f69-4d54-b23b-7abfe5c4da74"
                      name: "Blob Ross"
                    labels:
                      - "important"
                      - "archived"
                    _count:
                      documents: 15
                      favorites: 3
                pageInfo:
                  hasNextPage: true
                  nextCursor: "eyJpZCI6ImZvbGRlcjEyMyJ9"
                  pageSize: 20
                  totalRecords: 45
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: pageSize: Page size must be at least 1`
            - `Bad Request: ownerId: ownerId is required when scope is 'restricted'` (organization-scoped API keys only)
            - `Bad Request: sortField: Invalid enum value. Expected 'name' | 'path', received '<invalidField>'`
            - `Bad Request: include: Invalid value. Expected: _count, labels, onlySharedWithMe, received '<invalidValue>'`
            - `Bad Request: Invalid path pattern. Only a single wildcard (*) is allowed at the end of the pattern`
            - `Bad Request: onlySharedWithMe requires userId` - When using `include=onlySharedWithMe` with an Organization API key without providing a `userId` parameter
            - `Bad Request: onlySharedWithMe cannot be combined with ownerId` - When using `include=onlySharedWithMe` with an `ownerId` parameter
            - `Bad Request: onlySharedWithMe cannot be combined with path` - When using `include=onlySharedWithMe` with a `path` parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages/causes:
            
            - Personal Access Tokens (PAT) cannot access other users' restricted folders.
            - `User-scoped API keys cannot act on behalf of other users`
            - Operations targeting personal/restricted scope require the user's **Personal content access** setting (`omni_allows_personal_content` system attribute) to be enabled for the user. If not enabled, the API will respond with `403 Forbidden`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Folder with path <path> does not exist`
            - `User membership not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/folders/{folderId}:
    patch:
      tags:
        - Folders
      summary: Update folder
      description: |
        <Note>
          This endpoint requires [**Editor** permissions or higher](/share#content-access-permissions) on the folder being updated.
        </Note>

        Update a folder's name and/or path segment. Either or both fields can be updated in a single request.
      x-mint:
        content: |
          Name-only updates will not trigger updates to descendant folders (subfolders).

          However, path updates will automatically update all descendent folder paths. Consider the following folder structure:

          ```text title="Existing paths with /blob-shared"
          .
          └── blob-shared
              ├── sales      # full path: /blob-shared/sales
              └── marketing  # full path: /blob-shared/marketing
          ```

          If the path for `blob-shared` is updated to `all-blobs`, the full paths for its descendent folders will be automatically updated:

          ```text title="New paths with /all-blobs"
          .
          └── all-blobs
              ├── sales       # full path: /all-blobs/sales
              └── marketing   # full path: /all-blobs/marketing
          ```
      security:
        - bearerAuth: []
      operationId: updateFolder
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The UUID of the folder to update. Use the [List folders](/api/folders/list-folders) endpoint to retrieve folder IDs.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: |
                    The new display name for the folder. Leading and trailing whitespace is automatically trimmed.

                    At least one of `name` or `path` must be provided.
                path:
                  type: string
                  description: |
                    The new URL path segment for the folder, which will automatically be converted to lowercase. When a folder's path is updated, all descendant folder paths are automatically updated.

                    To be valid, path segments:

                    - Can contain only alphanumeric characters and dashes
                    - Cannot use reserved names: `favorite`, `labels`, `move`, `share`, `transfer`. Folder names may include these words, but the name cannot be only this word. For example, `favorite` is invalid but `my-favorite` is not.

                    At least one of `name` or `path` must be provided.
                resolvePathConflict:
                  type: boolean
                  default: false
                  description: |
                    When `true`, automatically resolves path conflicts by appending a numeric suffix (e.g., `blob-sales-2`).

                    When `false` (default), returns a `409 Conflict` error if the path already exists.
                breadcrumbRoot:
                  type: boolean
                  description: |
                    When `true`, breadcrumbs for this folder and its descendants start at this folder, hiding ancestor folders and the scope crumb (`Shared`, `<name>'s Documents`)

                    Omitting this field leaves the current setting unchanged. Setting it to `false` or `null` explicitly clears the breadcrumb root setting.
              example:
                name: "Blob Sales Reports"
                path: "sales-reports"
                resolvePathConflict: "true"
      responses:
        '200':
          description: Folder updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: Unique identifier for the folder
                  name:
                    type: string
                    description: Display name of the folder
                  path:
                    type: string
                    description: Full path to the folder
                  breadcrumbRoot:
                    type: boolean
                    description: Whether breadcrumbs start at this folder
              examples:
                success:
                  summary: Successful update
                  value:
                    id: "3c90c3cc-0d44-4b50-8888-8dd25736052a"
                    name: "Blob Sales Reports"
                    path: "sales-reports"
                    breadcrumbRoot: false
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `At least one of 'name' or 'path' must be provided`
            - `Path cannot be one of the reserved names: favorite, labels, move, share, transfer`
            - `Path must contain only alphanumeric characters and dashes`
            - Unrecognized field in the request body (e.g., a `labels` field)
            - Invalid JSON or validation errors
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingFields:
                  summary: No fields provided
                  value:
                    error: "400"
                    message: "At least one of 'name' or 'path' must be provided"
                reservedPath:
                  summary: Reserved path name
                  value:
                    error: "400"
                    message: "Path cannot be one of the reserved names: favorite, labels, move, share, transfer"
                invalidPathCharacters:
                  summary: Invalid path characters
                  value:
                    error: "400"
                    message: "Path must contain only alphanumeric characters and dashes"
        '403':
          description: |
            Forbidden

            Possible error messages:

            - Permission denied. Authenticating user must have **Edit** permissions on the folder.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Folder with id <folderId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: |
            Conflict

            Returned when `resolvePathConflict=false` (default) and the requested path already exists in the parent folder.

            Possible error messages:

            - `A folder with path '<path>' already exists at this location`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                pathConflict:
                  summary: Path already exists
                  value:
                    error: "409"
                    message: "A folder with path 'sales-reports' already exists at this location"
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Folders
      summary: Delete folder
      description: |
        Delete a folder. By default, only empty folders can be deleted. Use the `force` parameter to recursively delete folders containing documents and sub-folders.

        When `force` is enabled:

        - All documents are archived (soft-delete to trash) across the folder tree
        - Subfolders are deleted recursively, starting with the most deeply-nested subfolder and walking up the folder tree
        - Up to 100 items (documents and subfolders) can be deleted. If a folder contains more than 100 items, the item will return a `400`. No items will be deleted.
        - All operations occur in a single transaction - partial failures roll back completely
        - SSO embed users cannot force-delete entity folders
      security:
        - bearerAuth: []
      operationId: deleteFolder
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The UUID of the folder to delete. Use the [List folders](/api/folders/list-folders) endpoint to retrieve folder IDs.
        - name: force
          in: query
          required: false
          schema:
            type: boolean
            default: false
          description: When `true`, recursively archives documents (to trash) and deletes sub-folders. Limited to 100 total items.
      responses:
        '200':
          description: Folder deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Folders with documents cannot be deleted` (when `force=false` and folder is not empty)
            - `Only empty folders can be deleted` (when `force=false` and folder is not empty)
            - `Folder contains too many items to force delete. Found <count> items (limit: 100)` (when `force=true` and folder tree exceeds 100 items)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - Permission denied (when user lacks delete permission)
            - SSO embed users cannot force-delete entity folders (when SSO embed user attempts to force-delete entity folder)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Folder with id <folderId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/folders/{folderId}/labels:
    patch:
      tags:
        - Folder labels
      summary: Bulk update folder labels
      description: Add and/or remove multiple labels from a folder.
      x-mint:
        content: |
          When using this endpoint, keep in mind that:

          - **All changes succeed or fail together.** No partial updates occur.
          - **Label matching is case-insensitive**.
          - **Requests must have at least one operation.** Either `add` or `remove` must contain at least one label.
          - **Labels must already exist to be added to or removed from a folder.** Create labels with the [Create label API](/api/labels/create-label).
          - **Labels cannot be included in both `add` and `remove` in the same request.**
          - **Organization Admin permissions** are required to add or remove **Verified** or **Homepage** labels
      security:
        - bearerAuth: []
      operationId: bulkUpdateFolderLabels
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The folder identifier (UUID). Use the [List folders](/api/folders/list-folders) endpoint to retrieve folder IDs.
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Optional user ID that attributes the action to the specified user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                add:
                  type: array
                  items:
                    type: string
                    minLength: 2
                    maxLength: 25
                  default: []
                  description: Label names to add to the folder
                remove:
                  type: array
                  items:
                    type: string
                    minLength: 2
                    maxLength: 25
                  default: []
                  description: Label names to remove from the folder
              additionalProperties: false
            examples:
              addLabels:
                summary: Add labels
                value:
                  add:
                    - production
                    - reviewed
              removeLabels:
                summary: Remove labels
                value:
                  remove:
                    - draft
                    - needs-review
              addAndRemove:
                summary: Add and remove labels
                value:
                  add:
                    - approved
                  remove:
                    - pending-review
      responses:
        '200':
          description: Labels updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  labels:
                    type: array
                    items:
                      type: string
                    description: The updated list of labels on the folder
              example:
                labels:
                  - label-one
                  - label-two
                  - new-label
        '400':
          description: |
            Bad Request. Possible causes:

            - Empty request - Neither `add` nor `remove` contains any labels
            - Label appears in both `add` and `remove` arrays
            - Invalid label name (less than 2 or more than 25 characters)
            - Unrecognized field in the request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                emptyRequest:
                  summary: Empty request
                  value:
                    detail: "Bad Request: At least one label must be specified in add or remove"
                    status: 400
                    title: Bad Request
                conflictingLabels:
                  summary: Label in both arrays
                  value:
                    detail: "Bad Request: Labels cannot appear in both add and remove arrays"
                    status: 400
                    title: Bad Request
                unrecognizedField:
                  summary: Unrecognized field
                  value:
                    detail: "Bad Request: Unrecognized field in request body"
                    status: 400
                    title: Bad Request
        '403':
          description: |
            Forbidden. Possible causes:

            - User does not have permissions on the folder to add labels (Manager or Owner permissions on the folder)
            - User lacks Organization Admin permissions to modify **Verified** labels
            - User lacks Organization Admin permissions to modify **Homepage** labels
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                permissionDenied:
                  summary: Permission denied
                  value:
                    detail: You do not have permission to modify labels on this folder.
                    status: 403
                    title: Forbidden
                verifiedLabelDenied:
                  summary: Verified label permission denied
                  value:
                    detail: You do not have permission to modify Verified labels on this folder
                    status: 403
                    title: Forbidden
                homepageLabelDenied:
                  summary: Homepage label permission denied
                  value:
                    detail: Organization Admin permissions required to modify Homepage labels
                    status: 403
                    title: Forbidden
        '404':
          description: |
            Not Found. Possible causes:

            - Folder does not exist
            - Label does not exist. Create the label first with the [Create label API](/api/labels/create-label).
            - Label in `remove` array does not exist on the folder
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                folderNotFound:
                  summary: Folder not found
                  value:
                    detail: Folder with id "abc123" not found
                    status: 404
                    title: Not Found
                labelNotFound:
                  summary: Label not found globally
                  value:
                    detail: Label "my-label" not found
                    status: 404
                    title: Not Found
                labelNotOnFolder:
                  summary: Label not on folder
                  value:
                    detail: |
                      Labels not found on this folder: "label-one", "label-two"
                    status: 404
                    title: Not Found
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/folders/{folderId}/permissions:
    post:
      tags:
        - Folder permissions
      summary: Grant folder permissions
      description: Grant folder permissions to users or groups
      security:
        - bearerAuth: []
      operationId: grantFolderPermissions
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The UUID of the folder. Use the [List folders](/api/folders/list-folders) endpoint to retrieve folder IDs.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - role
              properties:
                role:
                  type: string
                  enum: [NO_ACCESS, VIEWER, EDITOR, MANAGER]
                  description: The content role to assign to the specified users or user groups.
                accessBoost:
                  type: boolean
                  default: false
                  description: If `true`, [AccessBoost](/share#boosting-permissions-with-accessboost) is enabled for the folder.
                userIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: |
                    The list of user IDs to grant permissions to. Use the [List users](/api/users/list-users) or [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs.

                    Either `userIds` or `userGroupIds` is required.
                userGroupIds:
                  type: array
                  items:
                    type: string
                  description: |
                    The list of user group IDs to grant permissions to. Use the [List user groups](/api/user-groups/list-user-groups) endpoint to retrieve user group IDs.

                    Either `userIds` or `userGroupIds` is required.
      responses:
        '200':
          description: Permissions granted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `userIds.userGroupIds: userIds or userGroupIds must be provided`
            - `userIds.0: Invalid uuid`
            - `userGroupIds.0: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `User does not have permission to manage folder permissions` - The user sending the API request must have **Manager** permissions for the folder.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Folder with identifier "<folderId>" not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      tags:
        - Folder permissions
      summary: Update folder permissions
      description: Update existing folder permissions for users or groups
      security:
        - bearerAuth: []
      operationId: updateFolderPermissions
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The UUID of the folder. Use the [List folders](/api/folders/list-folders) endpoint to retrieve folder IDs.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - role
              properties:
                role:
                  type: string
                  enum: [NO_ACCESS, VIEWER, EDITOR, MANAGER]
                  description: The content role to assign to the specified users or user groups.
                accessBoost:
                  type: boolean
                  default: false
                  description: If `true`, [AccessBoost](/share#boosting-permissions-with-accessboost) is enabled for the folder.
                userIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: |
                    The list of user IDs to update permissions for. Use the [List users](/api/users/list-users) or [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs.

                    Either `userIds` or `userGroupIds` is required.
                userGroupIds:
                  type: array
                  items:
                    type: string
                  description: |
                    The list of user group IDs to update permissions for. Use the [List user groups](/api/user-groups/list-user-groups) endpoint to retrieve user group IDs.

                    Either `userIds` or `userGroupIds` is required.
      responses:
        '200':
          description: Permissions updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `userIds.userGroupIds: userIds or userGroupIds must be provided`
            - `userIds.0: Invalid uuid`
            - `userGroupIds.0: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `User does not have permission to manage folder permissions` - The user sending the API request must have **Manager** permissions for the folder.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Folder with identifier "<folderId>" not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - Folder permissions
      summary: Get folder permissions
      description: Retrieve folder permissions for a user
      security:
        - bearerAuth: []
      operationId: getFolderPermissions
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The UUID of the folder. Use the [List folders](/api/folders/list-folders) endpoint to retrieve folder IDs.
        - name: userId
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the user to retrieve folder permissions for. Use the [List users](/api/users/list-users) or [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs.
      responses:
        '200':
          description: Folder permissions object
          content:
            application/json:
              schema:
                type: object
                properties:
                  permits:
                    type: array
                    items:
                      $ref: '#/components/schemas/FolderPermission'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `userId: userId must be provided`
            - `userId: Invalid userId`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `User does not have permission to manage folder permissions` - The user sending the API request must have **Manager** permissions for the folder.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Folder with identifier "<folderId>" not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Folder permissions
      summary: Revoke folder permissions
      description: Revoke folder permissions from users or groups
      security:
        - bearerAuth: []
      operationId: revokeFolderPermissions
      parameters:
        - name: folderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The UUID of the folder. Use the [List folders](/api/folders/list-folders) endpoint to retrieve folder IDs.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                userIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: |
                    The list of user IDs to revoke permissions from. Use the [List users](/api/users/list-users) or [List embed users](/api/users/list-embed-users) endpoints to retrieve user IDs.

                    Either `userIds` or `userGroupIds` is required.
                userGroupIds:
                  type: array
                  items:
                    type: string
                  description: |
                    The list of user group IDs to revoke permissions from. Use the [List user groups](/api/user-groups/list-user-groups) endpoint to retrieve user group IDs.

                    Either `userIds` or `userGroupIds` is required.
      responses:
        '200':
          description: Permissions revoked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `userIds.userGroupIds: userIds or userGroupIds must be provided`
            - `userIds.0: Invalid uuid`
            - `userGroupIds.0: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `User does not have permission to manage folder permissions` - The user sending the API request must have **Manager** permissions for the folder.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Folder with identifier "<folderId>" not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/jobs/{jobId}/status:
    get:
      tags:
        - Jobs
      summary: Get job status
      description: |
        <Note>
          Currently, this endpoint only supports schema refresh jobs. Job IDs from other job types will return an error.
        </Note>
        
        Retrieves the current status of an asynchronous job. The user authenticating the request must have **read** permissions on the connection.

        This endpoint is used to check the status of jobs initiated by other API calls, such as schema refreshes. Poll this endpoint to determine when the job has completed.
      x-mint:
        content: |
          The response will contain one of the following statuses:
  
          | Status      | Description             |
          |-------------|-------------------------|
          | `RUNNING`   | Job is currently executing |
          | `COMPLETED` | Job finished successfully |
          | `FAILED`    | Job failed              |
  
          <Tip>
            We recommend the following when polling:
  
            - Use a reasonable polling interval (2-5 seconds)
            - Avoid polling more frequently than once per second
          </Tip>
      security:
        - bearerAuth: []
      operationId: getJobStatus
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The job ID returned from an asynchronous operation such as [Refresh schema](/api/models/refresh-schema)
      responses:
        '200':
          description: Job status retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_type:
                    type: string
                    description: The type of job
                    example: "refresh_schema"
                  job_id:
                    type: string
                    format: uuid
                    description: The unique identifier of the job
                  status:
                    type: string
                    enum: [RUNNING, COMPLETED, FAILED]
                    description: Current status of the job
              examples:
                running:
                  summary: Job running
                  value:
                    job_type: "refresh_schema"
                    job_id: "4e6953a9-a71b-4c0b-8b63-a9ea308f6aaf"
                    status: "RUNNING"
                completed:
                  summary: Job completed
                  value:
                    job_type: "refresh_schema"
                    job_id: "4e6953a9-a71b-4c0b-8b63-a9ea308f6aaf"
                    status: "COMPLETED"
                failed:
                  summary: Job failed
                  value:
                    job_type: "refresh_schema"
                    job_id: "4e6953a9-a71b-4c0b-8b63-a9ea308f6aaf"
                    status: "FAILED"
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: jobId: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Job type not supported for status checks`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/labels:
    post:
      tags:
        - Labels
      summary: Create label
      description: |
        Create a new label in the organization.

        Any user can create basic labels, but **Organization Admin** permissions are required to:

        - Create **Verified** labels (`verified: true`)
        - Create **Homepage** labels (`homepage: true`)
      security:
        - bearerAuth: []
      operationId: createLabel
      parameters:
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Optional user ID that attributes the action to the specified user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  minLength: 2
                  maxLength: 25
                  description: |
                    Label name. Must be 2-25 characters.

                    Names are case-insensitive: `"Production"` and `"production"` are considered the same.
                color:
                  type: string
                  maxLength: 9
                  default: null
                  description: |
                    Hex color for the label
                  example: "#0366d6"
                description:
                  type: string
                  maxLength: 500
                  default: null
                  description: Description of the label
                  example: "Documents based on dev schemas"
                verified:
                  type: boolean
                  default: false
                  description: |
                    **Requires Organization Admin permissions**. If `true`, documents with this label will be marked as verified/curated.
                homepage:
                  type: boolean
                  default: false
                  description: |
                    **Requires Organization Admin permissions**. If `true`, documents with this label will display on the instance's Homepage.
            example:
              name: "Dev"
              color: "#0366d6"
              description: "Documents based on dev schemas"
      responses:
        '201':
          description: Label created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Label'
              example:
                name: "Dev"
                verified: true
                homepage: true
                usage_count: 0
                color: "#0366d6"
                description: "Documents based on dev schemas"
        '400':
          description: |
            Bad Request. Possible causes:

            - Label name too short (less than 2 characters)
            - Label name too long (more than 25 characters)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden. Possible causes:

            - User lacks **Organization Admin** permissions, which are required to create Verified and Homepage labels
            - User lacks the permissions required to manage labels
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: |
            Conflict. A label with this name already exists (case-insensitive).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - Labels
      summary: List labels
      description: Retrieve all labels in the organization.
      security:
        - bearerAuth: []
      operationId: listLabels
      responses:
        '200':
          description: List of all labels in the organization
          content:
            application/json:
              schema:
                type: object
                properties:
                  labels:
                    type: array
                    items:
                      $ref: '#/components/schemas/Label'
              example:
                labels:
                  - name: "Production"
                    verified: true
                    homepage: true
                    usage_count: 12
                    color: "#000000"
                    description: "Documents verified and in prod"
                  - name: "In Review"
                    verified: true
                    homepage: false
                    usage_count: 5
                    color: "#CCCCCC"
                    description: "Documents in review"
                  - name: "Draft"
                    verified: false
                    homepage: false
                    usage_count: 3
                    color: "#EEEEEE"
                    description: "Drafts that need review"
        '429':
          $ref: '#/components/responses/TooManyRequests'
  
  /v1/labels/{labelName}:
    get:
      tags:
        - Labels
      summary: Get label
      description: Retrieve a single label by name.
      security:
        - bearerAuth: []
      operationId: getLabel
      parameters:
        - name: labelName
          in: path
          required: true
          schema:
            type: string
          description: |
            The label name. Lookup is case-insensitive.
            
            URL-encode special characters (e.g., `In%20Review` for "In Review").
      responses:
        '200':
          description: Label details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Label'
              example:
                name: "Production"
                verified: true
                homepage: true
                usage_count: 12
                color: "#000000"
                description: "Documents verified and in prod"
        '404':
          description: Label not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      tags:
        - Labels
      summary: Update label
      description: |
        Update an existing label, including renaming, changing **Verified** status, or adding/removing it from the **Homepage**.

        Users can update basic labels they created. **Organization Admin** permissions are required to modify **Verified** or **Homepage** labels, including making a label `verified` or visible on the `homepage`.
      security:
        - bearerAuth: []
      operationId: updateLabel
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
          description: |
            The name of the label to update. Lookup is case-insensitive.
            
            URL-encode special characters.
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
           **Requires an Organization API key**. Optional user ID that attributes the action to the specified user.
      requestBody:
        description: |
          Only include the fields you want to update when submitting requests.
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  minLength: 2
                  maxLength: 25
                  description: The new name of the label.
                verified:
                  type: boolean
                  description: |
                    **Organization Admin permissions required**. If `true`, documents with the label will be marked as **Verified**.
                homepage:
                  type: boolean
                  description: |
                    **Organization Admin permissions required**. If `true`, documents with the label will be visible on the **Homepage**.
                color:
                  type: string
                  maxLength: 9
                  default: null
                  description: |
                    Hex color for the label
                  example: "#0366d6"
                description:
                  type: string
                  maxLength: 500
                  default: null
                  description: Description of the label
                  example: "Documents based on dev schemas"
            example:
              name: "Ready for Review"
              color: "#DDDDDD"
              description: "Documents that are ready for review"
      responses:
        '200':
          description: Label updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Label'
              example:
                name: "Ready for Review"
                verified: true
                homepage: false
                usage_count: 3
                color: "#DDDDDD"
                description: "Documents that are ready for review"
        '400':
          description: |
            Bad Request. Possible causes:

            - Name too short or too long
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden. Possible causes:

            - User lacks permission to manage labels
            - User lacks **Organization Admin** permissions, which are required to modify **Verified** labels
            - User lacks **Organization Admin** permissions, which are required to modify **Homepage** labels
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Label not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: |
            Conflict. The new label name already exists (case-insensitive).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-codeSamples:
        - lang: bash
          label: Rename label
          source: |
            curl -X PUT "https://{instance}.omniapp.co/api/v1/labels/Draft" \
              -H "Authorization: Bearer YOUR_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{"name": "Ready for Review"}'
        - lang: bash
          label: Set verified (admin)
          source: |
            curl -X PUT "https://{instance}.omniapp.co/api/v1/labels/Production" \
              -H "Authorization: Bearer YOUR_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{"verified": true, "homepage": true}'
    delete:
      tags:
        - Labels
      summary: Delete label
      x-mint:
        content: |
          <Warning>
            Labels that are currently applied to documents cannot be deleted. You must first remove the label from all documents.
          </Warning>
      description: |
        Delete a label from the organization.

        Any user can delete basic labels, but **Organization Admin** permissions are required to delete **Verified** and **Homepage** labels.
      security:
        - bearerAuth: []
      operationId: deleteLabel
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
          description: |
            The name of the label to delete. Lookup is case-insensitive.
            
            URL-encode special characters.
        - name: userId
          in: query
          required: false
          schema:
            type: string
          description: |
            **Requires an Organization API key**. Optional user ID that attributes the action to the specified user.
      responses:
        '204':
          description: Label deleted successfully. No response body.
        '403':
          description: |
            Forbidden. Possible causes:

            - User lacks permission to manage labels
            - User lacks **Organization Admin** permissions, which are required to delete **Verified** labels
            - User lacks **Organization Admin** permissions, which are required to delete **Homepage** labels
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Label not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: |
            Conflict. The label is currently applied to documents and cannot be deleted.

            Remove the label from all documents first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models:
    post:
      tags:
        - Models
      summary: Create model
      description: |
        Create a new model. The typical workflow for using this endpoint is:

        1. Create a schema model using this endpoint
        2. Use the [Refresh schema](/api/models/refresh-schema) endpoint to load the schema for the created model
        3. Create shared models based on the schema model
      security:
        - bearerAuth: []
      operationId: createModel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - connectionId
              properties:
                connectionId:
                  type: string
                  format: uuid
                  description: ID of the connection the model is based on.
                modelKind:
                  type: string
                  enum: [SCHEMA, SHARED, SHARED_EXTENSION, BRANCH]
                  default: SCHEMA
                  description: |
                    Type of model.

                    - `SCHEMA` - Mirrors the database structure
                    - `SHARED` - Represents the universal, governed data model that workbooks are based on
                    - `SHARED_EXTENSION` - Represents an extension to the `SHARED` model. Can be used to dynamically extend the `SHARED` model based on user attributes, or as a model that workbooks are based on in a departmental use case.
                    - `BRANCH` - A model layer that is used to develop modifications to the production shared model

                    For more information, see the [Modeling documentation](/modeling).
                modelName:
                  type: string
                  description: Name of the model.
                baseModelId:
                  type: string
                  description: |
                    **Applicable to branch and extension models.** ID of the base model.
                accessGrants:
                  type: array
                  default: []
                  description: List of [access grants](/modeling/develop/data-access-control) for the model.
                  items:
                    type: object
                    properties:
                      name:
                        type: string
                        description: Name of the access grant.
                      accessBoostable:
                        type: boolean
                        description: If `true`, the access grant can be boosted.
                allowAsWorkbookBase:
                  type: boolean
                  default: false
                  description: |
                    **Only applicable to `SHARED_EXTENSION` models**. Controls whether users can explore and query the extension model. If `true`, the model appears in the model selector when creating a new analysis, and users and AI can run queries against it. If `false` (the default), the extension model is hidden from the model selector and all exploration, query access, and AI chat access are disabled. Set to `true` for any extension model intended for end-user or AI interaction.
                useIsolatedBranches:
                  type: boolean
                  default: false
                  description: |
                    **Only applicable to `SHARED_EXTENSION` models**. If `true`, branches are shown on the extension model page instead of parent shared model.
      responses:
        '200':
          description: Model created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Error message if creation failed
                  message:
                    type: string
                    description: Additional message
                  model:
                    type: object
                    description: Details about the model
                    properties:
                      id:
                        type: string
                        format: uuid
                        description: ID of the created model.
                      modelKind:
                        type: string
                        description: The type of model.
                      name:
                        type: string
                        description: The name of the model.
                  success:
                    type: boolean
                    description: Indicates if the operation succeeded
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: Invalid parameter value`
            - `Bad Request: Schema model already exists for the connection`
            - `Bad Request: Schema model does not exist when creating a non-schema model`
            - `Bad Request: Shared model cannot be created when branch schema refresh is enabled`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Forbidden`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - Models
      summary: List models
      description: Retrieves a paginated list of models with their metadata.
      security:
        - bearerAuth: []
      operationId: listModels
      parameters:
        - name: baseModelId
          in: query
          required: false
          schema:
            type: string
          description: |
            Filter models by base model ID.
        - name: connectionId
          in: query
          required: false
          schema:
            type: string
          description: |
            Filter models by connection ID.
        - name: includeDeleted
          in: query
          required: false
          schema:
            type: boolean
          description: |
            If `true`, include deleted models.
        - name: modelId
          in: query
          required: false
          schema:
            type: string
          description: |
            Filter models by a specific model ID.
        - name: modelKind
          in: query
          required: false
          schema:
            type: string
            enum: [SCHEMA, SHARED, SHARED_EXTENSION, WORKBOOK, BRANCH, QUERY, TOPIC, FIELD_PICKER_TOPIC]
          description: |
            Filter by model kind.
        - name: name
          in: query
          required: false
          schema:
            type: string
          description: |
            Filter models by name.
        - name: pageSize
          in: query
          schema:
            type: integer
            default: 20
          description: Specifies the number of records per page.
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor
        - name: explorable
          in: query
          required: false
          schema:
            type: boolean
          description: |
            When `true`, returns only shared models (`modelKind: SHARED`) and shared extension models (`modelKind: SHARED_EXTENSION`) that can be used as a workbook base (`allowAsWorkbookBase: true`).
        - name: sortDirection
          in: query
          required: false
          schema:
            type: string
            default: desc
            enum: [asc, desc]
          description: |
            Sort direction.
        - name: sortField
          in: query
          required: false
          schema:
            type: string
            default: updatedAt
            enum: [name, modelKind, connectionId, baseModelId, createdAt, updatedAt]
          description: |
            Field to sort by. 
        - name: include
          in: query
          schema:
            type: string
            enum: [activeBranches]
          description: |
            Comma-separated list of additional fields to include in the response.

            - `activeBranches` - Include active branches for each model
      responses:
        '200':
          description: Paginated model list
          content:
            application/json:
              schema:
                type: object
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    description: List of model records.
                    items:
                      allOf:
                        - $ref: '#/components/schemas/Model'
                        - type: object
                          properties:
                            branches:
                              type: array
                              description: List of branch models. Only included when `include=activeBranches`.
                              items:
                                $ref: '#/components/schemas/Model'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}:
    patch:
      tags:
        - Models
      summary: Rename model
      description: |
        <Warning>
          Workbook models and query models cannot be renamed via this endpoint.
        </Warning>

        Rename a model. This endpoint allows you to programmatically rename models without using the UI, which is particularly useful for bulk operations on shared extension models created via `POST /api/v1/models`.

        The new name must be unique among active models with the same `modelKind` and `baseModelId`.

        **Branch-specific constraints:**

        - Name must match git-compatible regex: `[\w\-/]+`
        - No git ref path conflicts (e.g., cannot rename to `team` if `team/project` exists)
        - Cannot rename branches with an open pull request (when `pullRequestUrl` is set) — renaming would desync Omni from the git remote
      security:
        - bearerAuth: []
      operationId: renameModel
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: ID of the model to rename.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  description: The new name for the model.
                  example: "customer_metrics_v2"
      responses:
        '200':
          description: Model renamed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Model'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: Cannot rename models of kind WORKBOOK`
            - `Bad Request: Cannot rename models of kind QUERY`
            - `Bad Request: Model name must be unique`
            - `Bad Request: Branch name must match git-compatible regex [\w\-/]+`
            - `Bad Request: Branch name would create git ref path conflict`
            - `Bad Request: Cannot rename branch with open pull request`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/ai-agent-actions:
    get:
      tags:
        - Models
      summary: Get model AI agent actions
      description: |
        Return AI agent actions configured for a model — a unified list of sample queries and skills suitable for surfacing as suggested prompts in AI interfaces.

        Sample queries come from both `model.sample_queries` and each topic's `sample_queries`. Skills come from `model.skills` and each topic's `skills`, deduped by ID with topic skills overriding model skills.

        When no actions are configured at the model or topic level, `records` returns an empty array.
      security:
        - bearerAuth: []
      operationId: getModelAiAgentActions
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the model.
      responses:
        '200':
          description: AI agent actions retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Indicates the request was successful
                  records:
                    type: array
                    description: List of AI agent actions (sample queries and skills) configured for the model.
                    items:
                      type: object
                      required:
                        - type
                        - id
                        - label
                        - description
                        - prompt
                      properties:
                        type:
                          type: string
                          enum:
                            - sample_query
                            - skill
                          description: The type of action.
                        id:
                          type: string
                          description: Unique identifier for the action.
                        label:
                          type: string
                          description: Display label for the action.
                        description:
                          type: string
                          description: Description of what the action does.
                        prompt:
                          type: string
                          description: The prompt text to submit to the [Create AI job endpoint](/api/ai/create-ai-job). For skills, this is pre-formatted with the skill envelope. 
              example:
                success: true
                records:
                  - type: sample_query
                    id: revenue_by_region
                    label: Revenue by region
                    description: Show total revenue grouped by region
                    prompt: Show me revenue by region
                  - type: skill
                    id: analyze_trends
                    label: Analyze trends
                    description: Analyze trends in the data
                    prompt: "Skill: analyze_trends\nAnalyze trends in revenue over time"
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `modelId: Invalid UUID`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Permission denied`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/suggestions/generate:
    post:
      tags:
        - AI Model Suggestions
      summary: Generate model suggestions
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

        Trigger an AI suggestion generation run for the shared model and enqueue the async job. Poll the [Get status for a generation run endpoint](/api/ai-model-suggestions/get-status-for-a-generation-run) for status.

        A model can have one active run at a time and a cooldown period is enforced after a successful run. See the `Retry-After` header in the response for more information.
      security:
        - bearerAuth: []
      operationId: modelSuggestionsGenerate
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: UUID of the shared model the suggestions belong to
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
      responses:
        '202':
          description: Generation run enqueued
          content:
            application/json:
              schema:
                type: object
                required:
                  - runId
                  - status
                properties:
                  runId:
                    type: string
                    format: uuid
                    description: |
                      The ID of the created generation run. Poll the [Get status for a generation run endpoint](/api/ai-model-suggestions/get-status-for-a-generation-run) for status.
                  status:
                    type: string
                    enum:
                      - queued
                    description: The status of the run. Generation runs asynchronously.
              example:
                runId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                status: queued
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `modelId: Invalid UUID` - The model ID is not a valid UUID format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Authentication required
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Permission denied` - User is not an Organization Admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: Model not found in this organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '405':
          description: Method not allowed
        '409':
          description: A generation run is already active for this model
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError409'
              example:
                detail: "A generation run is already active for this model"
                status: 409
        '429':
          description: A run completed recently; retry after the cooldown (see the `Retry-After` header)
          headers:
            Retry-After:
              schema:
                type: integer
              description: Number of seconds to wait before retrying
          content:
            application/json:
              schema:
                type: object
                required:
                  - runId
                  - status
                properties:
                  runId:
                    type: string
                    format: uuid
                    description: |
                      The ID of the created generation run. Poll the [Get status for a generation run endpoint](/api/ai-model-suggestions/get-status-for-a-generation-run) for status.
                  status:
                    type: string
                    enum:
                      - queued
                    description: The status of the run. Generation runs asynchronously.
              example:
                detail: "A run completed recently. Please wait before triggering another run."
                lastCompletedAt: "2026-07-20T10:30:00.000Z"
                retryAfterSeconds: 300
                status: 429
        '500':
          description: Failed to enqueue the generation job

  /v1/models/{modelId}/suggestions/schedule:
    put:
      tags:
        - AI Model Suggestions
      summary: Enable model AI suggestions schedule
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

        Enable the daily generation schedule for AI model suggestions on a shared model. Suggestions are generated daily at midnight in the specified timezone.

        This operation is idempotent: calling it multiple times returns the persisted schedule.
      security:
        - bearerAuth: []
      operationId: modelSuggestionsScheduleEnable
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the model.
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                timezone:
                  type: string
                  description: |
                    IANA timezone string (e.g., `America/New_York`, `Europe/London`, `UTC`). Determines when the daily suggestion generation runs. Defaults to `UTC` if not provided.
              additionalProperties: false
            examples:
              withTimezone:
                summary: Enable with specific timezone
                value:
                  timezone: "America/New_York"
              defaultTimezone:
                summary: Enable with default UTC timezone
                value: {}
      responses:
        '200':
          description: Schedule enabled successfully. Returns the current state of the schedule, or the same state if already enabled.
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - sharedModelId
                  - status
                  - timezone
                properties:
                  id:
                    type: string
                    format: uuid
                    description: Unique identifier for the schedule.
                  sharedModelId:
                    type: string
                    format: uuid
                    description: The shared model the schedule generates suggestions for.
                  status:
                    type: string
                    enum:
                      - enabled
                  timezone:
                    type: string
                    description: IANA timezone the schedule runs in.
                    example: America/New_York
              example:
                id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                sharedModelId: f1e2d3c4-b5a6-7890-abcd-ef9876543210
                status: "enabled"
                timezone: "America/New_York"
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `modelId: Invalid UUID` - The model ID is not a valid UUID format
            - `timezone: Invalid timezone` - The provided timezone is not a valid IANA timezone format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Permission denied` - User is not an organization admin
            - `AI is not enabled for this organization` - Organization does not have AI features enabled
            - `Model must be shared` - The model is not a shared model
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - AI Model Suggestions
      summary: Disable model AI suggestions schedule
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

        Disable the daily generation schedule for AI model suggestions on a shared model.

        This operation is idempotent: calling it multiple times returns success even if the schedule is already disabled.
      security:
        - bearerAuth: []
      operationId: modelSuggestionsScheduleDisable
      parameters:
        - schema:
            type: string
            format: uuid
            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
          required: true
          description: UUID of the shared model the suggestions belong to
          name: modelId
          in: path
      responses:
        '200':
          description: Schedule disabled successfully. Returns confirmation even if the schedule was already disabled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                success: true
                message: "AI suggestions schedule disabled"
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `modelId: Invalid UUID` - The model ID is not a valid UUID format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Permission denied` - User is not an Organization Admin
            - `AI is not enabled for this organization` - Organization does not have AI features enabled
            - `Model must be shared` - The model is not a shared model
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '405':
          description: Method not allowed
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/branch/{branchName}:
    delete:
      tags:
        - Models
      summary: Delete branch
      description: Deletes a branch associated with the specified shared model.
      security:
        - bearerAuth: []
      operationId: deleteModelBranch
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: ID of the shared model.
        - name: branchName
          in: path
          required: true
          schema:
            type: string
          description: Name of the branch to delete.
      responses:
        '200':
          description: Branch deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: Invalid modelId format`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Shared model or branch model does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/refresh:
    post:
      tags:
        - Models
      summary: Refresh schema
      description: |
        <Note>
          This endpoint requires either **Connection Admin** or **Modeler** permissions:

          - **Modelers** can use this endpoint on connections that have exactly **one** shared model
          - **Connection Admins** can use this endpoint on any connection they are an admin of, whether the connection has one or multiple models
        </Note>

        Refresh the schema of the specified model. This will cause the model to reflect the latest changes to schemas, views, and fields from the data source. Schema refreshes will remove structures that are no longer present in the source, but not anything created by users.

        Depending on whether the **Branch-based schema refresh** setting is configured:

        - If **Branch-based schema refresh** is enabled, the `branch_id` query parameter is required. The `branch_id` is validated against the shared model.
        - If **Branch-based schema refresh** isn't enabled, do not provide the `branch_id` parameter. The API will return a `400` error in this case.
      security:
        - bearerAuth: []
      operationId: refreshSchema
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the model to be refreshed.
        - name: branch_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: |
            **Required if Branch-based schema refresh is enabled**. The ID of the branch for models with this setting enabled.

            Do not provide this parameter when the setting is not enabled.
        - name: hard_refresh
          in: query
          required: false
          schema:
            type: boolean
            default: true
          description: |
            Whether to perform a hard refresh (removes dropped objects) or soft refresh (additive only). Defaults to true (hard refresh).
        - name: schemas
          in: query
          required: false
          schema:
            type: string
          description: |
            Comma-separated list of schemas to selectively refresh. Can only be used when `hard_refresh=false`.
        - name: tables
          in: query
          required: false
          schema:
            type: string
          description: |
            Comma-separated list of tables to selectively refresh. Can only be used when `hard_refresh=false`.
      responses:
        '200':
          description: Model refresh started
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobId:
                    type: string
                    format: uuid
                    description: ID of the job.
                  modelId:
                    type: string
                    format: uuid
                    description: ID of the model.
                  status:
                    type: string
                    example: "running"
                    description: Status of the schema refresh. This value will be `running` to indicate that the refresh has started.
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: modelId: Invalid uuid`
            - `Bad Request: branch_id: Invalid uuid`
            - `Bad Request: branch_id is required when branch schema refresh is enabled`
            - `Bad Request: branch_id must not be provided when branch schema refresh is not enabled`
            - `Bad Request: selective schemas/tables filters require hard_refresh=false`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |
            Not Found

            Possible causes:

            - Model not found
            - Branch not found or does not belong to the model's shared model
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/cache_reset/{cachePolicyName}:
    post:
      tags:
        - Models
      summary: Reset cache
      description: |
        Reset the cache for the specified cache policy.

        <Note>
          `cachePolicyName` values are not validated against existing policies. Verify that the values you provide are exact matches to policies in the model.
        </Note>
      security:
        - bearerAuth: []
      operationId: resetModelCache
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the model associated with the cache policy.
        - name: cachePolicyName
          in: path
          required: true
          schema:
            type: string
          description: The name of the cache policy to reset.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                resetAt:
                  type: string
                  format: date-time
                  description: |
                    An ISO-8601 date string that sets the time the cache should be reset (invalidated). When set, cache entries created between this value and the current time will still be considered valid. Cannot be a future date. Defaults to now.
      responses:
        '200':
          description: Cache reset successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  cache_reset:
                    type: object
                    description: The cache reset details.
                    properties:
                      id:
                        type: string
                        format: uuid
                        description: ID of the cache reset record.
                      model_id:
                        type: string
                        format: uuid
                        description: ID of the model.
                      policy_name:
                        type: string
                        description: Name of the cache policy.
                      created_at:
                        type: string
                        format: date-time
                        description: Timestamp when the cache reset record was created.
                      updated_at:
                        type: string
                        format: date-time
                        description: Timestamp when the cache reset record was last updated.
                      reset_at:
                        type: string
                        format: date-time
                        description: Timestamp when the cache was reset.
                  success:
                    type: boolean
                    description: Indicates the request was successful.
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Model with id <modelId> does not exist`
            - `resetAt cannot be future dated`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/yaml:
    post:
      tags:
        - Models
      summary: Create or update YAML files
      description: |
        Create or overwrite a YAML file for a model. The file can be a special file (`model` or `relationships`) or a YAML file ending in `.topic`, `.view`, or `.composite_topic`.

        The following models cannot be edited using this endpoint:

        - Schema models
        - Models using git follower mode
      security:
        - bearerAuth: []
      operationId: updateModelYaml
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the model.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - fileName
                - yaml
                - mode
              properties:
                branchId:
                  type: string
                  format: uuid
                  description: |
                    **Required if git pull requests are required for the model.** The ID of the branch to create or update. To retrieve branch IDs, use the [List models](/api/models/list-models) endpoint with `modelKind=BRANCH`.
                fileName:
                  type: string
                  description: |
                    The name of the YAML file. Valid values are:

                    - `model` - [Model file](/modeling/models)
                    - `relationships` - [Relationships file](/modeling/relationships)
                    - `<topic_name>.topic` - [Topic file](/modeling/topics/parameters)
                    - `<composite_topic_name>.composite_topic` - [Composite topic file](/modeling/composite-topics/parameters)
                    - `<view_name>.view` - [View file](/modeling/views)
                yaml:
                  type: string
                  description: |
                    The YAML contents of the file. This can be empty, in which case:

                    - If empty and used with `mode: extension` the file will be removed from the model
                    - If empty and used with `mode: combined`, the file will be ignored in the model
                mode:
                  type: string
                  default: combined
                  enum: [combined, extension, staged, merged, history]
                  description: |
                    The mode to use when creating or overwriting the YAML file.

                    **Note**: Workbook models must use `combined` mode if there is a `branchId`.
                commitMessage:
                  type: string
                  description: |
                    **Required for [git-enabled models](/integrations/git).** Commit message describing the change.
                previousChecksum:
                  type: string
                  description: |
                    Checksum of the file when fetched, for conflict detection. Obtain this value from the [Get model YAML endpoint](/api/models/get-model-yaml) by including the `includeChecksums` parameter.

                    If the file has been modified since you fetched it, the request will fail with a `File has been modified since it was fetched` error.
                fullyResolved:
                  type: boolean
                  default: false
                  description: |
                    If `true`, accepts fully-resolved YAML. This means that Omni will resolve any extension (`extends`) usage and place the changes into the appropriate model files.

                    When `false` (default), the YAML is saved as-is to the specified file. This means that even if extensions are used, they will not be included in the changes.
      responses:
        '200':
          description: YAML file updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  fileName:
                    type: string
                    description: The name of the file that was created or updated.
                  success:
                    type: boolean
                    description: Indicates the request was successful.
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `<parameter>: <parameter> is required`
            - `modelId: Invalid uuid`
            - `branchId: Invalid uuid`
            - `<parameter>: Invalid value <description>`
            - `File has been modified since it was fetched`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Permission denied`
            - `Feature not enabled`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
            - `Branch does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - Models
      summary: Get model YAML
      description: Retrieve the YAML representation of a model with optional filtering and mode selection.
      security:
        - bearerAuth: []
      operationId: getModelYaml
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the model.
        - name: branchId
          in: query
          schema:
            type: string
            format: uuid
          description: The ID of the branch to retrieve YAML from. Only valid for shared models in `combined` mode. To retrieve branch IDs, use the [List models](/api/models/list-models) endpoint with `modelKind=BRANCH`.
        - name: fileName
          in: query
          schema:
            type: string
          description: Filter YAML files by name. Can be a string or a regex pattern.
        - name: mode
          in: query
          schema:
            type: string
            default: combined
            enum: [extension, staged, combined]
          description: |
            The mode to use when retrieving YAML.

            - `extension` - Returns extension-only format
            - `staged` - Returns staged changes
            - `combined` - Returns combined YAML
        - name: includeChecksums
          in: query
          schema:
            type: boolean
            default: false
          description: |
            If `true`, the response will include file checksums for concurrency control.

            A file's checksum can be passed as a query parameter in the [Create or update YAML endpoint](/api/models/create-or-update-yaml-files) to ensure changes made by another user aren't overwritten.
        - name: includeSchemas
          in: query
          schema:
            type: string
          description: |
            Filters the returned YAML to only include views from the specified schema. Relationships are preserved even if they reference views outside the specified schema.

            This parameter accepts a single schema name only. Use the [List model schemas endpoint](/api/models/list-model-schemas) to discover available schema names.
        - name: fullyResolved
          in: query
          schema:
            type: boolean
            default: false
          description: |
            If `true`, returns YAML with any extensions (`extends`) resolved. This will match what runs at query time.

            When `false` (default), returns the YAML as authored in the model files. This means even if extensions are used, they will not be included in the returned YAML.
      responses:
        '200':
          description: Model YAML specification
          content:
            application/json:
              schema:
                type: object
                properties:
                  files:
                    type: object
                    additionalProperties:
                      type: string
                    description: Map of file paths to YAML content.
                  version:
                    type: integer
                    description: Version of the YAML.
                  viewNames:
                    type: object
                    description: Map of view names to their definitions.
                  checksums:
                    type: object
                    description: Map of file paths to their checksums.
              example:
                files:
                  model.yaml: "name: Test Model\ntype: model"
                  views/customers.yaml: "name: Customers\nfields: []"
                  views/orders.yaml: "name: Orders\nfields: []"
                version: 1
                viewNames: {}
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Schema models do not have branches`
            - `Branches are not valid for workbook models with mode=extension, staged`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Permission denied`
            - `Feature not enabled`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
            - `Branch does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          description: |
            Internal Server Error

            Possible error messages:

            - `Failed to get model YAML`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      tags:
        - Models
      summary: Delete a YAML file
      description: |
        Delete a topic, composite topic, or view YAML file from a model. The file must be a YAML file ending in `.topic`, `.composite_topic`, or `.view`.

        The following models cannot be edited using this endpoint:

        - Schema models
        - Models using git follower mode
      security:
        - bearerAuth: []
      operationId: deleteModelYaml
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the model.
        - name: fileName
          in: query
          required: true
          schema:
            type: string
          description: |
            The name of the YAML file to delete. Valid values are:

            - `<topic_name>.topic` - [Topic file](/modeling/topics/parameters)
            - `<composite_topic_name>.composite_topic` - [Composite topic file](/modeling/composite-topics/parameters)
            - `<view_name>.view` - [View file](/modeling/views)
        - name: branchId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            **Required if git pull requests are required for the model.** The ID of the branch to delete from. To retrieve branch IDs, use the [List models](/api/models/list-models) endpoint with `modelKind=BRANCH`.
        - name: mode
          in: query
          schema:
            type: string
            default: combined
            enum: [combined, extension, staged, merged, history]
          description: |
            The mode to use when deleting the YAML file.

            **Note**: Workbook models must use `combined` mode if there is a `branchId`.
        - name: commitMessage
          in: query
          schema:
            type: string
          description: |
            **Required for [git-enabled models](/integrations/git).** Commit message describing the change.
      responses:
        '200':
          description: YAML file deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  fileName:
                    type: string
                    description: The name of the file that was deleted.
                  success:
                    type: boolean
                    description: Indicates the request was successful.
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `<parameter>: <parameter> is required`
            - `modelId: Invalid uuid`
            - `branchId: Invalid uuid`
            - `<parameter>: Invalid value <description>`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Permission denied`
            - `Feature not enabled`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
            - `Branch does not exist`
            - `File does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/schemas:
    get:
      tags:
        - Models
      summary: List model schemas
      description: Retrieves a sorted list of all available schema names for the specified model, including physical, virtual, and dynamic schemas.
      security:
        - bearerAuth: []
      operationId: getModelSchemas
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the model.
        - name: branchId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            The ID of the branch to retrieve schemas from.
      responses:
        '200':
          description: List of schema names
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
                description: Sorted array of schema names available in the model.
              example:
                - ANALYTICS
                - PUBLIC
                - STAGING
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Permission denied`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/dbt-exposures:
    get:
      operationId: getDbtExposures
      summary: Get dbt exposures
      description: |
        <Note>
          **Connection Admin** permissions are required to use this endpoint.
        </Note>

        Retrieve dbt exposures for the specified model. This endpoint computes exposures on-demand by analyzing which dbt models are referenced by dashboards that use the specified model.

        This enables automated retrieval of exposure data previously only available via the manual **Sync exposures** flow in Omni, making it ideal for CI/CD pipelines and automated workflows.
      tags:
        - dbt
      security:
        - bearerAuth: []
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: ID of the model
          example: "550e8400-e29b-41d4-a716-446655440000"
        - name: pageSize
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of results per page (1-100).
        - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: Pagination cursor from a previous response's `pageInfo.nextCursor`.
        - name: branch_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Branch ID for branch-aware model operations.
      responses:
        '200':
          description: dbt exposures successfully retrieved
          content:
            application/json:
              schema:
                type: object
                required:
                  - pageInfo
                  - records
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    items:
                      type: object
                      required:
                        - dashboard_identifier
                        - deduplication_name
                        - exposure
                      properties:
                        dashboard_identifier:
                          type: string
                          description: Unique identifier of the dashboard associated with this exposure.
                          example: "abc123"
                        deduplication_name:
                          type: string
                          description: Unique name used for deduplication, combining the exposure name and dashboard identifier.
                          example: "sales_overview_abc123"
                        exposure:
                          type: object
                          description: |
                            The generated exposure, with a structure matching the format defined in [dbt's exposures documentation](https://docs.getdbt.com/docs/build/exposures).
                            
                            This field will be `null` if the dashboard doesn't reference any dbt models.
                          required:
                            - name
                            - type
                            - owner
                            - depends_on
                            - label
                            - url
                          properties:
                            name:
                              type: string
                              description: Name of the exposure. Generated from dashboard title.
                              example: "sales_overview"
                            type:
                              type: string
                              description: Type of the exposure
                              example: "dashboard"
                            owner:
                              type: object
                              required:
                                - name
                                - email
                              properties:
                                name:
                                  type: string
                                  description: Owner's name
                                  example: "Blob Ross"
                                email:
                                  type: string
                                  description: Owner's email address
                                  example: "blob.ross@blobsrus.com"
                            depends_on:
                              type: array
                              items:
                                type: string
                              description: Array of dbt ref strings for models this exposure depends on
                              example: ["ref('orders')", "ref('customers')"]
                            label:
                              type: string
                              description: Human-readable label for the exposure
                              example: "Sales Overview"
                            url:
                              type: string
                              description: URL to the dashboard
                              example: "https://your-org.omni.co/dashboards/abc123"
              example:
                pageInfo:
                  hasNextPage: false
                  nextCursor: null
                  pageSize: 1000
                  totalRecords: 1
                records:
                  - dashboard_identifier: "abc123"
                    deduplication_name: "sales_overview_abc123"
                    exposure:
                      name: "sales_overview"
                      type: "dashboard"
                      owner:
                        name: "Blob Ross"
                        email: "blob.ross@blobsrus.com"
                      depends_on: ["ref('orders')", "ref('customers')"]
                      label: "Sales Overview"
                      url: "https://your-org.omni.co/dashboards/abc123"
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: Invalid modelId format`
            - `cursor: Invalid UUID`
            - `pageSize: Page size must be at least 1`
            - `pageSize: Page size cannot exceed 100`
            - `Bad Request: branch_id: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/branch/{branchName}/dbt:
    post:
      tags:
        - dbt
      summary: Set dbt environment on model branch
      description: |
        Sets the active dbt environment on a model branch. This endpoint allows you to programmatically configure which dbt environment a branch should use, enabling CI/CD pipelines to automate dbt environment configuration before triggering schema refreshes.
      security:
        - bearerAuth: []
      operationId: setModelBranchDbtEnvironment
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the model
          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        - name: branchName
          in: path
          required: true
          schema:
            type: string
          description: The name of the branch to configure
          example: "feature/new-models"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - dbt_environment_id
              properties:
                dbt_environment_id:
                  type: string
                  format: uuid
                  description: The ID of the dbt environment to set as active for the branch
                  example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                dbt_git_branch:
                  type: string
                  description: Optional git branch name to associate with the dbt environment
                  example: "feature/branch"
            example:
              dbt_environment_id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
              dbt_git_branch: "feature/branch"
      responses:
        '200':
          description: dbt environment configuration updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: Invalid modelId format`
            - `Bad Request: Invalid dbt_environment_id format`
            - `dbt_environment_id is required`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Shared model or branch model does not exist`
            - `Branch model with id <branch-id> does not exist`
            - `dbt environment not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/branch/{branchName}/merge:
    post:
      tags:
        - Model branches
      summary: Merge a branch
      description: |
        Merge a model branch into the shared model.
      x-mint:
        content: |
          For PR-required and [git follower](/integrations/git/follower-mode) models, direct merges via API are rejected by default as they would bypass the intended git workflow. You can use the `force_override_git_settings` parameter to override this check when necessary, but git will not be synced to avoid force-pushing to `main`.

          <Warning>
            The `force_override_git_settings` parameter requires **Connection Admin** or **Organization Admin** permissions. Users with lesser permissions will receive a `403 Forbidden` error when attempting to use this parameter.
          </Warning>

          | Model Configuration | Default Behavior | With `force_override_git_settings: true` |
          |---------------------|------------------|------------------------------------------|
          | No git | Merge succeeds, no git sync | N/A |
          | Git enabled (no PR required) | Merge succeeds, syncs to git | N/A |
          | Git + PR required | Rejected with 400 error | Merge succeeds, no git sync |
          | Git + git follower | Rejected with 400 error | Merge succeeds, no git sync |
      security:
        - bearerAuth: []
      operationId: mergeBranch
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the model
          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        - name: branchName
          in: path
          required: true
          schema:
            type: string
          description: The name of the branch to merge
          example: "feature/add-revenue-metrics"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                delete_branch:
                  type: boolean
                  default: false
                  description: Delete the branch after merging
                publish_drafts:
                  type: boolean
                  default: true
                  description: When enabled, publish branch-attached drafts when merging
                commit_message:
                  type: string
                  description: Custom commit message for git sync. Defaults to `"branch <name> merged via API"`
                  example: "Merged revenue metrics branch via CI/CD pipeline"
                force_override_git_settings:
                  type: boolean
                  default: false
                  description: |
                    **Requires Connection Admin or Organization Admin permissions.** Users with lesser permissions will receive a `403 Forbidden` error when attempting to use this parameter.
                    
                    Allow merge for PR-required or git-follower models. When enabled, the merge will succeed but git will not be synced to avoid force-pushing to main. 
            example:
              delete_branch: true
              publish_drafts: true
              commit_message: "Merged revenue metrics branch via CI/CD pipeline"
              force_override_git_settings: false
      responses:
        '200':
          description: Branch merged successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Whether the merge was successful
                    example: true
                  published_drafts_count:
                    type: integer
                    description: Number of drafts that were published during the merge
                    example: 2
                  failed_drafts_count:
                    type: integer
                    description: Number of drafts that failed to publish
                    example: 0
                  git_synced:
                    type: boolean
                    description: Whether the changes were synced to git
                    example: true
              example:
                success: true
                published_drafts_count: 2
                failed_drafts_count: 0
                git_synced: true
        '400':
          description: Bad Request - Merge not allowed for this model configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                prRequired:
                  summary: PR-required model without override
                  value:
                    error: "Cannot merge branch directly. This model requires pull requests for changes. Use force_override_git_settings: true to bypass."
                gitFollower:
                  summary: Git-follower model without override
                  value:
                    error: "Cannot merge branch directly. This model follows git as source of truth. Use force_override_git_settings: true to bypass."
        '403':
          description: Forbidden - Insufficient permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                insufficientPermissions:
                  summary: Modeler attempting to use force_override_git_settings
                  value:
                    error: "Insufficient permissions. The force_override_git_settings flag requires Connection Admin or higher permissions."
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          description: Invalid HTTP method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/models/{modelId}/validate:
    get:
      tags:
        - Models
      summary: Validate model
      description: Retrieve validation issues for a model and its branches.
      security:
        - bearerAuth: []
      operationId: validateModel
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the model.
        - name: branchId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            The ID of the branch to validate. The branch must exist for the model. If not provided, the model is validated directly. To retrieve branch IDs, use the [List models](/api/models/list-models) endpoint with `modelKind=BRANCH`.

            **Note**: Schema models do not support branches.
      responses:
        '200':
          description: A list of validation issues.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    message:
                      type: string
                      description: The validation issue message.
                    is_warning:
                      type: boolean
                      description: If `true`, the issue is a warning. If `false`, the issue is an error.
                    yaml_path:
                      type: string
                      description: The path to the YAML file containing the issue.
                    auto_fix:
                      type: object
                      description: Information about automatic fixes available for the issue.
                      properties:
                        description_short:
                          type: string
                          description: A short description of the auto-fix.
                        description_unique:
                          type: string
                          description: A unique description of the auto-fix.
              example:
                - message: "No view \"blob_sales\". Set base_view to a valid, existing view."
                  is_warning: false
                  yaml_path: "blob_sales.topic"
                  auto_fix:
                    description_short: "Delete topic \"blob_sales\""
                    description_unique: "Delete topic \"blob_sales\""
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `modelId: Invalid uuid`
            - `Schema models do not have branches`
            - `Unrecognized key(s) in object: '<invalidParameter>'`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model with id <modelId> does not exist`
            - `Branch model with id <branchId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          description: |
            Internal Server Error

            Possible error messages:

            - `Model service error`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v1/models/{modelId}/git:
    get:
      tags:
        - Model Git configuration
      summary: Get Git configuration
      description: |
        Retrieve the Git configuration for a shared model.
      security:
        - bearerAuth: []
      operationId: getModelGitConfig
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the shared model.
        - name: include
          in: query
          required: false
          schema:
            type: string
            enum: [webhookSecret]
          description: |
            Comma-separated list of additional fields to include in the response.

            - `webhookSecret` - Include the webhook secret in the response
      responses:
        '200':
          description: Git configuration retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelGitConfig'
              example:
                authMethod: "ssh"
                baseBranch: "main"
                branchPerPullRequest: false
                gitFollower: false
                gitServiceProvider: "github"
                modelPath: "omni/blobs_r_us"
                publicKey: "ssh-ed25519 AAAA..."
                requirePullRequest: "users-only"
                cloneUrl: "git@github.com:org/repo.git"
                webUrl: "https://github.com/org/repo"
                webhookUrl: "https://app.omni.co/api/webhooks/model/..."
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: modelId: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Forbidden: Requires MANAGE_MODEL permission`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Not Found: Model does not exist`
            - `Not Found: Git configuration not found for this model`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    post:
      tags:
        - Model Git configuration
      summary: Create Git configuration
      description: |
        Create a new Git configuration for a shared model.
      security:
        - bearerAuth: []
      operationId: createModelGitConfig
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the shared model.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - cloneUrl
              properties:
                authMethod:
                  type: string
                  enum: [ssh, https_token]
                  description: |
                    The authentication method to use:

                    - `ssh` for deploy key
                    - `https_token` for deploy token/PAT
                  example: "ssh"
                cloneUrl:
                  type: string
                  description: |
                    Clone URL of the git repository. Varies depending on the `authMethod`:

                    - `ssh` - A string starting with `git@...`
                    - `https_token` - A string starting with `https://...`
                  example: "git@github.com:org/repo.git"
                sshUrl:
                  type: string
                  deprecated: true
                  description: |
                    **Deprecated. Use `cloneUrl` instead**. The SSH URL of the git repository.
                  example: "git@github.com:org/repo.git"
                token:
                  type: string
                  maxLength: 1000
                  pattern: ^[a-zA-Z0-9_\\-.]+$
                  description: |
                    **Required when `authMethod: https_token`**. HTTPS token for authentication (deploy token value, PAT, etc.).
                baseBranch:
                  type: string
                  default: "main"
                  description: The target branch for Omni pull requests.
                  example: "main"
                branchPerPullRequest:
                  type: boolean
                  default: false
                  description: If `true`, all pull requests will create a branch in Omni, even those created outside of the tool.
                  example: false
                gitFollower:
                  type: boolean
                  default: false
                  description: If `true`, the shared model is read-only and can only be updated by merging pull requests to the `baseBranch`.
                  example: false
                gitServiceProvider:
                  type: string
                  enum: [auto, github, gitlab, azure_devops, bitbucket, bitbucket_datacenter]
                  default: "auto"
                  description: |
                    The git provider type. Use `auto` for automatic detection based on the SSH URL.

                    - `auto` - Automatically detect the provider
                    - `github` - GitHub
                    - `gitlab` - GitLab
                    - `azure_devops` - Azure DevOps
                    - `bitbucket` - Bitbucket Cloud
                    - `bitbucket_datacenter` - Bitbucket Data Center (self-hosted)
                  example: "github"
                modelPath:
                  type: string
                  description: Path to model files within the repository.
                  example: "omni/blobs_r_us"
                requirePullRequest:
                  type: string
                  enum: [always, users-only, never]
                  default: "never"
                  description: |
                    Controls when pull requests are required for changes:

                    - `always` - Required for all changes
                    - `users-only` - Required only for user-initiated changes
                    - `never` - Never required
                  example: "users-only"
                webUrl:
                  type: string
                  description: Custom web URL for the git repository. Use when the `cloneUrl` goes through a tunnel/VPC and differs from the inferred HTTPS address.
                  example: "https://github.com/org/repo"
                deployPrivateKey:
                  type: string
                  description: |
                    **Applicable to SSH authentication**. Supply your own RSA or ED25519 private key in PEM format (OpenSSH, PKCS#1, or PKCS#8) instead of using an Omni-generated keypair. Authorize the matching public key with your first, then set it here. See [Rotate Git SSH keys with the API](/guides/api/rotate-ssh-deploy-keys) for step-by-step instructions.
                  example: "-----BEGIN OPENSSH PRIVATE KEY-----\n..."
                deployKeyPassphrase:
                  type: string
                  description: |
                    **Applicable to SSH authentication.** Passphrase used to decrypt an encrypted `deployPrivateKey`. Omni uses it once to decrypt the key, then stores the key under its own encryption at rest. The passphrase itself is not retained. See [Rotate Git SSH keys with the API](/guides/api/rotate-ssh-deploy-keys) for step-by-step instructions.
                  example: "my-secure-passphrase"
            example:
              authMethod: "ssh"
              cloneUrl: "git@github.com:org/repo.git"
              baseBranch: "main"
              gitServiceProvider: "github"
              modelPath: "omni/blobs_r_us"
              requirePullRequest: "users-only"
      responses:
        '200':
          description: Git configuration created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelGitConfig'
              example:
                authMethod: "ssh"
                baseBranch: "main"
                branchPerPullRequest: false
                gitFollower: false
                gitServiceProvider: "github"
                modelPath: "omni/my_model"
                publicKey: "ssh-ed25519 AAAA..."
                requirePullRequest: "users-only"
                cloneUrl: "git@github.com:org/repo.git"
                webUrl: "https://github.com/org/repo"
                webhookSecret: "whsec_abc123xyz789"
                webhookUrl: "https://app.omni.co/api/webhooks/model/..."
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: modelId: Invalid uuid`
            - `Bad Request: sshUrl is required`
            - `Bad Request: Invalid sshUrl format`
            - `Bad Request: Invalid requirePullRequest value`
            - `Bad Request: deployPrivateKey: Invalid key format`
            - `Bad Request: deployPrivateKey: Wrong passphrase`
            - `Bad Request: deployPrivateKey: Encrypted key requires deployKeyPassphrase`
            - `Bad Request: deployKeyPassphrase cannot be provided without deployPrivateKey`
            - `Bad Request: deployPrivateKey not supported with authMethod: https_token`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Forbidden: Requires MANAGE_MODEL permission`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Not Found: Model does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: |
            Conflict

            Possible error messages:

            - `Conflict: Git configuration already exists for this model`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      tags:
        - Model Git configuration
      summary: Update Git configuration
      description: Update the Git configuration for a shared model. Oly provided fields will be updated.
      security:
        - bearerAuth: []
      operationId: updateModelGitConfig
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the shared model.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                authMethod:
                  type: string
                  enum: [ssh, https_token]
                  description: |
                    The authentication method to change to:

                    - `ssh` for deploy key
                    - `https_token` for deploy token/PAT
                token:
                  type: string
                  maxLength: 1000
                  pattern: ^[a-zA-Z0-9_\\-.]+$
                  description: |
                    **Required when `authMethod: https_token`**. HTTPS token for authentication (deploy token value, PAT, etc.).
                sshUrl:
                  type: string
                  deprecated: true
                  description: |
                    **Deprecated. Use `cloneUrl` instead.** The SSH URL of the git repository.
                  example: "git@github.com:org/repo.git"
                baseBranch:
                  type: string
                  description: The target branch for Omni pull requests.
                  example: "main"
                branchPerPullRequest:
                  type: boolean
                  description: If `true`, all pull requests will create a branch in Omni, even those created outside of the tool.
                  example: false
                gitFollower:
                  type: boolean
                  description: If `true`, the shared model is read-only and can only be updated by merging pull requests to the `baseBranch`.
                  example: false
                gitServiceProvider:
                  type: string
                  enum: [auto, github, gitlab, azure_devops, bitbucket, bitbucket_datacenter]
                  description: |
                    The git provider type. Use `auto` for automatic detection based on the SSH URL.

                    - `auto` - Automatically detect the provider
                    - `github` - GitHub
                    - `gitlab` - GitLab
                    - `azure_devops` - Azure DevOps
                    - `bitbucket` - Bitbucket Cloud
                    - `bitbucket_datacenter` - Bitbucket Data Center (self-hosted)
                  example: "github"
                modelPath:
                  type: string
                  description: Path to model files within the repository.
                  example: "omni/blobs_r_us"
                requirePullRequest:
                  type: string
                  enum: [always, users-only, never]
                  description: |
                    Controls when pull requests are required for changes:

                    - `always` - Pull requests are required for all changes
                    - `users-only` - Pull requests are required only for user-initiated changes
                    - `never` - Pull requests are never required
                  example: "users-only"
                webUrl:
                  type: string
                  description: Custom web URL for the git repository. Use when the `cloneUrl` goes through a tunnel/VPC and differs from the inferred HTTPS address.
                  example: "https://github.com/org/repo"
                deployPrivateKey:
                  type: string
                  description: |
                    **Applicable to SSH authentication**. Supply your own RSA or ED25519 private key in PEM format (OpenSSH, PKCS#1, or PKCS#8) instead of using an Omni-generated keypair. Authorize the matching public key with your first, then set it here. The new key takes effect on the next git operation.
                  example: "-----BEGIN OPENSSH PRIVATE KEY-----\n..."
                deployKeyPassphrase:
                  type: string
                  description: |
                    **Applicable to SSH authentication.** Passphrase used to decrypt an encrypted `deployPrivateKey`. Omni uses it once to decrypt the key, then stores the key under its own encryption at rest. The passphrase itself is not retained.
                  example: "my-secure-passphrase"
            example:
              baseBranch: "develop"
              requirePullRequest: "always"
      responses:
        '200':
          description: Git configuration updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelGitConfig'
              example:
                authMethod: "ssh"
                baseBranch: "develop"
                branchPerPullRequest: false
                gitFollower: false
                gitServiceProvider: "github"
                modelPath: "omni/my_model"
                publicKey: "ssh-ed25519 AAAA..."
                requirePullRequest: "always"
                cloneUrl: "git@github.com:org/repo.git"
                webUrl: "https://github.com/org/repo"
                webhookUrl: "https://app.omni.co/api/webhooks/model/..."
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: modelId: Invalid uuid`
            - `Bad Request: Invalid cloneUrl format`
            - `Bad Request: Invalid requirePullRequest value`
            - `Bad Request: deployPrivateKey: Invalid key format`
            - `Bad Request: deployPrivateKey: Wrong passphrase`
            - `Bad Request: deployPrivateKey: Encrypted key requires deployKeyPassphrase`
            - `Bad Request: deployKeyPassphrase cannot be provided without deployPrivateKey`
            - `Bad Request: deployPrivateKey not supported with authMethod: https_token`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Forbidden: Requires MANAGE_MODEL permission`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Not Found: Model does not exist`
            - `Not Found: Git configuration not found for this model`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Model Git configuration
      summary: Delete Git configuration
      description: Remove the Git configuration from a shared model.
      security:
        - bearerAuth: []
      operationId: deleteModelGitConfig
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the shared model.
      responses:
        '200':
          description: Git configuration deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: modelId: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Forbidden: Requires MANAGE_MODEL permission`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Not Found: Model does not exist`
            - `Not Found: Git configuration not found for this model`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/git/sync:
    post:
      tags:
        - Model Git configuration
      summary: Sync model with Git repository
      description: |
        Trigger a sync operation between the model and its configured Git repository.

        This will pull the latest changes from the repository and apply them to the model.
      security:
        - bearerAuth: []
      operationId: syncModelWithGit
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the shared model.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                commitMessage:
                  type: string
                  description: Optional commit message to use when pushing changes to the repository.
                  example: "Updated model configuration"
            example:
              commitMessage: "Updated model configuration"
      responses:
        '200':
          description: Sync operation completed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  didSync:
                    type: boolean
                    description: Whether a sync was performed. `false` if the model was already in sync.
                    example: true
                  gitSha:
                    type: string
                    nullable: true
                    description: The git SHA after the sync operation, or null if no sync was performed.
                    example: "abc123def456"
                  inSync:
                    type: boolean
                    description: Whether the model is currently in sync with the git repository.
                    example: true
                  message:
                    type: string
                    description: Human-readable status message describing the sync result.
                    example: "Model synced successfully"
              example:
                didSync: true
                gitSha: "abc123def456"
                inSync: true
                message: "Model synced successfully"
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: modelId: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Forbidden: Requires MANAGE_MODEL permission`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Not Found: Model does not exist`
            - `Not Found: Git configuration not found for this model`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/git/commit:
    post:
      tags:
        - Model Git configuration
      summary: Create or update a pull request for a model branch
      description: |
        <Note>
          This endpoint requires **Modeler** or **Connection Admin** permisisons.
        </Note>

        Push a branch's model contents to Git and create or update a pull request. This mirrors the **Create Pull Request** / **Update Pull Request** buttons in Omni allowing API consumers and AI agents to drive the pull request workflow programmatically.

        By default, the endpoint automatically detects whether the Git branch exists. If a branch doesn't exist, a successful request will create a new Git branch and open a pull request. If a branch does exist, a commit will be added to the branch.

        The `allow_branch_exists` and `require_branch_exists` parameters constrain this behavior:

        - **Create or update mode** (default) - Auto-detects and handles both scenarios
        - **Create-only mode** (`allow_branch_exists: false`) - Fails if the Git branch already exists
        - **Update-only mode** (`require_branch_exists: true`) - Fails if the Git branch doesn't exist
      security:
        - bearerAuth: []
      operationId: commitModelBranch
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the shared model.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - branch_id
                - commit_message
              properties:
                branch_id:
                  type: string
                  format: uuid
                  description: UUID of the Omni branch to commit.
                  example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                commit_message:
                  type: string
                  description: Commit message for the git commit.
                  example: "Add new customer dimension"
                allow_branch_exists:
                  type: boolean
                  default: true
                  description: |
                    Set `false` to fail if git branch already exists (**Create-only mode**).
                  example: true
                require_branch_exists:
                  type: boolean
                  default: false
                  description: |
                    Set `true` to fail if git branch doesn't exist (**Update-only mode**).
                  example: false
            example:
              branch_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
              commit_message: "Add new customer dimension"
              allow_branch_exists: true
              require_branch_exists: false
      responses:
        '200':
          description: Commit operation completed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  pr_url:
                    type: string
                    description: URL of the created or updated pull request.
                    example: "https://github.com/myorg/repo/compare/main...feature?expand=1"
                  git_sha:
                    type: string
                    description: The git SHA of the commit.
                    example: "abc123def456"
                  in_sync:
                    type: boolean
                    description: Whether the shared model is currently in sync with its configured default git branch.
                    example: true
                  did_sync:
                    type: boolean
                    description: Whether a sync operation was performed between the shared model and its configured default git branch.
                    example: true
              example:
                pr_url: "https://github.com/myorg/repo/compare/main...feature?expand=1"
                git_sha: "abc123def456"
                in_sync: true
                did_sync: true
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: modelId: Invalid uuid`
            - `Bad Request: branch_id: Invalid uuid`
            - `Bad Request: commit_message is required`
            - `Bad Request: Cannot set both allow_branch_exists=false and require_branch_exists=true`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Forbidden: Requires MANAGE_MODEL permission` - This endpoint requires **Modeler** or **Connection Admin** permisisons.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Not Found: Model does not exist`
            - `Not Found: Branch does not exist`
            - `Not Found: Git configuration not found for this model`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/migrate:
    post:
      tags:
        - Models
      summary: Migrate a model
      description: |
        Copy a model from one Omni connection to another by reading the source model's YAML at a specific Git ref and writing it to the target model. Supports same-organization and cross-organization migrations.

        This API:

        1. Reads the full model YAML from the source model's git repository at the specified `gitRef` (merged with the default branch).
        2. If `branchName` is provided and the branch already exists on the target model, the API writes to that branch. If the branch doesn't exist, one is created.
        3. Writes the YAML to the target model or branch, replacing its model definition.

        ### Requirements

        To successfully migrate a model:

        - The user performing the migration must have:
           - **For the source model** - **Querier**, **Modeler**, or **Connection Admin** permissions 
           - **For the target model** - **Modeler** or **Connection Admin** permissions
           - **For cross-organization migrations** - The user must be a member of both organizations
        - The source model must have [git configured](/integrations/git). This is required so the API can read the model YAML from the repository.
        - The target model should have an identical schema model to the source model at the instant the git ref was committed.
      security:
        - orgApiKey: []
      operationId: migrateModel
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The shared model ID to read YAML from.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - gitRef
                - targetModelId
              properties:
                gitRef:
                  type: string
                  description: Git reference (branch name, tag, or commit SHA) to read the source model YAML from.
                  example: "main"
                targetModelId:
                  type: string
                  format: uuid
                  description: The shared model ID to write the YAML to.
                  example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                branchName:
                  type: string
                  description: |
                    **Required if the target model has git enabled.**. Branch name on the target model. If the branch doesn't exist, it will be created.
                  example: "migrate-from-prod"
                commitMessage:
                  type: string
                  description: Git commit message.
                  example: "Migrate model from production"
                deleteViewsAndTopicsMissingFromSource:
                  type: boolean
                  description: |
                    Controls how views and topics present in the target model but missing from the source model are handled. When `true` (default), these views and topics are marked as `ignored: true`. When `false`, they are inherited from the parent model instead.
                  default: true
                  example: true
            example:
              gitRef: "main"
              targetModelId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
              branchName: "migrate-from-prod"
              commitMessage: "Migrate model from production"
              deleteViewsAndTopicsMissingFromSource: true
      responses:
        '200':
          description: Model migrated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Whether the migration completed successfully.
                    example: true
              example:
                success: true
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: Invalid JSON body`
            - `Bad Request: gitRef is empty`
            - `Bad Request: targetModelId is not a valid UUID`
            - `Bad Request: Target model has git enabled but branchName was not provided`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Forbidden: User does not have access to the source or target model`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Not Found: Source model not found`
            - `Not Found: Target model not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/query/run:
    post:
      tags:
        - Queries
      summary: Run query
      description: |
        Run the query specified in the request body. Successful requests will return the data as a base64 encoded [Apache Arrow](https://arrow.apache.org/) table, allowing you to extract query results from Omni and use them elsewhere. For example, piping data to Google Sheets or leveraging data in a Python notebook.
      x-mint:
        content: |
          **Encountering timeouts?** If a request takes too long, the API will return a `408 timeout` error with a body that includes a `remaining_job_ids` property. When this occurs, poll the [Wait for query results](/api/queries/wait-for-query-results) endpoint until the `timed_out` property is `false`.
      security:
        - bearerAuth: []
      operationId: runQuery
      parameters:
        - name: userId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: |
            **Requires an Organization API key.** The ID of the user to run the query as.

            Personal Access Tokens (PATs) cannot use this parameter to act on behalf of other users.
          example: 9e8719d9-276a-4964-9395-a493189a247c
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: object
                  description: |
                    A JSON object representing the query to be run.

                    To retrieve a query's JSON object from an Omni workbook:

                    1. Open a workbook in Omni.
                    2. Click the **bug icon** to toggle open the **Inspector** panel. For **Mac** use `Option + 9`; for **Windows**, use `Alt + 9`.
                    3. Locate the **Query structure** section.
                    4. Copy the query JSON.
                  required:
                    - modelId
                    - table
                    - fields
                  properties:
                    modelId:
                      type: string
                      format: uuid
                      description: The ID of the model to execute the query against.
                    table:
                      type: string
                      description: The base table or topic name.
                    fields:
                      type: array
                      items:
                        type: string
                      description: The column names to include in the query results.
                    limit:
                      oneOf:
                        - type: integer
                          maximum: 75000
                          default: 1000
                        - type: "null"
                      description: |
                        Number of rows to return. Defaults to `1000` if not defined. Maximum is `75000`.

                        - If `null`, unlimited results will be returned. **Note**: This is compatible with pivoted queries if `resultType` is `csv` or `xlsx`.
                        - If set to a negative number, the API will return a `400` response.
                    sorts:
                      type: array
                      items:
                        type: object
                        properties:
                          column_name:
                            type: string
                            description: The column to sort by
                          sort_descending:
                            type: boolean
                            description: If `true`, sort in descending order
                          is_column_sort:
                            type: boolean
                          null_sort:
                            type: string
                            description: How to handle nulls (e.g., `OMNI_DEFAULT`)
                      description: Sort specifications for the query results.
                    filters:
                      type: object
                      description: Filter conditions to apply to the query.
                    pivots:
                      type: array
                      items:
                        type: object
                      description: Pivot configurations for the query.
                    calculations:
                      type: array
                      items:
                        type: object
                      description: Custom calculations to include in the query.
                    column_totals:
                      type: object
                      description: Column total configuration.
                    row_totals:
                      type: object
                      description: Row total configuration.
                    column_limit:
                      type: integer
                      description: Column limit for pivoted queries.
                    join_paths_from_topic_name:
                      type: string
                      description: Topic name for join paths.
                    join_via_map:
                      type: object
                      description: Custom join path mappings.
                    version:
                      type: integer
                      description: Query version number.
                    default_group_by:
                      type: boolean
                      description: If `true`, enable default grouping behavior.
                    dbtMode:
                      type: boolean
                      description: If `true`, enable dbt mode.
                    rewriteSql:
                      type: boolean
                      description: If `true`, enable SQL mode.
                    dimensionIndex:
                      type: integer
                      description: The index of the last dimension in the list of fields in the query. Used internally to order and group fields in the UI.
                    controls:
                      type: array
                      items:
                        type: object
                      description: Control configurations for the query.
                    manualSort:
                      type: boolean
                      description: If `true`, sorting was manually applied to the query instead of using Omni's default behavior.
                    fill_fields:
                      type: array
                      items:
                        type: string
                      description: A list of dimensions that should have missing values filled with placeholder rows.
                    userEditedSQL:
                      type: string
                      description: User-edited SQL override.
                    custom_summary_types:
                      type: object
                      description: Custom summary type configurations.
                branchId:
                  type: string
                  format: uuid
                  description: |
                    Optional ID of a model branch to execute the query against. When provided, the query runs against the specified branch instead of the shared model.

                    The branch must belong to the same shared model specified in `query.modelId` and be within your organization.
                connectionEnvironmentId:
                  type: string
                  format: uuid
                  description: |
                    Optional connection environment ID to override the session-derived connection environment for this query run. The requesting user must have access to the specified environment.

                    When provided, the query runs against the specified connection environment instead of the user's session selection.
                userId:
                  type: string
                  format: uuid
                  description: |
                    Executes the query as the specified user.

                    Prefer passing `userId` as a query parameter instead of in the request body. Providing `userId` in both locations will return a `400` error.

                    If not provided in either location, the API uses the user associated with the API token. The `userId` must belong to a user in your organization.
                cache:
                  type: string
                  default: SkipRequery
                  enum: [ Standard, SkipRequery, SkipCache ]
                  description: |
                    Optional cache policy to control how query caching behaves. Must be one of:

                    - `Standard` - Uses standard caching behavior
                    - `SkipRequery` - Uses cached results if available, but does not requery if not found (default)
                    - `SkipCache` - Bypasses cache and always executes a fresh query
                resultType:
                  type: string
                  enum: [ csv, json, xlsx ]
                  description: |
                    **Cannot be used with `planOnly`.** Specifies the format of query results exported by the API.

                    If omitted, the API will return results in a base-64 encoded format.
                planOnly:
                  type: boolean
                  description: |
                    **Cannot be used with `resultType`.** If `true`, the API will return the query execution plan without running the query. The response will include the generated SQL and query metadata.
                formatResults:
                  type: boolean
                  default: true
                  description: |
                    **Applicable only if `resultType` is specified.** If `true`, formatting will be applied to numeric and currency values in the form of currency symbols and thousand separators.
                workbookUrl:
                  type: boolean
                  default: false
                  description: |
                    **Cannot be combined with `planOnly`.** If `true`, creates an ephemeral workbook reproducing the query (preserving the run's `branchId` and timezone) and returns its URL in the `X-Omni-Workbook-Url` response header. The header works for all `resultType` modes and is delivered as a header to keep response bodies byte-identical.

                    The header is best-effort: it will be silently omitted if the (target) user lacks the workbooks permission (`USE_WORKBOOKS`) on the query's model, or if workbook creation fails.
            example:
              query:
                modelId: "bcf0cffd-ec1b-44d5-945a-a261ebe407fc"
                table: "order_items"
                fields:
                  - "inventory_items.product_department"
                  - "inventory_items.product_category"
                  - "inventory_items.count"
                limit: 10
                sorts:
                  - column_name: "inventory_items.product_department"
                    sort_descending: false
                join_paths_from_topic_name: "order_items"
      responses:
        '200':
          description: |
            Successful responses will contain the following:

            - An object with a `jobs_submitted` property
            - An object containing details about the job, such as `job_id`, `status`, and the provided query. **Note**: If `planOnly: true` was included in the request body, the status will be `PLANNED`.
            - The job details object will also contain a `result` property, which contains the query results as a base64 encoded Apache Arrow table. You can use the [Omni Python SDK](https://github.com/exploreomni/omni-python-sdk) to decode and validate the results.

            If the request times out, the response will include `remaining_job_ids`. Poll the `/api/v1/query/wait` endpoint with these IDs until `timed_out` is `false`.
          headers:
            X-Omni-Workbook-Url:
              description: |
                URL of an ephemeral workbook reproducing this query, preserving the run's branch and timezone. Present only when the request sets `workbookUrl: true` and the (target) user has the permissions to use workbooks on the query's model.

                The workbook provides a "view in Omni" jump from API query results to the live data. Workbook creation is best-effort — if permission is missing or creation fails, the query still succeeds and this header is silently omitted.
              schema:
                type: string
                format: uri
                example: "https://myorg.omniapp.co/e/1:abc123DEF456/1"
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobs_submitted:
                    type: object
                    description: Map of job IDs to client result IDs
                  job_id:
                    type: string
                    format: uuid
                    description: The unique identifier for the query job
                  status:
                    type: string
                    description: Job status (e.g., `COMPLETE`, `PLANNED`)
                  client_result_id:
                    type: string
                    format: uuid
                    description: Client-side result identifier
                  summary:
                    type: object
                    description: Query execution summary including SQL, stats, and field metadata
                  cache_metadata:
                    type: object
                    description: Cache information including TTL and data freshness
                  query:
                    type: object
                    description: The executed query details
                  result:
                    type: string
                    description: Base64 encoded Apache Arrow table containing query results
                  stream_stats:
                    type: object
                    description: Server-side streaming metrics
                    properties:
                      server_stream:
                        description: Time in milliseconds to stream the result data from the server
                        type: integer
                  remaining_job_ids:
                    type: array
                    items:
                      type: string
                      format: uuid
                    description: IDs of the jobs still processing if the request timed out. Poll `/api/v1/query/wait` with these IDs.
                  timed_out:
                    type: string
                    description: Indicates if the request timed out. If `true`, use `remaining_job_ids` to poll for results.
        '400':
          description: |
            Bad Request. Possible causes:

            - `userId` provided in both the query parameter and request body
            - `formatResults` provided without `resultType`
            - `planOnly` and `resultType` both provided (incompatible)
            - `planOnly` and `workbookUrl` both provided (incompatible)
            - `workbookUrl` requested on a query containing `staticQueryReferences`
            - `query.limit` is a negative number
            - `branchId` provided when querying a schema model (schema models do not have branches)
            - `branchId` is not a valid UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                userIdInBothLocations:
                  summary: userId in both query parameter and request body
                  value:
                    detail: "userId may be provided in either the request body or as a query parameter, but not both"
                    status: 400
                formatResultsWithoutResultType:
                  summary: formatResults without resultType
                  value:
                    detail: "formatResults cannot be provided without resultType"
                    status: 400
                planOnlyWithResultType:
                  summary: planOnly and resultType both provided
                  value:
                    detail: "planOnly and resultType cannot both be provided"
                    status: 400
                negativeLimitValue:
                  summary: Negative limit value
                  value:
                    detail: "query.limit: Too small: expected number to be >0"
                    status: 400
                schemaModelWithBranch:
                  summary: Schema model with branchId
                  value:
                    detail: "Schema models do not have branches"
                    status: 400
                planOnlyWithWorkbookUrl:
                  summary: planOnly and workbookUrl both provided
                  value:
                    detail: "planOnly and workbookUrl cannot both be provided"
                    status: 400
                workbookUrlWithStaticQueryReferences:
                  summary: workbookUrl requested on a query with staticQueryReferences
                  value:
                    detail: "workbookUrl cannot be requested on queries containing staticQueryReferences"
                    status: 400
        '403':
          description: |
            Forbidden. Possible causes:

            - A Personal Access Token (PAT) attempted to use `userId` to run the query as a different user. PATs can only run queries as their own user.
            - If including a `connectionEnvironmentId`, the requesting user must have access to the specified environment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                userScopedKeyDifferentUser:
                  summary: PAT attempting to use different userId
                  value:
                    detail: "Personal Access Tokens cannot run queries on behalf of other users"
                    status: 403
        '404':
          description: |
            Not Found. Possible causes:

            - The specified `userId` does not exist or does not belong to your organization
            - The specified model, topic, or view does not exist
            - The specified `branchId` does not exist, belongs to a different model, or is in a different organization
            - The branch has been soft-deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                userNotFound:
                  summary: User not found
                  value:
                    detail: "User with id 550e8400-e29b-41d4-a716-446655440000 does not exist"
                    status: 404
                branchNotFound:
                  summary: Branch not found
                  value:
                    detail: "Branch model with id 550e8400-e29b-41d4-a716-446655440000 does not exist"
                    status: 404
        '408':
          $ref: '#/components/responses/QueryTimeoutError'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/query/wait:
    get:
      tags:
        - Queries
      summary: Wait for query results
      description: |
        Poll for the results of one or more query jobs. Use this endpoint when a request to the [Run query endpoint](/api/queries/run-query) times out and returns `remaining_job_ids` in the response.

        This endpoint will wait for the specified jobs to complete and return their results. If the jobs are still processing when the request times out, the response will include the remaining job IDs to poll again.
      security:
        - bearerAuth: []
      operationId: waitForQuery
      parameters:
        - name: job_ids
          in: query
          required: true
          schema:
            type: array
            items:
              type: string
              format: uuid
          description: |
            An array of job IDs to poll for results. These IDs are returned in the `remaining_job_ids` property when a request to the [Run query endpoint](/api/queries/run-query) times out.

            Format the parameter as a JSON array (e.g., `?job_ids=["job-id-1","job-id-2"]`).
      responses:
        '200':
          description: |
            Successful response containing the query results for completed jobs.

            If all jobs have completed, `timed_out` will be `false` and the response will include the full query results.

            If some jobs are still processing when this request times out, `timed_out` will be `true` and `remaining_job_ids` will contain the IDs of jobs that are still running. Continue polling with these IDs until all jobs complete.
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                    format: uuid
                    description: The unique identifier for the query job
                  status:
                    type: string
                    description: Job status (e.g., `COMPLETE`, `PLANNED`)
                  client_result_id:
                    type: string
                    format: uuid
                    description: Client-side result identifier
                  summary:
                    type: object
                    description: Query execution summary including SQL, stats, and field metadata
                  cache_metadata:
                    type: object
                    description: Cache information including TTL and data freshness
                  query:
                    type: object
                    description: The executed query details
                  result:
                    type: string
                    description: Base64 encoded Apache Arrow table containing query results
                  stream_stats:
                    type: object
                    description: Server-side streaming metrics
                    properties:
                      server_stream:
                        description: Time in milliseconds to stream the result data from the server
                        type: integer
                  remaining_job_ids:
                    type: array
                    items:
                      type: string
                      format: uuid
                    description: IDs of jobs still processing if this request timed out. Continue polling with these IDs.
                  timed_out:
                    type: boolean
                    description: Indicates if the request timed out. If `true`, use `remaining_job_ids` to poll again.
              examples:
                completedJob:
                  summary: Job completed successfully
                  value:
                    job_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                    status: "COMPLETE"
                    client_result_id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    summary:
                      sql: "SELECT * FROM order_items LIMIT 10"
                      total_rows: 10
                      execution_time_ms: 245
                    result: "QVJST1cxAAD/////..."
                    timed_out: false
                timedOut:
                  summary: Request timed out with remaining jobs
                  value:
                    timed_out: true
                    remaining_job_ids:
                      - "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                      - "b2c3d4e5-f6a7-8901-bcde-f12345678901"
        '400':
          description: |
            Bad Request. Possible causes:

            - Missing or invalid `job_ids` parameter
            - `job_ids` is not a valid JSON array
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingJobIds:
                  summary: Missing job_ids parameter
                  value:
                    detail: "job_ids parameter is required"
                    status: 400
                invalidJobIds:
                  summary: Invalid job_ids format
                  value:
                    detail: "job_ids must be a valid JSON array of UUIDs"
                    status: 400
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible causes:

            - One or more job IDs do not exist
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                jobNotFound:
                  summary: Job not found
                  value:
                    detail: "Job with id <jobId> does not exist"
                    status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/schedules:
    post:
      tags:
        - Schedules
      summary: Create schedule
      description: |
        Create a scheduled task for the specified dashboard. Supports applying filters and formatting, creating alert conditions, and triggering test deliveries.

        Supports the following destinations:

        - [**Email**](/share/deliveries/email)
        - [**Slack**](/share/deliveries/slack) - **Note**: A Slack workspace must already be connected to Omni to use this destination type.
        - [**SFTP**](/share/deliveries/sftp)
        - [**Webhook**](/share/deliveries/webhooks)
        - [**Amazon S3**](/share/deliveries/amazon-s3)
      security:
        - bearerAuth: []
      operationId: createSchedule
      parameters:
        - name: userId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            **Only supported when using an Organization API key.** The membership ID of the user who should own the schedule. **Note**: This value is the **membership ID**, not the user ID. Use the [List users](/api/users/list-users) endpoint to retrieve membership IDs.

            When using an Organization API key:

            - If `userId` is provided, the schedule is owned by the specified user
            - If `userId` is not provided, the schedule is owned by the organization

            Attempting to use a Personal Access Token (PAT) with this parameter will result in a `403 Forbidden` error. When using a PAT, the schedule owner will be the creator of the PAT.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - identifier
                - name
                - schedule
                - timezone
                - format
                - destinationType
              properties:
                identifier:
                  type: string
                  description: |
                    The ID of the dashboard where the task will be created. This can be retrieved by:

                    - **Opening the document settings** - Navigate to **File > Document settings** in the dashboard and then click **Settings**. The **Identifier** field contains the dashboard ID.
                    - **Using the dashboard's URL** - The string after `/dashboards` is the dashboard's ID; for example: `https://myorg.omniapp.co/dashboards/12db1a0a`
                name:
                  type: string
                  description: The name of the task.
                schedule:
                  type: string
                  description: |
                    A cron expression defining when the schedule will run, in [Amazon Web Services (AWS) Cloudwatch syntax](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html).

                    **Note**: Delivery times are approximate and will happen within 15 minutes of the specified schedule run time.
                timezone:
                  type: string
                  description: |
                    The IANA timezone where the task should run, such as `Asia/Dubai`. Refer to the [`TZ` column in this IANA.org timezone description file](https://data.iana.org/time-zones/tzdb-2021a/zone1970.tab) for a list of accepted values.
                format:
                  type: string
                  enum: [csv, link_only, json, pdf, png, xlsx]
                  description: |
                    The output format of the schedule.
                destinationType:
                  type: string
                  enum: [webhook, email, sftp, slack, s3]
                  description: |
                    The destination where the task's output should be delivered.

                    **Note**: If using `slack`, you must connect a Slack workspace to Omni first.
                filterConfig:
                  type: object
                  description: |
                    An object specifying the filter conditions to apply to the task. The filter key specified must already exist in the dashboard. If the dashboard doesn't have any filters configured, filters can't be used in the task.

                    Use the [Get dashboard filters and controls endpoint](/api/dashboard-filters/get-dashboard-filters-and-controls) to retrieve filter configuration objects.

                    **Note**: Filter keys are case-sensitive and must match exactly.
                conditionType:
                  type: string
                  enum: [RESULTS_PRESENT, RESULTS_CHANGED, RESULTS_MISSING, RESULTS_UNCHANGED]
                  description: |
                    **Required if `conditionQueryMapKey` is provided.** Defines the type of condition to use for alerts.
                conditionQueryMapKey:
                  type: string
                  description: |
                    **Required if `conditionType` is provided.** The ID of the query to monitor for triggering an alert. Must reference a valid query in the dashboard.
                queryIdentifierMapKey:
                  type: string
                  description: |
                    The ID of the query to include in a single tile task. Must reference a valid query in the dashboard.

                    **Required for:**
                    - `format: json` with `enableFormatting: true`
                    - `format: xlsx` with `overrideRowLimit: true`
                killJobsOnFailure:
                  type: boolean
                  default: false
                  description: |
                    If `true`, stop the entire job if any queries included in the task fail.
                testNow:
                  type: boolean
                  default: false
                  description: |
                    If `true`, run the task immediately instead of scheduling.

                    Not supported for `RESULTS_CHANGED` alert conditions or `s3` destinations.
                hideTitle:
                  type: boolean
                  default: false
                  description: |
                    **Compatible with `pdf` and `png` formats.** If `true`, the content's title will be hidden in the task's output.
                hideHiddenFields:
                  type: boolean
                  default: false
                  description: |
                    **Compatible with `csv` and `xlsx` formats.** If `true`, fields marked as `hidden` won't be displayed in the task's output.
                enableFormatting:
                  type: boolean
                  default: false
                  description: |
                    **Compatible with `csv`, `xlsx`, and `json` formats.** If `true`, number and date formatting will be preserved in the task's output.

                    If `true` for `json` format, `queryIdentifierMapKey` is required.
                overrideRowLimit:
                  type: boolean
                  default: false
                  description: |
                    **Compatible with `csv`, `json`, and `xlsx` formats.** If `true`, the default row limit will be overridden.

                    If `true` for `json` and `xlsx` formats, a `queryIdentifierMapKey` is required.
                maxRowLimit:
                  type: integer
                  description: |
                    **Compatible with `csv`, `json`, and `xlsx` formats.** Used with `overrideRowLimit`. Specifies the maximum number of rows.
                showContentLink:
                  type: boolean
                  default: true
                  description: |
                    **Compatible with all formats except `link_only`.** If `true`, a link to the content will be shown in the task's output.
                showFilters:
                  type: boolean
                  default: true
                  description: |
                    **Compatible with all formats except `link_only` and `csv`.** If `true`, filters will be shown in the task's output.
                expandTablesToShowAllRows:
                  type: boolean
                  default: false
                  description: |
                    **Compatible with `pdf` and `png` formats.** If `true`, up to 1,000 rows in table visualizations will be included in the delivery.

                    This parameter cannot be used when `paperFormat: fit_page`.
                paperFormat:
                  type: string
                  enum: [a3, a4, letter, legal, fit_page, tabloid]
                  description: |
                    **Compatible with `pdf` format.** Defines the paper format (size) of the resulting PDF.

                    The `fit_page` option cannot be used if `expandTablesToShowAllRows: true`.
                paperOrientation:
                  type: string
                  enum: [portrait, landscape]
                  description: |
                    **Compatible with `pdf` format.** Defines the paper orientation of the resulting PDF.
                singleColumnLayout:
                  type: boolean
                  default: false
                  description: |
                    **Compatible with `pdf` and `png` formats.** If `true`, dashboard tiles will be arranged into a single vertical column.
                url:
                  type: string
                  description: |
                    **Required if `destinationType: webhook`.** A webhook URL. Must be a valid HTTP/HTTPS URL.
                recipients:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
                  description: |
                    **Required if `destinationType: email` or `destinationType: slack`.**

                    For **email** destinations: An array of strings, each specifying a valid email address.

                    For **Slack** destinations: Channel or user IDs. Accepts either a string or array format:

                    - **Channels** can be a single channel ID (`"C01234567"`) or an array with exactly **one** channel ID (`["C01234567"]`). Additionally:
                      - **For private channels**, invite the Omni Slackbot to the channel first
                      - Only one Slack channel is supported per schedule. Multiple channels will return a 400 error.
                    - **Users** can be a string of comma-separated user IDs (`"U111,U222"`) or an array of user IDs (`["U111", "U222"]`)   
                subject:
                  type: string
                  description: |
                    **Required if `destinationType: email`.** Email subject line.
                textBody:
                  type: string
                  description: |
                    **Applicable to email and Slack destinations.** Custom message text included with the delivery.
                slackRecipientType:
                  type: string
                  enum: [channel, users]
                  description: |
                    **Required if `destinationType: slack`.** Specifies whether recipients are a channel or individual users.

                    - `channel`: Deliver to a Slack channel. If the channel is private, you'll also need to invite the Omni Slackbot to the channel.
                    - `users`: Deliver to individual Slack users
                fanOut:
                  type: boolean
                  default: false
                  description: |
                    **Applicable to email destinations.** If `true`, send individual emails to each recipient.

                    Note: If your organization's [delivery personalization setting](/administration/content-permissions#delivery-personalization) is set to **Never**, `fanOut` is always persisted as `false` regardless of the submitted value. If set to **Always**, personalization is forced on for all deliveries. See [Personalizing email deliveries](/share/deliveries/email/personalization) for more information.
                address:
                  type: string
                  description: |
                    **Required if `destinationType: sftp`.** SFTP server address.
                port:
                  type: integer
                  description: |
                    **Required if `destinationType: sftp`.** SFTP port.
                username:
                  type: string
                  description: |
                    **Required if `destinationType: sftp`.** SFTP username.
                path:
                  type: string
                  description: |
                    **Required if `destinationType: sftp`.** Remote file path.
                passwordUnencrypted:
                  type: string
                  description: |
                    **Applicable to SFTP destinations.** SFTP password.
                bucketName:
                  type: string
                  description: |
                    **Required if `destinationType: s3`.** The name of the target S3 bucket. Must be 3-63 characters, lowercase letters, numbers, hyphens, and periods only.
                  example: "my-reports-bucket"
                region:
                  type: string
                  description: |
                    **Required if `destinationType: s3`.** The AWS region of the S3 bucket (e.g., `us-east-1`).
                  example: "us-east-1"
                roleArn:
                  type: string
                  description: |
                    **Required if `destinationType: s3`.** The ARN of the IAM role in the customer's AWS account that grants Omni write access to the S3 bucket.
                  example: "arn:aws:iam::123456789012:role/OmniS3DeliveryRole"
                keyPrefix:
                  type: string
                  description: |
                    **Applicable to S3 destinations.** Folder path prefix for the uploaded file (e.g., `reports/daily/`). Must not contain path traversal sequences.
                  example: "reports/daily/"
                filename:
                  type: string
                  description: |
                    **Applicable to S3 destinations.** Filename template (without extension) using Mustache syntax. Supported variables: `{{currentDate}}`, `{{currentTime}}`, `{{currentYear}}`, `{{currentMonth}}`, `{{currentDay}}`, `{{yesterdayDate}}`, `{{timeZone}}`, `{{entityName}}`, `{{format}}`, `{{scheduledTaskName}}`.
                  example: "{{scheduledTaskName}}_{{currentDate}}"
            examples:
              'Webhook':
                summary: Webhook destination
                value:
                  identifier: "12db1a0a"
                  name: "My Webhook schedule"
                  schedule: "0 9 ? * * *"
                  timezone: "UTC"
                  format: "json"
                  destinationType: "webhook"
                  url: "https://webhooksrus.com/1234566"
              'Email':
                summary: Email destination
                value:
                  identifier: "12db1a0a"
                  name: "My Email schedule"
                  schedule: "0 9 ? * * *"
                  timezone: "UTC"
                  format: "pdf"
                  destinationType: "email"
                  paperFormat: "legal"
                  expandTablesToShowAllRows: true
                  paperOrientation: "landscape"
                  recipients:
                    - "user1@example.com"
                    - "user2@example.com"
                  subject: "Daily Sales report"
                  textBody: "Here are the daily sales!"
                  fanOut: false
              'SFTP':
                summary: SFTP destination
                value:
                  identifier: "12db1a0a"
                  name: "My SFTP schedule"
                  schedule: "0 9 ? * * *"
                  timezone: "UTC"
                  format: "xlsx"
                  destinationType: "sftp"
                  address: "sftp.example.com"
                  port: 22
                  username: "sftpuser"
                  passwordUnencrypted: "password123"
              'Slack channel':
                summary: Slack channel destination
                value:
                  identifier: "12db1a0a"
                  name: "Weekly Report"
                  schedule: "0 9 ? * MON *"
                  timezone: "America/New_York"
                  format: "pdf"
                  destinationType: "slack"
                  recipients: "C01234567"
                  slackRecipientType: "channel"
                  textBody: "Here is your weekly report!"
              'Slack users':
                summary: Slack users destination
                value:
                  identifier: "12db1a0a"
                  name: "Sales Team Report"
                  schedule: "0 9 ? * * *"
                  timezone: "America/New_York"
                  format: "pdf"
                  destinationType: "slack"
                  recipients:
                    - "U11111111"
                    - "U22222222"
                  slackRecipientType: "users"
                  textBody: "Your daily sales report"
              'Amazon S3':
                summary: Amazon S3 destination
                value:
                  identifier: "12db1a0a"
                  name: "Daily Export to S3"
                  schedule: "0 9 ? * * *"
                  timezone: "UTC"
                  format: "csv"
                  destinationType: "s3"
                  bucketName: "my-reports-bucket"
                  region: "us-east-1"
                  roleArn: "arn:aws:iam::123456789012:role/OmniS3DeliveryRole"
                  keyPrefix: "reports/daily/"
                  filename: "{{entityName}}-{{currentDate}}"
              'Filters and formatting':
                summary: Filters and formatting
                value:
                  identifier: "dashboard-abc123"
                  name: "Weekly Regional Report"
                  schedule: "0 0 ? * MON *"
                  timezone: "UTC"
                  format: "pdf"
                  destinationType: "email"
                  recipients:
                    - "iamagoodblob@blobsrus.com"
                    - "managerblob@blobsrus.com"
                  subject: "Weekly Regional Report"
                  filterConfig:
                    region:
                      kind: "EQ"
                      left_side: "US"
                      type: "string"
                  hideTitle: true
              'Alert condition':
                summary: Alert condition
                value:
                  identifier: "dashboard-abc123"
                  name: "Data Alert Webhook"
                  schedule: "0 */6 * * ? *"
                  timezone: "UTC"
                  format: "json"
                  destinationType: "webhook"
                  url: "https://api.example.com/webhook"
                  conditionType: "RESULTS_PRESENT"
                  conditionQueryMapKey: "1"
                  queryIdentifierMapKey: "1"
                  overrideRowLimit: true
                  maxRowLimit: 1000
              'Test delivery':
                summary: Test delivery
                value:
                  identifier: "dashboard-abc123"
                  name: "Test Delivery"
                  schedule: "0 0 1 1 ? 2099"
                  timezone: "UTC"
                  format: "pdf"
                  destinationType: "email"
                  recipients:
                    - "iamagoodblob@blobsrus.com"
                  subject: "Test Delivery"
                  testNow: true
      responses:
        '200':
          description: Schedule created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: The ID of the created schedule.
                  message:
                    type: string
                    example: "Successfully created schedule"
                    description: A success message.
                  delivererRoleArn:
                    type: string
                    description: |
                      **S3 destinations only.** The ARN of Omni's deliverer IAM role. The customer uses this as the `Principal` in their IAM trust policy to allow Omni to assume their role.
                  externalId:
                    type: string
                    description: |
                      **S3 destinations only.** The organization ID, used as the external ID in the IAM trust policy `sts:ExternalId` condition. This value is the same for all S3 destinations in the organization.
              examples:
                'Default':
                  summary: Default response
                  value:
                    id: "12db1a0a"
                    message: "Successfully created schedule"
                'Amazon S3':
                  summary: Amazon S3 destination
                  value:
                    id: "12db1a0a"
                    message: "Successfully created schedule"
                    delivererRoleArn: "arn:aws:iam::123456789012:role/OmniS3DeliveryRole"
                    externalId: "<externalId>"
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Analyses do not support dashboards.`
            - `Document does not have a dashboard`
            - `schedule: Schedule is an invalid cron expression`
            - `timezone: Time zone must be IANA valid.`
            - `hideTitle can only be used with PDF or PNG formats`
            - `Invalid filter keys found in schedule configuration: <filterKey>. Available dashboard filter keys are: <key1>, <key2>, ....`
            - `Test delivery not supported for condition type RESULTS_CHANGED`
            - `Print options are not supported for this format`
            - `Single column layout and table expansion options are not supported for FIT_PAGE format.`
            - `Must provide both a trigger query and an alert condition type`
            - `slackRecipientType is required for Slack destinations`
            - `Only one Slack channel is supported per schedule`
            - `Organization does not have a Slack credential configured`
            - `Test delivery not supported for S3 destinations`
            - `Invalid bucket name`
            - `Invalid role ARN`
            - `Invalid region`
            - `Key prefix contains invalid path traversal`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Personal access tokens cannot create schedules on behalf of other users`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Document with identifier "<dashboardId>" not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      tags:
        - Schedules
      summary: List schedules
      description: |
        Retrieves scheduled tasks. This endpoint supports filtering, sorting, and cursor-based pagination.

        To retrieve the recipients for a schedule, use the [List schedule recipients](/api/schedule-recipients/list-schedule-recipients) endpoint.
      security:
        - bearerAuth: []
      operationId: listSchedules
      parameters:
        - name: q
          in: query
          schema:
            type: string
          description: |
            Search term for filtering schedules by name, dashboard name, or owner name (case-insensitive).
        - name: status
          in: query
          schema:
            type: string
            enum: [success, error, canceled, none]
          description: Filter schedules by delivery status.
        - name: destination
          in: query
          schema:
            type: string
            enum: [email, slack, webhook, sftp, s3]
          description: Filter schedules by destination type.
        - name: scheduleType
          in: query
          schema:
            type: string
            enum: [alert, schedule]
          description: Filter by schedule type.
        - name: contentType
          in: query
          schema:
            type: string
            enum: [dashboard, single tile]
          description: Filter schedules by content type.
        - name: sortField
          in: query
          schema:
            type: string
            default: scheduleName
            enum: [scheduleName, dashboardName, ownerName, lastRun, lastRunStatus]
          description: The field to sort results by.
        - name: sortDirection
          in: query
          schema:
            type: string
            default: desc
            enum: [asc, desc]
          description: The direction to sort results.
        - name: cursor
          in: query
          schema:
            type: integer
            default: 1
          description: The page number for offset-based pagination.
        - name: pageSize
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: The number of results to include on each page.
        - name: ownerId
          in: query
          schema:
            type: string
            format: uuid
          description: |
            Filter schedules by the owner's membership ID. Use the [List users](/api/users/list-users) endpoint to retrieve membership IDs.

            API results vary by API key type:

            - **Organization API keys** - When specified, only schedules for the specified owner are returned. Otherwise, all schedules in the organization are returned.
            - **Personal access token** - When specified, only the token owner's membership ID is allowed. If another user's membership ID is provided, the API will return a `403 Forbidden` error.
        - name: embedEntity
          in: query
          schema:
            type: string
          description: |
            Filter schedules by embed entity:

            - If used as the only filter, list all schedules in the embed entity
            - If used with `ownerId`, list only schedules for the specified user if they are associated with the `embedEntity`. The response will be empty if the specified `ownerId` doesn't have the entity.
            - Can be combined with other filters for more specific results
        - name: identifier
          in: query
          schema:
            type: string
          description: |
            Filter schedules by dashboard ID. This can be retrieved by:

            - **Opening the document settings** - Navigate to **File > Document settings** in the dashboard and then click **Settings**. The **Identifier** field contains the dashboard ID.
            - **Using the dashboard's URL** - The string after `/dashboards` is the dashboard's ID; for example: `https://myorg.omniapp.co/dashboards/12db1a0a`
      responses:
        '200':
          description: Paginated schedule list
          content:
            application/json:
              schema:
                type: object
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    description: List of schedule records.
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: The schedule ID.
                        schedule:
                          type: string
                          description: The cron expression for the schedule.
                        disabledAt:
                          type: string
                          format: date-time
                          nullable: true
                          description: Timestamp when the schedule was paused, if applicable.
                        name:
                          type: string
                          description: The name of the schedule.
                        timezone:
                          type: string
                          description: The IANA timezone for the schedule.
                        identifier:
                          type: string
                          description: The ID of the dashboard associated with the schedule.
                        dashboardName:
                          type: string
                          description: The name of the dashboard associated with the schedule.
                        ownerId:
                          type: string
                          format: uuid
                          description: The membership ID of the schedule owner. Use the [List users](/api/users/list-users) endpoint to retrieve membership IDs.
                        ownerName:
                          type: string
                          description: The name of the schedule owner.
                        lastCompletedAt:
                          type: string
                          format: date-time
                          nullable: true
                          description: Timestamp of the last completed run.
                        lastStatus:
                          type: string
                          nullable: true
                          description: Status of the last run (e.g., `COMPLETE`, `ERROR_DELIVERED`).
                        destinationType:
                          type: string
                          description: The destination type (e.g., `email`, `webhook`, `sftp`, `slack`).
                        format:
                          type: string
                          description: The output format of the schedule. (e.g., `CSV`, `JSON`)
                        recipientCount:
                          type: integer
                          description: Number of recipients. Returns `-1` for webhook destinations.
                        content:
                          type: string
                          description: Content type (`dashboard` or `single tile`).
                        slackRecipientType:
                          type: string
                          nullable: true
                          description: Slack recipient type, if applicable.
                        systemDisabledAt:
                          type: string
                          format: date-time
                          nullable: true
                          description: Timestamp when the schedule was system-disabled, if applicable.
                        systemDisabledReason:
                          type: string
                          nullable: true
                          description: Reason for system disabling, if applicable.
                        alert:
                          type: string
                          nullable: true
                          description: Alert configuration, if applicable.
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Invalid enum value`
            - `ownerId: Invalid uuid`
            - `Page size cannot exceed 100`
            - `Invalid page number. The last valid page is <number>.`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `Personal access tokens can only filter by their own ownerId`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `User with id <id> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/schedules/{scheduleId}:
    get:
      tags:
        - Schedules
      summary: Get schedule
      description: |
        Retrieve the details of a single schedule, including its delivery destinations, recurrence configuration, and current status.
      x-mint:
        content: |
          How this endpoint behaves depends on the type of API key being used:

          | API Key Type          | Behavior                                                                                                        |
          | --------------------- | --------------------------------------------------------------------------------------------------------------- |
          | Personal Access Token | The user must be able to view and manage the associated dashboard. Users who can view and schedule but do not have permissions to manage schedules must also be the schedule owner to access the schedule. |
          | Organization API Key  | Acts at org level with no additional permission check; optionally accepts `userId` to check permissions as a specific user. |
      security:
        - bearerAuth: []
      operationId: getSchedule
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            UUID of the schedule. Found in the schedule URL after `/schedules/`.

            To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
            ```
        - name: userId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: |
            Membership ID of the user whose permissions should be checked (org API keys only). When provided, the endpoint verifies the user can view the associated dashboard before returning the schedule.
      responses:
        '200':
          description: Schedule retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: Schedule UUID
                  name:
                    type: string
                    description: Schedule name
                  organizationId:
                    type: string
                    format: uuid
                    description: Organization UUID
                  entityId:
                    type: string
                    format: uuid
                    description: ID of the associated dashboard
                  ownerId:
                    type: string
                    format: uuid
                    description: User ID of the schedule owner
                  owner:
                    type: object
                    properties:
                      name:
                        type: string
                        description: Display name of the schedule owner
                  schedule:
                    type: string
                    description: Schedule's cron expression (`minute hour day-of-month month day-of-week year`)
                  timezone:
                    type: string
                    description: IANA timezone identifier (e.g., `America/New_York`)
                  fanOut:
                    type: boolean
                    description: |
                      When `true`, each recipient receives a personalized delivery. The value returned reflects the organization's [delivery personalization setting](/administration/content-permissions#delivery-personalization): if set to **Never**, this will always be `false`; if set to **Always**, this reflects the forced-personalization state. See [Personalizing email deliveries](/share/deliveries/email/personalization) for more information.
                  killJobsOnFailure:
                    type: boolean
                    description: When `true`, the job stops if any queries fail
                  filterConfig:
                    type: object
                    nullable: true
                    description: Dashboard filter values applied at delivery time
                  metadata:
                    type: object
                    nullable: true
                    description: Format options and other delivery settings
                  conditionType:
                    type: string
                    nullable: true
                    enum: [RESULTS_CHANGED, RESULTS_PRESENT, RESULTS_MISSING]
                    description: Alert condition type. `null` for standard schedules.
                  conditionQueryMapKey:
                    type: string
                    nullable: true
                    description: Query key used to evaluate the alert condition. `null` for standard schedules.
                  disabledAt:
                    type: string
                    format: date-time
                    nullable: true
                    description: Timestamp when the schedule was manually paused. `null` if active.
                  systemDisabledAt:
                    type: string
                    format: date-time
                    nullable: true
                    description: Timestamp when the system automatically disabled the schedule
                  systemDisabledReason:
                    type: string
                    nullable: true
                    enum: [missingQuery, noAccess, orphanedFilterConfigKeys]
                    description: Reason for system disabling
                  createdAt:
                    type: string
                    format: date-time
                    description: ISO 8601 creation timestamp
                  updatedAt:
                    type: string
                    format: date-time
                    description: ISO 8601 last-updated timestamp
                  destinations:
                    type: array
                    description: Delivery destination configurations
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: Destination UUID
                        format:
                          type: string
                          enum: [pdf, png, csv, xlsx, json, link_only]
                          description: Output format
                        lastStatus:
                          type: string
                          nullable: true
                          enum: [COMPLETE, ERROR, ERROR_DELIVERED, KILLED, CONDITION_UNMET]
                          description: Status of last delivery
                        lastCompletedAt:
                          type: string
                          format: date-time
                          nullable: true
                          description: ISO 8601 timestamp of last successful delivery
                        metadata:
                          type: object
                          nullable: true
                          description: Destination-specific configuration
                        recipients:
                          type: array
                          items:
                            type: object
                            properties:
                              id:
                                type: string
                                format: uuid
                              membershipId:
                                type: string
                                format: uuid
                              membership:
                                type: object
                                properties:
                                  user:
                                    type: object
                                    properties:
                                      email:
                                        type: string
                                        format: email
                                      name:
                                        type: string
                                        nullable: true
                        userGroupRecipients:
                          type: array
                          items:
                            type: object
              example:
                id: "123e4567-e89b-12d3-a456-426614174000"
                name: "Weekly Sales Report"
                organizationId: "org-uuid"
                entityId: "dashboard-uuid"
                ownerId: "user-uuid"
                owner:
                  name: "Jane Smith"
                schedule: "0 9 ? * MON *"
                timezone: "America/New_York"
                fanOut: false
                killJobsOnFailure: false
                filterConfig: null
                metadata: null
                conditionType: null
                conditionQueryMapKey: null
                disabledAt: null
                systemDisabledAt: null
                systemDisabledReason: null
                createdAt: "2025-01-15T10:00:00.000Z"
                updatedAt: "2025-01-20T08:30:00.000Z"
                destinations:
                  - id: "dest-uuid"
                    format: "pdf"
                    lastStatus: "COMPLETE"
                    lastCompletedAt: "2025-01-20T09:00:05.000Z"
                    metadata:
                      type: "email"
                    recipients:
                      - id: "recipient-uuid"
                        membershipId: "membership-uuid"
                        membership:
                          user:
                            email: "alice@example.com"
                            name: "Alice"
                    userGroupRecipients: []
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `scheduleId` is not a valid UUID
            - `userId` is not a valid UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalidScheduleId:
                  summary: Invalid schedule ID
                  value:
                    detail: "Bad Request: scheduleId: Invalid UUID"
                    status: 400
                invalidUserId:
                  summary: Invalid user ID
                  value:
                    detail: "Bad Request: userId: Invalid UUID"
                    status: 400
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            The authenticated user does not have permission to view the schedule. Possible scenarios:

            - User cannot view the associated dashboard
            - User cannot schedule the associated dashboard
            - User is not the schedule owner
            - User-scoped API key attempted to use `userId` parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                cannotViewDashboard:
                  summary: Cannot view dashboard
                  value:
                    detail: "You do not have permission to view this dashboard"
                    status: 403
                cannotScheduleDashboard:
                  summary: Cannot schedule dashboard
                  value:
                    detail: "User does not have permission to schedule this dashboard"
                    status: 403
                userScopedKeyWithUserId:
                  summary: User-scoped key cannot act as another user
                  value:
                    detail: "User-scoped API keys cannot act on behalf of other users"
                    status: 403
        '404':
          description: |
            Not Found

            Possible error messages:

            - Schedule not found (or belongs to a different organization)
            - `userId` not found in the organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                scheduleNotFound:
                  summary: Schedule not found
                  value:
                    detail: "Scheduled task with id 123e4567-e89b-12d3-a456-426614174000 does not exist"
                    status: 404
                userNotFound:
                  summary: User not found
                  value:
                    detail: "User with id foreign-membership-uuid does not exist"
                    status: 404
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      tags:
        - Schedules
      summary: Update schedule
      description: |
        Update the specified task. Changes to the schedule are applied to future runs. Currently running jobs are not affected.

        <Note>
          Only properties included in the request are updated. Omitted properties will retain their current values.
        </Note>
      security:
        - bearerAuth: []
      operationId: updateSchedule
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            The ID of the schedule. To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
      requestBody:
        content:
          application/json:
            schema:
              type: object
              description: |
                Same properties as [Create schedule](/api/schedules/create-schedule). All properties are optional - only included properties will be updated.
            example:
              name: "Weekly Sales Report"
              schedule: "0 9 * * MON"
              timezone: "America/New_York"
              format: "pdf"
              destinationType: "email"
              recipients:
                - "blob.ross@blobsrus.com"
              subject: "Weekly Sales Dashboard"
              paperFormat: "letter"
              paperOrientation: "landscape"
      responses:
        '200':
          description: Schedule updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Indicates the request was successful.
                  scheduledTaskId:
                    type: string
                    format: uuid
                    description: The ID of the updated schedule.
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: scheduleId: Invalid uuid`
            - `Invalid method`
            - `Bad Request: name: name is required, schedule: schedule is required, timezone: timezone is required, destinationType: Invalid discriminator value. Expected email | webhook | sftp | slack | s3`
            - `schedule: Schedule is an invalid cron expression`
            - `timezone: Time zone must be IANA valid.`
            - `hideTitle can only be used with PDF or PNG formats`
            - `Invalid filter keys found in schedule configuration: <filterKey>. Available dashboard filter keys are: <key1>, <key2>, ....`
            - `Print options are not supported for this format`
            - `Single column layout and table expansion options are not supported for FIT_PAGE format.`
            - `slackRecipientType is required for Slack destinations`
            - `Only one Slack channel is supported per schedule`
            - `Organization does not have a Slack credential configured`
            - `Invalid bucket name`
            - `Invalid role ARN`
            - `Invalid region`
            - `Key prefix contains invalid path traversal`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Scheduled task with id <scheduleId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Schedules
      summary: Delete schedule
      description: Delete a schedule.
      security:
        - bearerAuth: []
      operationId: deleteSchedule
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            The ID of the schedule. To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
            ```
      responses:
        '200':
          description: Schedule deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `scheduleId: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Scheduled task with id <scheduleId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/schedules/{scheduleId}/trigger:
    post:
      tags:
        - Schedules
      summary: Trigger schedule
      description: Trigger the execution of a schedule on demand, outside of its regular schedule.
      security:
        - bearerAuth: []
      operationId: triggerSchedule
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            The ID of the schedule. To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
            ```
      responses:
        '200':
          description: Schedule triggered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: scheduleId: Invalid uuid`
            - `Invalid method`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Scheduled task with id <scheduleId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '409':
          description: |
            409 Conflict

            Possible error messages:

            - `Cannot trigger schedule while another execution is in progress` - Occurs when an `EMAIL` schedule has more than 50 recipients, has `fanOut: true`, and is currently in progress
            - `Cannot trigger an inactive schedule` - Occurs when the schedule is paused (`disabledAt`) or system-disabled (`systemDisabledAt`)
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          description: |
            Internal Server Error

            Possible error messages:

            - Various error messages related to dispatch failures
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v1/schedules/{scheduleId}/pause:
    put:
      tags:
        - Schedules
      summary: Pause schedule
      description: Pause a schedule.
      security:
        - bearerAuth: []
      operationId: pauseSchedule
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            The ID of the schedule. To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
            ```
      responses:
        '200':
          description: Schedule paused successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Schedule is system disabled`
            - `Schedule is already paused`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Scheduled task with id <scheduleId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/schedules/{scheduleId}/resume:
    put:
      tags:
        - Schedules
      summary: Resume schedule
      description: Resume a schedule.
      security:
        - bearerAuth: []
      operationId: resumeSchedule
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            The ID of the schedule. To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
      responses:
        '200':
          description: Schedule resumed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Indicates the schedule has been resumed successfully.
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Schedule is system disabled`
            - `Schedule is already resumed`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Scheduled task with id <scheduleId> does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/schedules/{scheduleId}/transfer-ownership:
    put:
      tags:
        - Schedules
      summary: Transfer schedule ownership
      description: Transfer ownership of a schedule from one user to another user in the same organization. New ownership will be immediate and can't be reversed using this endpoint.
      x-mint:
        content: |
          How ownership transfer affects schedule execution depends on the type of destination the schedule uses, and for email destinations, whether the [**Personalize delivery with the recipient's user attributes**](/share/deliveries/email/personalization) for more information. is enabled:

          |                                                                     | Slack, SFTP, &amp; Webhooks | Email without personalization | Email with personalization |
          |---------------------------------------------------------------------|-----------------------------|-------------------------------|----------------------------|
          | **Future jobs are executed by...**                                  | New owner                   | New owner                     | Each recipient             |
          | **Permissions, data access, &amp; user attributes are based on...** | New owner                   | New owner                     | Each recipient             |
          | **Output is generated using...**                                    | New owner access            | New owner access              | Recipient access           |
      security:
        - bearerAuth: []
      operationId: transferScheduleOwnership
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            The ID of the schedule. To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
            ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - userId
              properties:
                userId:
                  type: string
                  format: uuid
                  description: |
                    The ID of the user to transfer schedule ownership to. Use the [List users](/api/users/list-users) endpoint to retrieve user IDs.

                    The new owner must:

                    - Be a member of the same organization
                    - Not be the current owner
                    - Have permission to view the dashboard associated with the schedule
      responses:
        '200':
          description: Ownership transferred successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `userId: Invalid uuid`
            - `userId is a required field`
            - `User already owns the schedule`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Forbidden

            Possible error messages:

            - `New owner does not have permission to view the dashboard`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Scheduled task with id <scheduleId> does not exist`
            - `New owner is not a member of the organization`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/schedules/{scheduleId}/recipients:
    get:
      tags:
        - Schedule recipients
      summary: List schedule recipients
      description: |
        Retrieve the list of recipients for the specified schedule. Successful responses will include the recipients and details about the schedule's destination type.

        To retrieve a schedule's full configuration, use the [List schedules](/api/schedules#list-schedules) endpoint.
      security:
        - bearerAuth: []
      operationId: listScheduleRecipients
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            The ID of the schedule. To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
      responses:
        '200':
          description: |
            Successful requests return a `200 OK` status and response body specific to the schedule's destination type.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ScheduleRecipientsEmail'
                  - $ref: '#/components/schemas/ScheduleRecipientsSftp'
                  - $ref: '#/components/schemas/ScheduleRecipientsSlack'
                  - $ref: '#/components/schemas/ScheduleRecipientsWebhook'
                  - $ref: '#/components/schemas/ScheduleRecipientsS3'
              examples:
                email:
                  summary: Email destination
                  value:
                    recipients:
                      - email: "blobby@blobsrus.com"
                        emailOnly: false
                        id: "3e7c4152-cdac-42ce-8749-fe3b81d5d8ab"
                        name: "blobby@blobsrus.com"
                    type: "email"
                sftp:
                  summary: SFTP destination
                  value:
                    type: "sftp"
                    address: "sftp.example.com"
                    port: 22
                    username: "username"
                slackChannel:
                  summary: Slack destination with channel recipients
                  value:
                    type: "slack"
                    recipients:
                      - recipientType: "channel"
                        slackId: "C123456789"
                slackUser:
                  summary: Slack destination with user recipients
                  value:
                    type: "slack"
                    recipients:
                      - recipientType: "users"
                        slackId: "U123456789"
                webhook:
                  summary: Webhook destination
                  value:
                    type: "webhook"
                    url: "https://example.com/webhook"
                s3:
                  summary: Amazon S3 destination
                  value:
                    type: "s3"
                    bucketName: "my-reports-bucket"
                    region: "us-east-1"
                    roleArn: "arn:aws:iam::123456789012:role/OmniS3DeliveryRole"
                    externalId: "4fbeb98a-d973-4529-9f36-0df471894ea8"
                    keyPrefix: "reports/daily/"
                    filename: "{{entityName}}-{{currentDate}}"
        '400':
          description: |
            Bad Request. Possible error messages include:

            - `Bad Request: scheduleId: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible error messages include:

            - `Scheduled task with id {scheduleId} does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/schedules/{scheduleId}/add-recipients:
    put:
      tags:
        - Schedule recipients
      summary: Add schedule recipients
      x-mint:
        content: |
      description: |
        Add one or more recipients to an existing scheduled email task. Recipients can be specified by email address or user ID.

        <Note>
          This endpoint only works with schedules that have [email destinations](/share/deliveries/email).
        </Note>
      security:
        - bearerAuth: []
      operationId: addScheduleRecipients
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            The ID of the schedule to add recipients to. To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                emails:
                  type: array
                  items:
                    type: string
                    format: email
                  description: |
                    **At least one email or user ID must be provided.** Array of email addresses to add as recipients.
                userIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: |
                    **At least one email or user ID must be provided.** Array of user UUIDs to add as recipients. Use the [List users](/api/users#list-users) and [List embed users](/api/users#list-embed-users) endpoints to retrieve user IDs.
      responses:
        '200':
          description: Recipients added successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  addedRecipientsCount:
                    type: integer
                    description: The number of recipients added to the schedule.
                  success:
                    type: boolean
                    description: Indicates whether the operation was successful.
              example:
                addedRecipientsCount: 2
                success: true
        '400':
          description: |
            Bad Request. Possible error messages include:

            - `userIds: Invalid uuid`
            - `emails: Invalid email address`
            - `Please provide either valid email addresses, valid user IDs, or both`
            - `At least one recipient must be provided`
            - `{parameter}: Array must contain at least 1 element(s)`
            - `Invalid recipient(s): The following members do not exist or do not have access to this organization: {userId}`
            - `Cannot add recipients to destination type {destinationType}`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible error messages include:

            - `Scheduled task with id {scheduleId} does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/schedules/{scheduleId}/remove-recipients:
    put:
      tags:
        - Schedule recipients
      summary: Remove schedule recipients
      description: |
        Remove one or more recipients from an existing scheduled email task. Recipients can be specified by email address or user ID.

        <Note>
          This endpoint only works with schedules that have [email destinations](/share/deliveries/email).
        </Note>
      security:
        - bearerAuth: []
      operationId: removeScheduleRecipients
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: |
            The ID of the schedule to remove recipients from. To find a schedule's ID:

            1. Navigate to the dashboard of a document.
            2. Click **File > Deliveries & Alerts**.
            3. Next to a schedule, click **Edit**.

            The schedule's ID is in the page's URL, after `/schedules/`. For example, the schedule ID in this URL is `123e4567-e89b-12d3-a456-426614174000`:

            ```markdown
            https://blobsrus.omniapp.co/dashboards/e23ebaa0/schedules/123e4567-e89b-12d3-a456-426614174000
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                emails:
                  type: array
                  items:
                    type: string
                    format: email
                  description: |
                    **At least one email or user ID must be provided.** Array of recipient email addresses to remove from the scheduled task.
                userIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: |
                    **At least one email or user ID must be provided.** Array of recipient user UUIDs to remove from the scheduled task. Use the [List users](/api/users#list-users) and [List embed users](/api/users#list-embed-users) endpoints to retrieve user IDs.
      responses:
        '200':
          description: Recipients removed successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  removedRecipientsCount:
                    type: integer
                    description: The number of recipients removed from the schedule.
                  success:
                    type: boolean
                    description: Indicates whether the operation was successful.
              example:
                removedRecipientsCount: 2
                success: true
        '400':
          description: |
            Bad Request. Possible error messages include:

            - `Bad Request: userIds: Invalid uuid`
            - `Bad Request: emails: Invalid email address`
            - `Please provide either valid email addresses, valid user IDs, or both`
            - `At least one recipient must be provided`
            - `{parameter}: Array must contain at least 1 element(s)`
            - `Invalid recipient(s): The following members do not exist or do not have access to this organization: {userId}`
            - `Cannot change recipients on a scheduled task destination of type {destinationType}`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible error messages include:

            - `Scheduled task with id {scheduleId} does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/connections/{connectionId}/schedules:
    get:
      tags:
        - Schema refresh schedules
      summary: List schema refresh schedules
      description: |
        Retrieve all schema refresh schedules configured for the specified connection. Each schedule uses a cron expression and timezone to define when the schema should be refreshed.

        <Note>
          This endpoint requires Connection Admin permissions for the connection.
        </Note>
      security:
        - bearerAuth: []
      operationId: listSchemaRefreshSchedules
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the connection
      responses:
        '200':
          description: Successfully retrieved schema refresh schedules
          content:
            application/json:
              schema:
                type: object
                properties:
                  schedules:
                    type: array
                    items:
                      $ref: '#/components/schemas/SchemaRefreshSchedule'
              example:
                schedules:
                  - scheduleId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                    connectionId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
                    schedule: "00 09 * * ? *"
                    timezone: "America/New_York"
                    description: "At 09:00 AM EDT"
                    hardRefresh: false
                    createdAt: "2025-01-15T10:30:00.000Z"
                    updatedAt: "2025-01-15T10:30:00.000Z"
                    disabledAt: null
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Insufficient permissions. **Connection Admin** permissions for the connection are required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    post:
      tags:
        - Schema refresh schedules
      summary: Create schema refresh schedule
      description: |
        Create a new schema refresh schedule for the specified connection. Multiple schedules can be created for the same connection.

        <Note>
          This endpoint requires Connection Admin permissions for the connection.
        </Note>
      security:
        - bearerAuth: []
      operationId: createSchemaRefreshSchedule
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the connection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - schedule
                - timezone
              properties:
                schedule:
                  type: string
                  description: |
                    A 6-field cron expression in AWS EventBridge format defining when the schema refresh should run, specified as:

                    ```
                    minute hour day-of-month month day-of-week year
                    ```

                    For example:

                    - `00 09 * * ? *` - Every day at 9:00 AM
                    - `00 18 ? * MON-FRI *` - Every weekday at 6:00 PM
                    - `30 08 1 * ? *` - First day of every month at 8:30 AM
                timezone:
                  type: string
                  description: |
                    An IANA timezone identifier for when the schedule should run, such as `America/New_York`, `Europe/London`, `Asia/Tokyo`
                hardRefresh:
                  type: boolean
                  default: false
                  description: Whether the scheduled refresh performs a hard refresh (removes dropped objects) or soft refresh (additive only). Defaults to false (soft refresh).
            example:
              schedule: "00 09 * * ? *"
              timezone: "America/New_York"
              hardRefresh: false
      responses:
        '201':
          description: Schema refresh schedule created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SchemaRefreshSchedule'
              example:
                scheduleId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                connectionId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
                schedule: "00 09 * * ? *"
                timezone: "America/New_York"
                description: "At 09:00 AM EDT"
                hardRefresh: false
                createdAt: "2025-01-15T10:30:00.000Z"
                updatedAt: "2025-01-15T10:30:00.000Z"
                disabledAt: null
        '400':
          description: |
            Bad Request

            Possible error messages:
            - Invalid cron expression
            - Invalid timezone
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Insufficient permissions. **Connection Admin** permissions for the connection are required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/connections/{connectionId}/schedules/{scheduleId}:
    get:
      tags:
        - Schema refresh schedules
      summary: Get schema refresh schedule
      description: |
        Retrieve the details of a connection's specific schema refresh schedule.

        <Note>
          This endpoint requires Connection Admin permissions for the connection.
        </Note>
      security:
        - bearerAuth: []
      operationId: getSchemaRefreshSchedule
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the connection
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the schema refresh schedule
      responses:
        '200':
          description: Successfully retrieved schema refresh schedule
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SchemaRefreshSchedule'
              example:
                scheduleId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                connectionId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
                schedule: "00 09 * * ? *"
                timezone: "America/New_York"
                description: "At 09:00 AM EDT"
                hardRefresh: false
                createdAt: "2025-01-15T10:30:00.000Z"
                updatedAt: "2025-01-15T10:30:00.000Z"
                disabledAt: null
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Insufficient permissions. **Connection Admin** permissions for the connection are required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - Schedule not found
            - Schedule belongs to a different connection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      tags:
        - Schema refresh schedules
      summary: Update schema refresh schedule
      description: |
        Update the schedule and/or timezone for an existing schema refresh schedule.

        <Note>
          This endpoint requires Connection Admin permissions for the connection.
        </Note>
      security:
        - bearerAuth: []
      operationId: updateSchemaRefreshSchedule
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the connection
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the schema refresh schedule
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - schedule
                - timezone
              properties:
                schedule:
                  type: string
                  description: |
                    A 6-field cron expression in AWS EventBridge format defining when the schema refresh should run, specified as:

                    ```
                    minute hour day-of-month month day-of-week year
                    ```

                    For example:

                    - `00 09 * * ? *` - Every day at 9:00 AM
                    - `00 18 ? * MON-FRI *` - Every weekday at 6:00 PM
                    - `30 08 1 * ? *` - First day of every month at 8:30 AM
                timezone:
                  type: string
                  description: |
                    An IANA timezone identifier for when the schedule should run.
                hardRefresh:
                  type: boolean
                  default: false
                  description: Whether the scheduled refresh performs a hard refresh (removes dropped objects) or soft refresh (additive only). Defaults to false (soft refresh).
            example:
              schedule: "00 18 ? * * *"
              timezone: "Europe/London"
              hardRefresh: false
      responses:
        '200':
          description: Schema refresh schedule updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SchemaRefreshSchedule'
              example:
                scheduleId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                connectionId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
                schedule: "00 18 ? * * *"
                timezone: "Europe/London"
                description: "At 06:00 PM GMT"
                hardRefresh: false
                createdAt: "2025-01-15T10:30:00.000Z"
                updatedAt: "2025-01-15T11:00:00.000Z"
                disabledAt: null
        '400':
          description: |
            Bad Request

            Possible error messages:
            - Invalid cron expression
            - Invalid timezone
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Insufficient permissions. **Connection Admin** permissions for the connection are required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - Schedule not found
            - Connection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Schema refresh schedules
      summary: Delete schema refresh schedule
      description: |
        Delete a schema refresh schedule. This does not affect other schedules for the same connection.

         <Note>
          This endpoint requires Connection Admin permissions for the connection.
        </Note>
      security:
        - bearerAuth: []
      operationId: deleteSchemaRefreshSchedule
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the connection
        - name: scheduleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the schema refresh schedule
      responses:
        '200':
          description: Schema refresh schedule deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                success: true
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: |
            Insufficient permissions. **Connection Admin** permissions for the connection are required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:
            - Schedule not found
            - Connection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/view/{viewName}:
    delete:
      tags:
        - Models
      summary: Delete view
      description: |
        Delete or ignores a view in a model. Use the `mode` parameter to specify the behavior of this endpoint.
      security:
        - bearerAuth: []
      operationId: deleteView
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the model.
        - name: viewName
          in: path
          required: true
          schema:
            type: string
          description: The name of the view to delete or ignore.
        - name: branchId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Optional branch ID
        - name: mode
          in: query
          required: false
          schema:
            type: string
            enum: [COMBINED, MERGED, EXTENSION]
            default: EXTENSION
          description: |
            Controls how the view is deleted or hidden:

            - `COMBINED` — **Default**. Marks the view as ignored (`ignored: true`) if it exists in the parent model, otherwise hard-deletes
            - `EXTENSION` — Hard-deletes the view from the extension layer
            - `MERGED` — Marks the view as `shallowIgnored: true`, hiding it only from the immediate parent layer
      responses:
        '200':
          description: View deleted or ignored successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          description: Missing or invalid authentication.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '400':
          description: |
            Bad Request

            Possible error messages:

            - `Bad Request: Invalid modelId format`
            - `Bad Request: Invalid mode parameter`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model not found`
            - `View not found`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/models/{modelId}/suggestions/{suggestionId}/ignore:
    post:
      tags:
        - AI Model Suggestions
      summary: Dismiss a suggestion
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

        Dismiss a suggestion, removing it from the active list. Dismissed suggestions can be restored using the [restore endpoint](/api/ai-model-suggestions/restore-a-dismissed-suggestion).
      security:
        - bearerAuth: []
      operationId: modelSuggestionsIgnore
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
          description: The unique identifier of the model.
        - name: suggestionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
            example: b2c3d4e5-f6a7-8901-bcde-f12345678901
          description: The unique identifier of the suggestion to dismiss.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  maxLength: 4000
                  description: Optional free-text reason for dismissing the suggestion.
                  example: Already covered by an existing field description.
              additionalProperties: false
      responses:
        '200':
          description: Suggestion dismissed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Indicates the request was successful
                    example: true
        '400':
          description: Invalid body or malformed ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid authentication.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: |
            Feature not enabled, AI disabled, model is not a shared model, or caller lacks Organization Admin permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model not found`
            - `Suggestion not found`
            - Cross-organization suggestion access denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/models/{modelId}/suggestions/{suggestionId}/restore:
    post:
      tags:
        - AI Model Suggestions
      summary: Restore a dismissed suggestion
      description: |
        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>

        Restore a previously dismissed suggestion back to the active list.
      security:
        - bearerAuth: []
      operationId: modelSuggestionsRestore
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
          description: UUID of the shared model the suggestion belongs to.
        - name: suggestionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
            example: b2c3d4e5-f6a7-8901-bcde-f12345678901
          description: The unique identifier of the suggestion to restore.
      responses:
        '200':
          description: Suggestion restored successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Indicates the request was successful
                    example: true
        '400':
          description: Malformed ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
        '401':
          description: Missing or invalid authentication.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: |
            Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found

            Possible error messages:

            - `Model not found`
            - `Suggestion not found`
            - Cross-organization suggestion access denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/models/{modelId}/topic/{topicName}:
    get:
      tags:
        - Topics
      summary: Retrieve a topic
      description: Retrieve a topic in a model by name.
      security:
        - bearerAuth: []
      operationId: getTopic
      parameters:
        - name: modelId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the model that contains the topic.
        - name: topicName
          in: path
          required: true
          schema:
            type: string
          description: The name of the topic.
      responses:
        '200':
          description: ""
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Indicates the request was successful
                  topic:
                    type: object
                    description: Details about the topic. The properties included in the response will typically mirror the [IDE parameters](/modeling/topics/parameters) for topic files.
                    properties:
                      name:
                        type: string
                        description: The name of the topic
                      base_view_name:
                        type: string
                        description: The name of the base view for the topic
                      label:
                        type: string
                        description: The display label for the topic
                      default_filters:
                        type: object
                        description: Default filters applied to the topic
                      join_via_map:
                        type: object
                        description: Join path mappings for the topic. Keys are view names, values are arrays of view names representing the join path.
                        additionalProperties:
                          type: array
                          items:
                            type: string
                      join_via_map_key_order:
                        type: array
                        items:
                          type: string
                        description: Order of join path keys
                      ignored_props:
                        type: array
                        items:
                          type: string
                        description: Properties to ignore
                      always_where_filters:
                        type: object
                        description: |
                          Filters that applied to all rows in a query. In the model IDE, this is the [`always_where_filters`](/modeling/topics/parameters/always-where-filters) parameter.
                      extension_model_id:
                        type: string
                        format: uuid
                        description: The ID of the extension model
                      has_frozen_join_via_map:
                        type: boolean
                        description: Whether the join via map is frozen
                      relationships:
                        type: array
                        description: Relationships defined in the topic. The properties in each object will typically mirror the [IDE parameters](/modeling/relationships/parameters) used in a model's `relationships` file.
                        items:
                          type: object
                          properties:
                            left_view_name:
                              type: string
                              description: The left view in the relationship
                            right_view_name:
                              type: string
                              description: The right view in the relationship
                            join_type:
                              type: string
                              description: The type of join (e.g., ALWAYS_LEFT)
                            on:
                              type: object
                              description: The join condition expression
                              properties:
                                type:
                                  type: string
                                operator:
                                  type: string
                                operands:
                                  type: array
                                  items:
                                    type: object
                                distinct:
                                  type: boolean
                            sql:
                              type: string
                              description: The SQL expression for the join
                            id:
                              type: string
                              description: Unique identifier for the relationship
                            type:
                              type: string
                              description: The relationship type (e.g., `ONE_TO_MANY`)
                            ignored:
                              type: boolean
                              description: Whether the relationship is ignored
                            bidirectional:
                              type: boolean
                              description: Whether the relationship is bidirectional
                            original_on_sql:
                              type: string
                              description: The original SQL for the join condition
                            yaml_path_prefix:
                              type: string
                              description: The YAML path prefix for the relationship
                      ide_file_name:
                        type: string
                        description: The file name for the topic in the IDE
                      views:
                        type: array
                        description: A list of views included in the topic. The properties in each object will mirror the [IDE parameters](/modeling/views/parameters) available for views.
                        items:
                          type: object
                          properties:
                            name:
                              type: string
                              description: The name of the view
                            label:
                              type: string
                              description: The display label for the view
                            table_name:
                              type: string
                              description: The underlying database table name
                            schema:
                              type: string
                              description: The database schema
                            schema_label:
                              type: string
                              description: The display label for the schema
                            extension_model_id:
                              type: string
                              format: uuid
                            ide_file_name:
                              type: string
                              description: The name of the IDE file for the view
                            yaml_path:
                              type: string
                              description: The YAML file path for the view
                            filter_only_fields:
                              type: array
                              items:
                                type: string
                            is_pseudo_display_view:
                              type: boolean
                            primary_key:
                              type: array
                              description: Primary key fields for the view
                              items:
                                type: object
                                properties:
                                  type:
                                    type: string
                                  field_name:
                                    type: string
                            dimensions:
                              type: array
                              description: A list of dimensions defined in the view. The properties in each object will mirror the [IDE parameters](/modeling/dimensions/parameters) for `dimensions.`
                              items:
                                type: object
                                properties:
                                  field_name:
                                    type: string
                                  view_name:
                                    type: string
                                  data_type:
                                    type: string
                                    description: Data type (e.g., STRING, NUMBER, TIMESTAMP)
                                  format:
                                    type: string
                                    description: Display format (e.g., ID)
                                  label:
                                    type: string
                                    description: Display label for the dimension
                                  description:
                                    type: string
                                    description: Description of the dimension
                                  view_label:
                                    type: string
                                  extension_model_id:
                                    type: string
                                    format: uuid
                                  is_dimension:
                                    type: boolean
                                  fully_qualified_name:
                                    type: string
                                  time_frames:
                                    type: array
                                    description: Available time frames for date dimensions
                                    items:
                                      type: string
                                  date_type:
                                    type: string
                                    description: Date granularity (e.g., DATE, WEEK, MONTH, QUARTER, YEAR)
                                  drill_fields:
                                    type: array
                                    description: Fields available for drilling
                                    items:
                                      type: string
                                  parent_field:
                                    type: string
                                    description: Parent field name for grouped dimensions
                                  parent_label:
                                    type: string
                                    description: Parent field label
                                  group_label:
                                    type: string
                                    description: Group label for the dimension
                                  is_group_parent_field:
                                    type: boolean
                                    description: Whether this is a parent field in a group
                                  data_type_metadata:
                                    type: object
                                    description: Additional metadata about the data type
                                    properties:
                                      is_date_type:
                                        type: boolean
                                  ai_context:
                                    type: string
                                    description: Context hints for AI usage
                            measures:
                              type: array
                              description: Measures defined in the view. The properties in each object will mirror the [IDE parameters](/modeling/measures/parameters) for `measures.`
                              items:
                                type: object
                                properties:
                                  type:
                                    type: string
                                    description: Measure type (e.g., aggregation)
                                  field_name:
                                    type: string
                                  view_name:
                                    type: string
                                  aggregate_type:
                                    type: string
                                    description: Aggregation type (e.g., COUNT, SUM, AVG)
                                  data_type:
                                    type: string
                                  label:
                                    type: string
                                  format:
                                    type: string
                                  view_label:
                                    type: string
                                  ignored:
                                    type: boolean
                                    description: Whether the measure is ignored
                                  display_sql:
                                    type: string
                                    description: SQL displayed in the UI
                                  dialect_sql:
                                    type: string
                                    description: SQL specific to the database dialect
                                  fully_qualified_name:
                                    type: string
                                  filters:
                                    type: object
                                    description: Filters applied to the measure
              example:
                success: true
                topic:
                  name: "Customers"
                  base_view_name: "main__customers"
                  label: "Customers"
                  default_filters: {}
                  join_via_map:
                    main__orders: []
                    main__order_items:
                      - "main__orders"
                  join_via_map_key_order:
                    - "main__orders"
                    - "main__order_items"
                  ignored_props: []
                  always_where_filters: {}
                  extension_model_id: "5da4e30e-45e2-4693-b87d-51c39d69963f"
                  has_frozen_join_via_map: true
                  relationships:
                    - left_view_name: "main__orders"
                      right_view_name: "main__customers"
                      join_type: "ALWAYS_LEFT"
                      sql: "${main__orders.customer_id} = ${main__customers.id}"
                      id: "main__orders_main__customers"
                      type: "ASSUMED_MANY_TO_ONE"
                      ignored: false
                      bidirectional: false
                  ide_file_name: "Customers.topic"
                  views:
                    - name: "main__customers"
                      label: "Customers"
                      table_name: "customers"
                      schema: "main"
                      schema_label: "Main"
                      extension_model_id: "5da4e30e-45e2-4693-b87d-51c39d69963f"
                      ide_file_name: "main/customers.view"
                      yaml_path: "main__customers.view"
                      filter_only_fields: []
                      is_pseudo_display_view: false
                      primary_key:
                        - type: "field"
                          field_name: "main__customers.id"
                      dimensions:
                        - field_name: "country"
                          view_name: "main__customers"
                          data_type: "STRING"
                          description: "User's country"
                          view_label: "Customers"
                          is_dimension: true
                          fully_qualified_name: "main__customers.country"
                        - field_name: "created_date"
                          view_name: "main__customers"
                          data_type: "TIMESTAMP"
                          time_frames:
                            - null
                            - "DATE"
                            - "WEEK"
                            - "MONTH"
                            - "QUARTER"
                            - "YEAR"
                          date_type: "DATE"
                          description: "Date of user creation"
                          parent_field: "created_date"
                          parent_label: "Created Date"
                          view_label: "Customers"
                          group_label: "Created Date"
                          is_dimension: true
                          is_group_parent_field: true
                          fully_qualified_name: "main__customers.created_date"
                      measures:
                        - type: "aggregation"
                          field_name: "count"
                          view_name: "main__customers"
                          aggregate_type: "COUNT"
                          data_type: "NUMBER"
                          ignored: false
                          label: "Customers Count"
                          format: "NUMBER_0"
                          view_label: "Customers"
                          display_sql: "COUNT(*)"
                          dialect_sql: "COUNT(*)"
                          fully_qualified_name: "main__customers.count"
        '400':
          description: |
            Bad Request. Possible causes:

            - Invalid model UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Model with id <modelId> does not exist"
                status: 400
        '404':
          description: |
            Not Found. Possible causes:

            - Model not found
            - Topic not found or access not permitted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                modelNotFound:
                  summary: Model not found
                  value:
                    detail: "Model with id <modelId> does not exist"
                    status: 404
                topicNotFound:
                  summary: Topic not found
                  value:
                    detail: "No such topic \"<topicName>\" or access not permitted"
                    status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/uploads:
    get:
      tags:
        - Uploads
      summary: List uploads
      description: |
        List all uploads ([CSV files and spreadsheets](/analyze-explore/data-input-csvs)) in the organization with metadata and optional filtering.

        This endpoint requires **Organization Admin** permissions.
      security:
        - bearerAuth: []
      operationId: listUploads
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum:
              - csv
              - spreadsheet
            default: csv
          description: Filter by upload type.
        - name: connectionId
          in: query
          schema:
            type: string
            format: uuid
          description: Filter by connection ID.
        - name: modelId
          in: query
          schema:
            type: string
            format: uuid
          description: Filter by model ID. Shared models return non-private connection uploads; workbook models return their own uploads.
        - name: searchTerm
          in: query
          schema:
            type: string
          description: Search term to filter by file name (case-insensitive).
        - name: pageSize
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of items to return.
        - name: cursor
          in: query
          schema:
            type: string
          description: Cursor for pagination (from previous response).
        - name: sortField
          in: query
          schema:
            type: string
            enum:
              - createdAt
              - fileName
              - updatedAt
            default: updatedAt
          description: Field to sort by.
        - name: sortDirection
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
          description: Sort direction.
      responses:
        '200':
          description: Uploads retrieved successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: Unique ID for the upload.
                        file_name:
                          type: string
                          description: Original file name.
                        view_name:
                          type: string
                          description: View name in the model.
                        connection_id:
                          type: string
                          format: uuid
                          description: ID of the connection associated with the upload.
                        in_db_as_table_name:
                          type: string
                          nullable: true
                          description: |
                            **Requires that the connection have a defined table upload schema.** The name of the database table associated with the upload.
                        model_id:
                          type: string
                          format: uuid
                          nullable: true
                          description: ID of the model the upload is associated with. Inferred from connection's shared model if not explicitly set via query parameter.
                        size_bytes:
                          type: integer
                          nullable: true
                          description: File size in bytes.
                        created_at:
                          type: string
                          format: date-time
                          description: ISO 8601 timestamp of when the upload was created.
                        updated_at:
                          type: string
                          format: date-time
                          description: ISO 8601 timestamp of when the upload was last updated.
                        uploaded_by_user:
                          type: object
                          nullable: true
                          description: User who uploaded the file. null if unknown.
                          properties:
                            id:
                              type: string
                              format: uuid
                              description: Membership ID.
                            name:
                              type: string
                              description: User display name.
              example:
                pageInfo:
                  hasNextPage: false
                  nextCursor: null
                  pageSize: 20
                  totalRecords: 2
                records:
                  - id: 550e8400-e29b-41d4-a716-446655440000
                    file_name: users.csv
                    view_name: users
                    connection_id: 660e8400-e29b-41d4-a716-446655440001
                    in_db_as_table_name: omni_upload_t550e8400
                    model_id: 880e8400-e29b-41d4-a716-446655440003
                    size_bytes: 1024
                    created_at: "2025-01-15T10:00:00Z"
                    updated_at: "2025-01-15T10:00:00Z"
                    uploaded_by_user:
                      id: 770e8400-e29b-41d4-a716-446655440002
                      name: John Doe
        '400':
          description: |
            Bad Request. Possible error messages include:

            - `connectionId: Invalid uuid`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Bad Request: connectionId: Invalid uuid"
                status: 400
        '401':
          description: Missing or invalid authentication.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions. Requires `MANAGE_UPLOADS` permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: You do not have permission to perform this action
                status: 403
        '404':
          description: Model not found (invalid `modelId`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    post:
      tags:
        - Uploads
      summary: Upload a CSV file
      description: |
        <Note>
          To use this endpoint:
          
          - The **Upload data** setting in **Settings > Content permissions** must enabled by an **Organization Admin**
          - The authenticating user must have **Restricted Querier** permissions or higher on the model the file will be uploaded to
        </Note>

        Upload a CSV file to create a new [data input table](/analyze-explore/data-input-csvs). The file is parsed, converted to Arrow format, uploaded to storage, written to the connection's table upload (scratch) schema, and a view is created in the specified model.

        Uploaded files:

        - Must be a CSV file with `.csv` extension
        - Can have a maximum of 500,000 rows. Files will be truncated if this limit is exceeded.

        The returned `id` can be passed as `fileUploadId` on a `csv` or `spreadsheet` queryPresentation tab via the [Documents v2 draft-patch endpoints](/api/documents-v2/create-draft-and-patch-document#binding-an-upload-as-a-source-tab) to bind the upload as a document Source tab.
      security:
        - bearerAuth: []
      operationId: uploadCsvFile
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
                - modelId
              properties:
                file:
                  type: string
                  format: binary
                  description: The CSV file to upload, which must have a `.csv` extension
                modelId:
                  type: string
                  format: uuid
                  description: UUID of the model to create the view in
                  example: 880e8400-e29b-41d4-a716-446655440003
                branchId:
                  type: string
                  format: uuid
                  description: UUID of the branch to create the view in. Mutually exclusive with `branchName`.
                  example: 990e8400-e29b-41d4-a716-446655440004
                branchName:
                  type: string
                  description: Name of the branch to create the view in. Mutually exclusive with `branchId`.
                  example: my-branch
                viewName:
                  type: string
                  description: Override the view name. Defaults to sanitized file name.
                  example: custom_view_name
            encoding:
              file:
                contentType: text/csv
      responses:
        '201':
          description: CSV file uploaded successfully and view created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: Unique identifier for the upload
                  fileName:
                    type: string
                    description: Original file name
                  viewName:
                    type: string
                    description: Name of the view created
                  modelId:
                    type: string
                    format: uuid
                    description: ID of the model the view was created in
                  inDbAsTableName:
                    type: string
                    description: Database table name in the scratch schema
                  rowCount:
                    type: integer
                    description: Number of rows in the uploaded file
                  truncated:
                    type: boolean
                    description: Whether the file was truncated due to row limit (500,000 rows)
                  viewCreated:
                    type: boolean
                    description: Whether a view was created in the model
              example:
                id: 550e8400-e29b-41d4-a716-446655440000
                fileName: users.csv
                viewName: users
                modelId: 880e8400-e29b-41d4-a716-446655440003
                inDbAsTableName: omni_upload_t550e8400
                rowCount: 150
                truncated: false
                viewCreated: true
        '400':
          description: |
            Bad Request. Possible error messages include:

            - Missing required fields (`file` or `modelId`)
            - Invalid file type (not a CSV file)
            - CSV parsing failed
            - Invalid UUID format
            - Both `branchId` and `branchName` provided (mutually exclusive)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Bad Request: file must have .csv extension"
                status: 400
        '401':
          description: Missing or invalid authentication.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Unauthorized"
                status: 401
        '403':
          description: Insufficient permissions. Authenticating user must have **Restricted Querier** permissions or higher on the model.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "You do not have permission to perform this action"
                status: 403
        '404':
          description: |
            Not Found. Possible error messages include:

            - Model not found (invalid `modelId`)
            - Branch not found (invalid `branchId` or `branchName`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Model not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/uploads/{uploadId}:
    delete:
      tags:
        - Uploads
      summary: Delete an upload
      description: |
        Delete a CSV upload by its ID. This removes the file from storage and marks the record as deleted.

        <Note>
          This endpoint requires **Organization Admin** permissions.
        </Note>
      security:
        - bearerAuth: []
      operationId: deleteUpload
      parameters:
        - name: uploadId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the upload to delete
      responses:
        '200':
          description: Upload deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                success: true
        '400':
          description: Invalid upload ID format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Bad Request: uploadId: Invalid uuid"
                status: 400
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions. Requires **Organization Admin** permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "You do not have permission to perform this action"
                status: 403
        '404':
          description: Upload not found or already deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Upload not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      tags:
        - Uploads
      summary: Replace upload data
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>
        
        Replace the data in an existing CSV upload. 
        
        The view's [`uploaded_table_name`](/modeling/views/parameters/uploaded-table-name) property and references to it (returned as `refreshedModelIds`) will automatically update, but the upload's ID will remain unchanged. This means that any documents, queries, views, etc. using the upload will be re-pointed to the new data. The previous data's artifacts are left untouched.
      x-mint:
        content: |
          <Warning>
            Renaming or removing columns may break content (fields, calculations, filters) referencing the old columns.
          </Warning>
      security:
        - bearerAuth: []
      operationId: replaceUploadData
      parameters:
        - name: uploadId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the upload
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  description: The replacement CSV file (must have `.csv` extension). Fully replaces the existing data. Column renames/removals may break content referencing the old columns.
                  format: binary
              required:
                - file
              additionalProperties: false
            encoding:
              file:
                contentType: text/csv
      responses:
        '200':
          description: Upload data replaced successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - fileName
                  - viewName
                  - inDbAsTableName
                  - refreshedModelIds
                  - rowCount
                  - truncated
                properties:
                  id:
                    type: string
                    format: uuid
                    description: The upload ID. This will not change after uploading new data.
                    example: "550e8400-e29b-41d4-a716-446655440000"
                  fileName:
                    type: string
                    description: File name of the replacement upload
                    example: users_updated.csv
                  viewName:
                    type: string
                    description: View name. This will not change after uploading new data.
                    example: users
                  inDbAsTableName:
                    type: string
                    description: Database table name in the table upload (scratch) schema. This will be empty if the underlying connection doesn't have a table upload schema configured.
                    example: omni_upload_t7c1e22ab
                  refreshedModelIds:
                    type: array
                    items:
                      type: string
                      format: uuid
                    description: Models whose view of this upload was re-pointed at the new data, including the upload's workbook model, or the connection's shared models holding a view backed by this upload.
                    example: ["880e8400-e29b-41d4-a716-446655440003"]
                  rowCount:
                    type: integer
                    description: Number of rows in the replacement file
                    example: 152
                  truncated:
                    type: boolean
                    description: Whether the file was truncated due to row limit
                    example: false
              example:
                id: "550e8400-e29b-41d4-a716-446655440000"
                fileName: "users_updated.csv"
                viewName: "users"
                inDbAsTableName: "omni_upload_t7c1e22ab"
                refreshedModelIds: ["880e8400-e29b-41d4-a716-446655440003"]
                rowCount: 152
                truncated: false
        '400':
          description: Invalid request (missing file, non-CSV file, parse failure, or attempting to replace a non-CSV upload)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError400'
              examples:
                invalidId:
                  summary: Invalid upload ID
                  value:
                    detail: "Bad Request: uploadId: Invalid UUID"
                    status: 400
                nonCsv:
                  summary: Non-CSV file
                  value:
                    detail: "File must be a CSV file"
                    status: 400
                nonCsvUpload:
                  summary: Spreadsheet-backed upload
                  value:
                    detail: "Only CSV uploads can be replaced through this endpoint"
                    status: 400
        '401':
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Insufficient permissions. Requires **MANAGE_UPLOADS** permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
              example:
                detail: "You do not have permission to perform this action"
                status: 403
        '404':
          description: Upload not found or already deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
              example:
                detail: "Upload not found"
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/users/email-only:
    get:
      tags:
        - Users
      summary: List email-only users
      description: List email-only users and their user attributes. Supports pagination and filtering.
      security:
        - bearerAuth: []
      operationId: listEmailOnlyUsers
      parameters:
        - name: email
          in: query
          schema:
            type: string
          description: Filter users by email (partial match, case-insensitive)
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor from previous response
        - name: pageSize
          in: query
          schema:
            type: integer
            default: 20
            maximum: 20
          description: Results per page
        - name: sortDirection
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: desc
          description: |
            Sort order:
            - `asc` - Ascending order (A-Z)
            - `desc` - Descending order (Z-A)
      responses:
        '200':
          description: Paginated list of email-only users
          content:
            application/json:
              schema:
                type: object
                properties:
                  pageInfo:
                    $ref: '#/components/schemas/PageInfo'
                  records:
                    type: array
                    items:
                      type: object
                      properties:
                        email:
                          type: string
                          format: email
                          description: User's email address
                        user_id:
                          type: string
                          format: uuid
                          description: User's membership ID
                        user_attributes:
                          type: object
                          description: Key-value map of user attributes, including system attributes like `omni_user_email`, `omni_user_name`, `omni_user_id`, `omni_is_org_admin`, plus any custom attributes.
                          additionalProperties:
                            type: string
              example:
                pageInfo:
                  hasNextPage: true
                  nextCursor: "bob@example.com"
                  pageSize: 20
                  totalRecords: 50
                records:
                  - email: "alice@example.com"
                    user_id: "550e8400-e29b-41d4-a716-446655440000"
                    user_attributes:
                      omni_user_email: "alice@example.com"
                      omni_user_name: "alice@example.com"
                      omni_user_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                      omni_is_org_admin: "false"
                      omni_user_timezone: "America/New_York"
                      department: "Engineering"
                  - email: "bob@example.com"
                    user_id: "550e8400-e29b-41d4-a716-446655440001"
                    user_attributes:
                      omni_user_email: "bob@example.com"
                      omni_user_name: "bob@example.com"
                      omni_user_id: "b2c3d4e5-f6a7-8901-bcde-f23456789012"
                      omni_is_org_admin: "false"
                      omni_user_timezone: "Europe/London"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: Unauthorized - Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      tags:
        - Schedule recipients
      summary: Manage email-only user
      description: |
        Create or update an [email-only user](/share/deliveries/email). An email-only user is a recipient of a delivery that isn't associated with an Omni account.

        If Omni matches the provided email to an existing email-only user, the user will be updated according to the `userAttributes` in the request body.

        <Tip>
          Need to include multiple email-only users? Use the [Create or update multiple email-only users](/api/schedule-recipients/bulk-manage-email-only-users) endpoint.
        </Tip>
      security:
        - orgApiKey: []
      operationId: manageEmailOnlyUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
                  description: The user's email address.
                userAttributes:
                  type: object
                  description: |
                    An object defining the user's [user attributes](/administration/users/attributes). Attributes are represented as key/value pairs, where the keys map to the IDs of user attributes (the **Reference** column in the **User attributes** page) defined in Omni.

                    When providing user attributes, note that:

                    - `omni_user_timezone` is the only supported system attribute
                    - **Values must match the user attribute's specified type**. For example, `number` attribute values must be numbers such as `1`, `10`, etc.
                    - **Multi-value attributes should be provided using arrays**. For example: `["US","EU"]` or `[1, 10]`

                    To unset attributes:
                    
                    - `null` values
                    - `""` - Empty strings for string attributes
                    - `[]` - Empty arrays for multi-value attributes
            examples:
              basic:
                summary: Basic request
                value:
                  email: "iamagoodblob@blobsrus.co"
              withAttributes:
                summary: Set user attributes
                value:
                  email: "iamagoodblob@blobsrus.co"
                  userAttributes:
                    region: ["US", "EU"]
                    omni_user_timezone: "America/New_York"
                    is_admin: 0
                    is_sales_team: 1
              unsetAttributes:
                summary: Unset user attributes
                value:
                  email: "iamagoodblob@blobsrus.co"
                  userAttributes:
                    is_admin: null
                    is_sales_team: ""
                    region: []
      responses:
        '200':
          description: Email-only user created or updated successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  email:
                    type: string
                    format: email
                    description: The email address of the created or updated user.
                  userId:
                    type: string
                    format: uuid
                    description: The unique identifier of the email-only user.
              example:
                email: "iamagoodblob@blobsrus.co"
                userId: "9e8719d9-276a-4964-9395-a493189a247c"
        '400':
          description: |
            Bad Request. Possible error messages include:

            - `Invalid JSON`
            - `email: email is required`
            - `email: Invalid email address`
            - `The provided user attributes: "<attribute>" do not match the names of existing user attributes.`
            - `User Attribute <attribute> is type number, but passed-in value <value> is not a number.`
            - `The timezone <invalidTimezone> is not supported. Please see the connection page or [user profile page](/administration/users/your-account) for the list of valid timezones. Use e.g. "America/New_York" instead of "EST"`
            - `User Attribute <attribute> has multiple values enabled, but the passed-in value is not an array. If passing a single value for a user attribute with multiple values enabled, please wrap the value in an array [x].`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/users/email-only/bulk:
    post:
      tags:
        - Schedule recipients
      summary: Bulk manage email-only users
      description: |
        Create or update up to 20 [email-only users](/share/deliveries/email). An email-only user is a recipient of a delivery that isn't associated with an Omni account.

        If Omni matches a provided email to an existing email-only user, the user will be updated according to the `userAttributes` in the request body.
      security:
        - orgApiKey: []
      operationId: bulkManageEmailOnlyUsers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - users
              properties:
                users:
                  type: array
                  maxItems: 20
                  description: |
                    An array of user objects, which includes `email` and `userAttributes` properties. Up to 20 users can be included in each request.
                  items:
                    type: object
                    required:
                      - email
                    properties:
                      email:
                        type: string
                        format: email
                        description: The user's email address.
                      userAttributes:
                        type: object
                        description: |
                          An object defining the user's [user attributes](/administration/users/attributes). Attributes are represented as key/value pairs, where the keys map to the IDs of user attributes (the **Reference** column in the **User attributes** page) defined in Omni.

                          When providing user attributes, note that:

                          - `omni_user_timezone` is the only supported system attribute
                          - **Values must match the user attribute's specified type**. For example, `number` attribute values must be numbers such as `1`, `10`, etc.
                          - **Multi-value attributes should be provided using arrays**. For example: `["US","EU"]` or `[1, 10]`

                          To unset attributes:
                          
                          - `null` values
                          - `""` - Empty strings for string attributes
                          - `[]` - Empty arrays for multi-value attributes
            examples:
              basic:
                summary: Basic request
                value:
                  users:
                    - email: "iamagoodblob@blobsrus.co"
                    - email: "blobmanager@blobsrus.co"
              withAttributes:
                summary: Set user attributes
                value:
                  users:
                    - email: "iamagoodblob@blobsrus.co"
                      userAttributes:
                        region: ["US", "EU"]
                        omni_user_timezone: "America/New_York"
                        is_admin: 0
                        is_sales_team: 1
                    - email: "blobmanager@blobsrus.co"
                      userAttributes:
                        region: ["US"]
                        omni_user_timezone: "America/New_York"
                        is_admin: 1
                        is_sales_team: 0
              unsetAttributes:
                summary: Unset user attributes
                value:
                  users:
                    - email: "iamagoodblob@blobsrus.co"
                      userAttributes:
                        region: []
                        omni_user_timezone: ""
                        is_admin: null
      responses:
        '200':
          description: Email-only users created or updated successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    description: An array of created or updated email-only users.
                    items:
                      type: object
                      properties:
                        email:
                          type: string
                          format: email
                          description: The email address of the created or updated user.
                        userId:
                          type: string
                          format: uuid
                          description: The unique identifier of the email-only user.
              example:
                results:
                  - email: "iamagoodblob@blobsrus.co"
                    userId: "1e23dadc-961b-4a21-b36b-17168130fc3f"
                  - email: "blobmanager@blobsrus.co"
                    userId: "e9d19f71-0b59-4f8e-8343-a75d30576d28"
        '400':
          description: |
            Bad Request. Possible error messages include:

            - `Invalid JSON`
            - `users: Maximum of 20 users can be processed in a single request`
            - `users: users is required`
            - `email: email is required`
            - `email: Invalid email address`
            - `The provided user attributes: "<attribute>" do not match the names of existing user attributes.`
            - `User Attribute <attribute> is type number, but passed-in value <value> is not a number.`
            - `The timezone <invalidTimezone> is not supported. Please see the connection page or [user profile page](/administration/users/your-account) for the list of valid timezones. Use e.g. "America/New_York" instead of "EST"`
            - `User Attribute <attribute> has multiple values enabled, but the passed-in value is not an array. If passing a single value for a user attribute with multiple values enabled, please wrap the value in an array [x].`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/user-attributes:
    get:
      tags:
        - User attributes
      summary: List user attributes
      description: |
        <Note>
          **Organization Admin** permissions are required to use this endpoint.
        </Note>

        Retrieve all [user attribute](/administration/users/attributes) definitions in your organization, including both custom-defined and system-defined attributes.

        User attributes enable row-level security filtering, personalize dashboard content, and configure per-user database credentials. This endpoint is useful for embed integrations to discover available attributes for setting values in SSO sessions.
      security:
        - bearerAuth: []
      operationId: listUserAttributes
      responses:
        '200':
          description: List of user attribute definitions
          content:
            application/json:
              schema:
                type: object
                properties:
                  records:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: |
                            The unique identifier of the user attribute. This will be an empty string for system attributes.
                          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                        name:
                          type: string
                          description: The attribute reference name used in modeling and API calls
                          example: "region"
                        label:
                          type: string
                          description: The human-readable display name for the attribute
                          example: "Region"
                        type:
                          type: string
                          enum: [String, Number]
                          description: The data type of the attribute
                          example: "String"
                        multiple_values:
                          type: boolean
                          description: Whether the attribute accepts multiple values
                          example: false
                        default_value:
                          type: string
                          nullable: true
                          description: The default value for the attribute if not specified for a user
                          example: "us-east"
                        description:
                          type: string
                          nullable: true
                          description: Optional description of the attribute's purpose
                          example: "User region for RLS"
                        system:
                          type: boolean
                          description: Whether this is a system-defined attribute (`true`) or custom attribute (`false`)
                          example: false
              examples:
                success:
                  summary: Success response
                  value:
                    records:
                      - id: ""
                        name: "omni_user_id"
                        label: "Omni User ID"
                        type: "String"
                        multiple_values: false
                        default_value: null
                        description: null
                        system: true
                      - id: ""
                        name: "omni_user_email"
                        label: "Omni User Email"
                        type: "String"
                        multiple_values: false
                        default_value: null
                        description: null
                        system: true
                      - id: ""
                        name: "omni_user_name"
                        label: "Omni User Name"
                        type: "String"
                        multiple_values: false
                        default_value: null
                        description: null
                        system: true
                      - id: ""
                        name: "omni_user_timezone"
                        label: "Omni User Timezone"
                        type: "String"
                        multiple_values: false
                        default_value: null
                        description: null
                        system: true
                      - id: ""
                        name: "omni_is_org_admin"
                        label: "Omni Is Org Admin"
                        type: "String"
                        multiple_values: false
                        default_value: null
                        description: null
                        system: true
                      - id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                        name: "region"
                        label: "Region"
                        type: "String"
                        multiple_values: false
                        default_value: "us-east"
                        description: "User region for RLS"
                        system: false
                      - id: "b2c3d4e5-f6a7-8901-bcde-f23456789012"
                        name: "access_level"
                        label: "Access Level"
                        type: "Number"
                        multiple_values: false
                        default_value: "1"
                        description: "Numeric access level for user permissions"
                        system: false
                      - id: "c3d4e5f6-a7b8-9012-cdef-234567890123"
                        name: "departments"
                        label: "Departments"
                        type: "String"
                        multiple_values: true
                        default_value: null
                        description: "User departments for data filtering"
                        system: false
        '401':
          description: Unauthorized - Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "Unauthorized"
                message: "Invalid or missing API key"
        '403':
          description: Forbidden - User does not have required admin permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "Forbidden"
                message: "User does not have permission to manage user attributes"
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /scim/v2/users:
    post:
      tags:
        - Users
      summary: Create user
      description: Create a user.
      x-mint:
        content: |
          <Tip>
            To manage model and connection role assignments for users, see the [User model role APIs](/api/user-model-roles).
          </Tip>
      security:
        - orgApiKey: []
      operationId: createUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - displayName
                - userName
              properties:
                displayName:
                  type: string
                  description: The user's display name
                  example: Blob Ross
                userName:
                  type: string
                  format: email
                  description: The user's email address
                  example: blob.ross@blobsrus.com
                urn:omni:params:1.0:UserAttribute:
                  type: object
                  description: |
                    [User attributes](/administration/users/attributes) as key/value pairs, where keys map to the IDs of user attributes defined in Omni. This is the **Reference** column in the **User attributes** page.
                  example:
                    good_blob: true
      responses:
        '201':
          description: User created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScimUser'
              example:
                active: true
                displayName: Blob Ross
                emails:
                  - primary: true
                    value: blob.ross@blobsrus.co
                groups: []
                id: 9e8719d9-276a-4964-9395-a493189a247c
                meta:
                  created: "2024-12-03T23:13:14.109Z"
                  lastModified: "2024-12-03T23:13:14.109Z"
                  resourceType: User
                schemas:
                  - "urn:ietf:params:scim:schemas:core:2.0:User"
                userName: blob.ross@blobsrus.co
                urn:omni:params:1.0:UserAttribute:
                  good_blob: "yes"
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - Users
      summary: List users
      description: |
        List users in the organization, sorted by creation time. Use the [embed users](/api/users/list-embed-users) endpoint to retrieve embed users.
      security:
        - orgApiKey: []
      operationId: listUsers
      parameters:
        - name: filter
          in: query
          schema:
            type: string
          description: |
            Filter users using the `userName` field with SCIM operators:

            | Operator | Description | Example |
            |----------|-------------|---------|
            | `eq`     | Exact match | `userName eq "blob.ross@blobsrus.co"` |
            | `co`     | Contains substring | `userName co "smith"` |

            **Case sensitivity:**

            - Attribute names (`userName`, `USERNAME`) are case-insensitive
            - Operator names (`eq`, `EQ`, `co`, `CO`) are case-insensitive
            - Email comparisons are case-insensitive

            Usernames should be URL-encoded when passed as filters. For example, `userName eq "user@example.com"` will become `userName%20eq%20%22user%40example.com%22` when encoded.
          example: userName co "smith"
        - name: count
          in: query
          schema:
            type: integer
            default: 100
          description: The number of users to return per page
          example: 50
        - name: startIndex
          in: query
          schema:
            type: integer
            default: 1
          description: An integer index that determines the starting point of the sorted result list
          example: 1
      responses:
        '200':
          description: List of users
          content:
            application/json:
              schema:
                type: object
                properties:
                  Resources:
                    type: array
                    description: The list of users
                    items:
                      $ref: '#/components/schemas/ScimUser'
                  schemas:
                    type: array
                    description: SCIM schema information for the response
                    items:
                      type: string
                  itemsPerPage:
                    type: integer
                    description: The number of users returned in the current page
                  totalResults:
                    type: integer
                    description: The total number of users matching the query
                  startIndex:
                    type: integer
                    description: The starting index of the current page in the result set
              example:
                Resources:
                  - active: true
                    displayName: Blob Ross
                    emails:
                      - primary: true
                        value: blob.ross@blobsrus.co
                    groups: []
                    id: 9e8719d9-276a-4964-9395-a493189a247c
                    meta:
                      created: "2024-11-04T16:01:47.015Z"
                      lastModified: "2024-11-04T16:05:45.356Z"
                      resourceType: User
                    schemas:
                      - "urn:ietf:params:scim:schemas:core:2.0:User"
                      - "urn:omni:params:1.0:UserAttribute"
                      - "urn:omni:params:scim:schemas:extension:user:2.0"
                    userName: blob.ross@blobsrus.co
                    urn:omni:params:scim:schemas:extension:user:2.0:
                      lastLogin: "2024-01-03T00:00:00.000Z"
                itemsPerPage: 1
                schemas:
                  - "urn:ietf:params:scim:api:messages:2.0:ListResponse"
                startIndex: 1
                totalResults: 1
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /scim/v2/users/{userId}:
    get:
      tags:
        - Users
      summary: Retrieve user
      description: Retrieve a user using their unique ID.
      security:
        - orgApiKey: []
      operationId: getUser
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the user to be retrieved
          example: 9e8719d9-276a-4964-9395-a493189a247c
      responses:
        '200':
          description: User details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScimUser'
              example:
                active: true
                displayName: Blob Ross
                emails:
                  - primary: true
                    value: blob.ross@blobsrus.co
                groups: []
                id: 9e8719d9-276a-4964-9395-a493189a247c
                meta:
                  created: "2024-12-03T23:13:14.109Z"
                  lastModified: "2024-12-03T23:13:14.109Z"
                  resourceType: User
                schemas:
                  - "urn:ietf:params:scim:schemas:core:2.0:User"
                  - "urn:omni:params:1.0:UserAttribute"
                  - "urn:omni:params:scim:schemas:extension:user:2.0"
                userName: blob.ross@blobsrus.co
                urn:omni:params:1.0:UserAttribute:
                  good_blob: "yes"
                urn:omni:params:scim:schemas:extension:user:2.0:
                  lastLogin: "2024-01-03T00:00:00.000Z"
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      tags:
        - Users
      summary: Replace user
      description: |
        Replace the specified user. Per the SCIM 2.0 specification, `PUT` replaces the entire user resource: any optional attribute omitted from the request body is reset to its default value.

        To update individual attributes without affecting others, use the [Update user](/api/users/update-user) endpoint, which performs a partial update via `PATCH`.
      security:
        - orgApiKey: []
      operationId: replaceUser
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the user to be replaced
          example: 9e8719d9-276a-4964-9395-a493189a247c
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - userName
              properties:
                active:
                  type: boolean
                  default: true
                  description: Whether the user is active. Set to `false` to deactivate the user.
                  example: true
                displayName:
                  type: string
                  description: The user's display name
                  example: Blob Ross
                userName:
                  type: string
                  format: email
                  description: The user's email address.
                  example: blob.ross@blobsrus.co
                urn:omni:params:1.0:UserAttribute:
                  type: object
                  description: |
                    [User attributes](/administration/users/attributes) as key/value pairs, where keys map to the IDs of user attributes defined in Omni. This is the **Reference** column in the **User attributes** page.
                  example:
                    good_blob: "yes"
      responses:
        '200':
          description: User replaced successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScimUser'
              example:
                active: true
                displayName: Blob Ross
                emails:
                  - primary: true
                    value: blob.ross@blobsrus.co
                groups: []
                id: 9e8719d9-276a-4964-9395-a493189a247c
                meta:
                  created: "2024-12-03T23:13:14.109Z"
                  lastModified: "2024-12-03T23:13:14.109Z"
                  resourceType: User
                schemas:
                  - "urn:ietf:params:scim:schemas:core:2.0:User"
                userName: blob.ross@blobsrus.co
                urn:omni:params:1.0:UserAttribute:
                  good_blob: "yes"
        '400':
          description: Invalid request body
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      tags:
        - Users
      summary: Update user
      description: |
        Update the specified user by applying a list of [SCIM 2.0 patch operations](https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2). Attributes not referenced in the request are left unchanged.

        To replace the entire user resource in a single request, use the [Replace user](/api/users/replace-user) endpoint.

        <Warning>
          Omni does not have a reversible "deactivated" state for users. Setting `active` to `false` revokes the user's membership, which is **not reversible**: their schedules are deleted (unless transferred first) and their Personal Access Tokens are disabled. See [Revoking user memberships](/administration/users/delete) before using this operation.
        </Warning>
      security:
        - orgApiKey: []
      operationId: updateUser
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the user to be updated
          example: 9e8719d9-276a-4964-9395-a493189a247c
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - Operations
                - schemas
              properties:
                schemas:
                  type: array
                  description: The SCIM schema URIs for the request. Must include `urn:ietf:params:scim:api:messages:2.0:PatchOp`.
                  items:
                    type: string
                    enum:
                      - "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                  example:
                    - "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                Operations:
                  type: array
                  minItems: 1
                  description: The list of patch operations to apply to the user.
                  items:
                    type: object
                    required:
                      - op
                      - value
                    properties:
                      op:
                        type: string
                        description: The operation to perform.
                        enum:
                          - add
                          - replace
                          - remove
                      path:
                        type: string
                        description: |
                          The attribute path the operation targets. Omit to apply the operation to the resource as a whole.
                        example: displayName
                      value:
                        description: The value to apply for the operation. Required for `add` and `replace` operations.
                        example: "Blob Ross"
                        anyOf:
                          - type: string
                          - type: number
                          - type: boolean
                          - type: array
                            items:
                              type: string
                          - type: object
                            properties:
                              active:
                                type: boolean
                              displayName:
                                type: string
                              userName:
                                type: string
                                format: email
            examples:
              revokeUser:
                summary: Revoke a user's membership
                value:
                  schemas:
                    - "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                  Operations:
                    - op: replace
                      path: active
                      value: false
              updateDisplayNameAndEmail:
                summary: Update display name and email
                value:
                  schemas:
                    - "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                  Operations:
                    - op: replace
                      value:
                        displayName: Blob Ross
                        userName: blob.ross@blobsrus.co
      responses:
        '200':
          description: User updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScimUser'
              example:
                active: false
                displayName: Blob Ross
                emails:
                  - primary: true
                    value: blob.ross@blobsrus.co
                groups: []
                id: 9e8719d9-276a-4964-9395-a493189a247c
                meta:
                  created: "2024-12-03T23:13:14.109Z"
                  lastModified: "2024-12-03T23:13:14.109Z"
                  resourceType: User
                schemas:
                  - "urn:ietf:params:scim:schemas:core:2.0:User"
                userName: blob.ross@blobsrus.co
        '400':
          description: Invalid patch operations
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Users
      summary: Delete user
      description: |
        Delete the specified user. Use the [Delete embed user](/api/users/delete-embed-user) endpoint to delete embed users.

        <Warning>
          Deleting a user revokes their membership, which is **not reversible**: their schedules are deleted (unless transferred first) and their Personal Access Tokens are disabled. See [Revoking user memberships](/administration/users/delete) for the full list of consequences before using this operation.
        </Warning>
      security:
        - orgApiKey: []
      operationId: deleteUser
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the user to be deleted
          example: 9e8719d9-276a-4964-9395-a493189a247c
      responses:
        '204':
          description: User deleted successfully
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /scim/v2/embed/users:
    get:
      tags:
        - Users
      summary: List embed users
      description: |
        List embed users in the organization, sorted by creation time. Use the [users](/api/users/list-users) endpoint to retrieve standard users.
      security:
        - orgApiKey: []
      operationId: listEmbeddedUsers
      parameters:
        - name: filter
          in: query
          schema:
            type: string
          description: |
            Filter embed users using the `userName` or `embedExternalId` fields with SCIM operators.

            | Operator | Description | Example |
            |----------|-------------|---------|
            | `eq`     | Exact match | `userName eq "blob.ross@blobsrus.co"` |
            | `co`     | Contains substring | `embedExternalId co "sales"` |

            **Case sensitivity:**

            - Attribute names (`userName`, `embedExternalId`) are case-insensitive
            - Operator names (`eq`, `EQ`, `co`, `CO`) are case-insensitive
            - Email comparisons are case-insensitive
            - External ID (`embedExternalId`) comparisons are case-sensitive, per SCIM spec

            Usernames should be URL-encoded when passed as filters. For example, `userName eq "user@example.com"` will become `userName%20eq%20%22user%40example.com%22` when encoded.
          example: embedExternalId co "sales"
        - name: count
          in: query
          schema:
            type: integer
            default: 100
          description: The number of users to return
          example: 50
        - name: startIndex
          in: query
          schema:
            type: integer
            default: 1
          description: An integer index that determines the starting point of the sorted result list
          example: 1
      responses:
        '200':
          description: of embed users
          content:
            application/json:
              schema:
                type: object
                properties:
                  Resources:
                    type: array
                    description: The list of embed users
                    items:
                      $ref: '#/components/schemas/ScimEmbedUser'
                  schemas:
                    type: array
                    description: SCIM schema information for the response
                    items:
                      type: string
                  itemsPerPage:
                    type: integer
                    description: The number of embed users returned in the current page
                  totalResults:
                    type: integer
                    description: The total number of embed users matching the query
                  startIndex:
                    type: integer
                    description: The starting index of the current page in the result set
              example:
                Resources:
                  - active: true
                    displayName: Blobby
                    emails:
                      - primary: true
                        value: embed-user-i_4TrNwyVTu34bZaC35VJVD3-pknp4YF7V8_Bi4TMdw@blobsrus.embed-exploreomni.co
                    groups:
                      - display: All Embed Users
                        value: 4GcvQ2D9
                      - display: Omni
                        value: nqGuU_uh
                    id: 2212aecf-a2ba-4d99-b23b-f615bc4c6522
                    meta:
                      created: "2024-09-30T20:25:01.822Z"
                      lastModified: "2024-09-30T20:51:47.558Z"
                      resourceType: User
                    schemas:
                      - "urn:ietf:params:scim:schemas:core:2.0:User"
                    userName: embed-user-i_4TrNwyVTu34bZaC35VJVD3-pknp4YF7V8_Bi4TMdw@blobsrus.embed-exploreomni.co
                    embedEmail: null
                    embedEntity: omni
                    embedExternalId: blobby-manager
                itemsPerPage: 1
                schemas:
                  - "urn:ietf:params:scim:api:messages:2.0:ListResponse"
                startIndex: 1
                totalResults: 1
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /scim/v2/embed/users/{userId}:
    get:
      tags:
        - Users
      summary: Retrieve embed user
      description: Retrieve an embed user using their unique ID. Use the [Retrieve user](/api/users/retrieve-user) endpoint to retrieve standard users.
      security:
        - orgApiKey: []
      operationId: getEmbeddedUser
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the embed user to be retrieved
          example: 2212aecf-a2ba-4d99-b23b-f615bc4c6522
      responses:
        '200':
          description: Embed user details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScimEmbedUser'
              example:
                active: true
                displayName: Blobby
                emails:
                  - primary: true
                    value: embed-user-i_4TrNwyVTu34bZaC35VJVD3-pknp4YF7V8_Bi4TMdw@blobsrus.embed-exploreomni.co
                groups:
                  - display: All Embed Users
                    value: 4GcvQ2D9
                  - display: Omni
                    value: nqGuU_uh
                id: 2212aecf-a2ba-4d99-b23b-f615bc4c6522
                meta:
                  created: "2024-09-30T20:25:01.822Z"
                  lastModified: "2024-09-30T20:51:47.558Z"
                  resourceType: User
                schemas:
                  - "urn:ietf:params:scim:schemas:core:2.0:User"
                userName: embed-user-i_4TrNwyVTu34bZaC35VJVD3-pknp4YF7V8_Bi4TMdw@blobsrus.embed-exploreomni.co
                embedEmail: null
                embedEntity: omni
                embedExternalId: blobby-manager
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - Users
      summary: Delete embed user
      description: Delete the specified embed user. Use the [Delete user](/api/users/delete-user) endpoint to delete standard users.
      security:
        - orgApiKey: []
      operationId: deleteEmbeddedUser
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the embed user to be deleted
          example: 2212aecf-a2ba-4d99-b23b-f615bc4c6522
      responses:
        '204':
          description: Embed user deleted successfully
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /scim/v2/groups:
    post:
      tags:
        - User groups
      summary: Create user group
      description: |
        Create a [user group](/administration/users/groups). To manage model and connection role assignments for user groups, see the [User group model role APIs](/api/user-group-model-roles).
      security:
        - orgApiKey: []
      operationId: createUserGroup
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - displayName
              properties:
                displayName:
                  type: string
                  description: The name of the group. Names must be 64 characters or less.
                  example: Blob Sales
                members:
                  type: array
                  description: A list of users to add as group members. Each member should be specified as an object containing a user ID.
                  items:
                    type: object
                    properties:
                      value:
                        type: string
                        description: The user's unique identifier
                  example:
                    - value: 9e8719d9-276a-4964-9395-a493189a247c
      responses:
        '201':
          description: Group created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScimGroup'
              example:
                displayName: Blob Sales
                id: mEhXj6ZI
                meta:
                  created: "2024-12-04T00:08:03.250Z"
                  lastModified: "2024-12-04T00:08:03.250Z"
                  resourceType: Group
                schemas:
                  - "urn:ietf:params:scim:schemas:core:2.0:Group"
                members:
                  - display: blob.ross@blobsrus.co
                    value: 9e8719d9-276a-4964-9395-a493189a247c
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - User groups
      summary: List user groups
      description: Return a list of user groups, sorted by creation time.
      security:
        - orgApiKey: []
      operationId: listUserGroups
      parameters:
        - name: count
          in: query
          schema:
            type: integer
            default: 100
          description: The number of groups to return
          example: 50
        - name: startIndex
          in: query
          schema:
            type: integer
            default: 1
          description: An integer index that determines the starting point of the sorted result list
          example: 1
      responses:
        '200':
          description: List of user groups
          content:
            application/json:
              schema:
                type: object
                properties:
                  Resources:
                    type: array
                    description: The list of user groups
                    items:
                      $ref: '#/components/schemas/ScimGroup'
                  schemas:
                    type: array
                    description: SCIM schema information for the response
                    items:
                      type: string
                  itemsPerPage:
                    type: integer
                    description: The number of groups returned in the current page
                  startIndex:
                    type: integer
                    description: The starting index of the current page in the result set
                  totalResults:
                    type: integer
                    description: The total number of groups matching the query
              example:
                Resources:
                  - displayName: Blob Sales
                    id: mEhXj6ZI
                    meta:
                      created: "2024-08-29T20:33:36.626Z"
                      lastModified: "2024-08-29T20:33:36.626Z"
                      resourceType: Group
                    schemas:
                      - "urn:ietf:params:scim:schemas:core:2.0:Group"
                    members:
                      - display: blob.ross@blobsrus.co
                        value: 9e8719d9-276a-4964-9395-a493189a247c
                itemsPerPage: 1
                schemas:
                  - "urn:ietf:params:scim:api:messages:2.0:ListResponse"
                startIndex: 1
                totalResults: 1
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /scim/v2/groups/{userGroupId}:
    get:
      tags:
        - User groups
      summary: Retrieve user group
      description: Retrieves a user group using its unique ID.
      security:
        - orgApiKey: []
      operationId: getUserGroup
      parameters:
        - name: userGroupId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the group to be retrieved
          example: mEhXj6ZI
      responses:
        '200':
          description: User group details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScimGroup'
              example:
                displayName: Blob Sales
                id: mEhXj6ZI
                meta:
                  created: "2024-08-29T20:33:36.626Z"
                  lastModified: "2024-08-29T20:33:36.626Z"
                  resourceType: Group
                schemas:
                  - "urn:ietf:params:scim:schemas:core:2.0:Group"
                members:
                  - display: blob.ross@blobsrus.co
                    value: 9e8719d9-276a-4964-9395-a493189a247c
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      tags:
        - User groups
      summary: Replace user group
      description: |
        Replace the specified user group. Per the SCIM 2.0 specification, `PUT` replaces the entire group resource, including its membership.

        To add or remove individual members without replacing the full membership list, use the [Update user group](/api/user-groups/update-user-group) endpoint, which performs a partial update via `PATCH`.
      security:
        - orgApiKey: []
      operationId: replaceUserGroup
      parameters:
        - name: userGroupId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the group to be replaced
          example: mEhXj6ZI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - displayName
                - members
              properties:
                displayName:
                  type: string
                  description: The name of the group. Names must be 64 characters or less.
                  example: Blob SEs
                members:
                  type: array
                  description: |
                    A list of users that defines the group membership. **Note**: This will overwrite the existing membership. Include existing members to retain their membership.
                  items:
                    type: object
                    properties:
                      display:
                        type: string
                        description: The user's email address
                      value:
                        type: string
                        description: The user's unique identifier
                  example:
                    - display: blob.ross@blobsrus.co
                      value: 9e8719d9-276a-4964-9395-a493189a247c
      responses:
        '200':
          description: Group replaced successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScimGroup'
              example:
                displayName: Blob SEs
                id: mEhXj6ZI
                meta:
                  created: "2024-12-04T00:08:03.250Z"
                  lastModified: "2024-12-04T00:20:47.346Z"
                  resourceType: Group
                schemas:
                  - "urn:ietf:params:scim:schemas:core:2.0:Group"
                members:
                  - display: blob.ross@blobsrus.co
                    value: 9e8719d9-276a-4964-9395-a493189a247c
        '400':
          description: Invalid request body
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      tags:
        - User groups
      summary: Update user group
      description: |
        Update the specified user group by applying a list of [SCIM 2.0 patch operations](https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2). Use `PATCH` to add or remove individual members, or to rename the group, without affecting other attributes.

        To replace the entire group resource (including the full membership list) in a single request, use the [Replace user group](/api/user-groups/replace-user-group) endpoint.
      security:
        - orgApiKey: []
      operationId: updateUserGroup
      parameters:
        - name: userGroupId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the group to be updated
          example: mEhXj6ZI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - Operations
                - schemas
              properties:
                schemas:
                  type: array
                  description: The SCIM schema URIs for the request. Must include `urn:ietf:params:scim:api:messages:2.0:PatchOp`.
                  items:
                    type: string
                    enum:
                      - "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                  example:
                    - "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                Operations:
                  type: array
                  minItems: 1
                  description: |
                    The list of patch operations to apply to the group. Supported operations:

                    | Operation | Description |
                    |-----------|-------------|
                    | `add` with `path: "members"` | Add one or more users to the group |
                    | `remove` with `path: 'members[value eq "{userId}"]'` | Remove a single user from the group |
                    | `replace` with `path: "displayName"` | Rename the group |
                    | `replace` with `path: "members"` | Replace the entire membership list |
                    | `replace` without a `path` | Replace top-level attributes (for example, `displayName`) |
                  items:
                    type: object
                    required:
                      - op
                    properties:
                      op:
                        type: string
                        description: The operation to perform.
                        enum:
                          - add
                          - replace
                          - remove
                      path:
                        type: string
                        description: |
                          The attribute path the operation targets. For member removal, use the filter syntax `members[value eq "{userId}"]`. Omit to apply the operation to the resource as a whole.
                        example: members
                      value:
                        description: The value to apply for the operation. Required for `add` and `replace` operations.
            examples:
              addMembers:
                summary: Add members to the group
                value:
                  schemas:
                    - "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                  Operations:
                    - op: add
                      path: members
                      value:
                        - display: blob.ross@blobsrus.co
                          value: 9e8719d9-276a-4964-9395-a493189a247c
              removeMember:
                summary: Remove a member from the group
                value:
                  schemas:
                    - "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                  Operations:
                    - op: remove
                      path: 'members[value eq "9e8719d9-276a-4964-9395-a493189a247c"]'
              renameGroup:
                summary: Rename the group
                value:
                  schemas:
                    - "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                  Operations:
                    - op: replace
                      path: displayName
                      value: Blob SEs
      responses:
        '200':
          description: Group updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScimGroup'
              example:
                displayName: Blob SEs
                id: mEhXj6ZI
                meta:
                  created: "2024-12-04T00:08:03.250Z"
                  lastModified: "2024-12-04T00:20:47.346Z"
                  resourceType: Group
                schemas:
                  - "urn:ietf:params:scim:schemas:core:2.0:Group"
                members:
                  - display: blob.ross@blobsrus.co
                    value: 9e8719d9-276a-4964-9395-a493189a247c
        '400':
          description: Invalid patch operations
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      tags:
        - User groups
      summary: Delete user group
      description: Delete the specified user group.
      security:
        - orgApiKey: []
      operationId: deleteUserGroup
      parameters:
        - name: userGroupId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the group to be deleted
          example: mEhXj6ZI
      responses:
        '204':
          description: Group deleted successfully
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/user-groups/{userGroupId}/model-roles:
    post:
      tags:
        - User group model roles
      summary: Assign or update user group model role
      description: |
        Assign or update a model role for a user group. If the user group already has a role for the specified model, this endpoint will update it to the new role. All members of the user group will inherit this role.

        Model roles control what actions user group members can perform on models and connections. To manage user groups, see the [User group APIs](/api/user-groups).
      security:
        - bearerAuth: []
      operationId: assignUserGroupModelRole
      parameters:
        - name: userGroupId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the user group to assign or update a model role for.
          example: mEhXj6ZI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - roleName
              properties:
                connectionId:
                  type: string
                  format: uuid
                  description: |
                    The ID of the connection that the model belongs to:
                    
                    - **Required** if `modelId` is not provided
                    - **Optional** if `modelId` is provided, as it will be inferred from the model
                modelId:
                  type: string
                  format: uuid
                  description: |
                    The ID of the model to assign the role for:
                    
                    - **Optional** when assigning `CONNECTION_ADMIN` or [custom roles](/administration/users/custom-roles) with `CONNECTION_ADMIN` as the base role
                    - **Required** for other role types
                roleName:
                  type: string
                  description: |
                    The role to assign. Available roles include:

                    - `VIEWER` - Can view the model
                    - `QUERIER` - Can view and query the model
                    - `QUERY_TOPICS` - Can query specific topics. Equivalent to **Restricted Querier.**
                    - `MODELER` - Can edit and model the data
                    - `CONNECTION_ADMIN` - Full administrative access to the connection
                    - `NO_ACCESS` - No access to the model
                    - [Custom roles](/administration/users/custom-roles) defined for your organization
            example:
              connectionId: bc1f9c9f-208d-48a2-9ae3-ff80f2c79fed
              modelId: 7d3e4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a
              roleName: QUERIER
      responses:
        '200':
          description: Model role assigned or updated successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  userGroupId:
                    type: string
                    description: The ID of the user group.
                  connectionId:
                    type: string
                    format: uuid
                    description: The ID of the connection.
                  modelId:
                    type: string
                    format: uuid
                    description: The ID of the model.
                  roleName:
                    type: string
                    description: The assigned role name.
              example:
                userGroupId: mEhXj6ZI
                connectionId: bc1f9c9f-208d-48a2-9ae3-ff80f2c79fed
                modelId: 7d3e4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a
                roleName: QUERIER
        '400':
          description: |
            Bad Request. Possible error messages include:

            - `Invalid JSON`
            - `Invalid model ID`
            - `Invalid connection ID`
            - `Method not allowed`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible error messages include:

            - `User group not found in organization`
            - `Model does not exist`
            - `Connection does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: |
            Unprocessable Entity. Possible error messages include:

            - `Invalid role`
            - `Model does not belong to connection`
            - `Only shared and shared_extension models can be assigned model roles`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - User group model roles
      summary: Retrieve user group model roles
      description: |
        Retrieve the model role assignments for a user group.
      security:
        - bearerAuth: []
      operationId: getUserGroupModelRoles
      parameters:
        - name: userGroupId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the user group to retrieve model roles for.
          example: mEhXj6ZI
        - name: modelId
          in: query
          schema:
            type: string
            format: uuid
          description: Filter results to a specific model ID. If not provided, returns roles for all models the user group has access to.
          example: 7d3e4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a
        - name: connectionId
          in: query
          schema:
            type: string
            format: uuid
          description: Filter results to models from a specific connection. If not provided, returns roles for all connections.
          example: bc1f9c9f-208d-48a2-9ae3-ff80f2c79fed
      responses:
        '200':
          description: User group model roles retrieved successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  userGroupId:
                    type: string
                    description: The ID of the user group.
                  results:
                    type: array
                    description: Array of role assignments for the user group.
                    items:
                      type: object
                      properties:
                        modelId:
                          type: string
                          format: uuid
                          description: The ID of the model.
                        connectionId:
                          type: string
                          format: uuid
                          description: The ID of the connection.
                        roleName:
                          type: string
                          description: The role assigned to the user group for this model.
                        baseRole:
                          type: string
                          description: The base role assigned to the user group.
              example:
                userGroupId: mEhXj6ZI
                results:
                  - baseRole: QUERIER
                    roleName: QUERIER
                    connectionId: bc1f9c9f-208d-48a2-9ae3-ff80f2c79fed
                    modelId: 7d3e4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a
        '404':
          description: User group not found in organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: User group not found in organization
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/users/{userId}/model-roles:
    post:
      tags:
        - User model roles
      summary: Assign or update user model role
      description: |
        Assign or update a model role for a user. If the user already has a role for the specified model, this endpoint will update it to the new role.

        Model roles control what actions a user can perform on models and connections. To manage users, see the [User APIs](/api/users).
      security:
        - bearerAuth: []
      operationId: assignUserModelRole
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the user to assign or update a model role for.
          example: 9e8719d9-276a-4964-9395-a493189a247c
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - roleName
              properties:
                connectionId:
                  type: string
                  format: uuid
                  description: |
                    The ID of the connection that the model belongs to:
                    
                    - **Required** if `modelId` is not provided
                    - **Optional** if `modelId` is provided, in which case it will be inferred from the model
                modelId:
                  type: string
                  format: uuid
                  description: |
                    The ID of the model to assign the role for:
                    
                    - **Optional** when assigning `CONNECTION_ADMIN` or [custom roles](/administration/users/custom-roles) with `CONNECTION_ADMIN` as the base role
                    - **Required** for other role types
                roleName:
                  type: string
                  description: |
                    The role to assign. Available roles include:

                    - `VIEWER` - Can view the model
                    - `QUERIER` - Can view and query the model
                    - `QUERY_TOPICS` - Can query specific topics. Equivalent to **Restricted Querier**.
                    - `MODELER` - Can edit and model the data
                    - `CONNECTION_ADMIN` - Full administrative access to the connection
                    - `NO_ACCESS` - No access to the model
                    - [Custom roles](/administration/users/custom-roles) defined for your organization
            example:
              connectionId: bc1f9c9f-208d-48a2-9ae3-ff80f2c79fed
              modelId: 7d3e4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a
              roleName: QUERIER
      responses:
        '200':
          description: Model role assigned or updated successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  userId:
                    type: string
                    format: uuid
                    description: The ID of the user.
                  connectionId:
                    type: string
                    format: uuid
                    description: The ID of the connection.
                  modelId:
                    type: string
                    format: uuid
                    description: The ID of the model.
                  roleName:
                    type: string
                    description: The assigned role name.
              example:
                userId: 9e8719d9-276a-4964-9395-a493189a247c
                connectionId: bc1f9c9f-208d-48a2-9ae3-ff80f2c79fed
                modelId: 7d3e4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a
                roleName: QUERIER
        '400':
          description: |
            Bad Request. Possible error messages include:

            - `Invalid JSON`
            - `Invalid model ID`
            - `Invalid connection ID`
            - `Method not allowed`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Not Found. Possible error messages include:

            - `User not found in organization`
            - `Model does not exist`
            - `Connection does not exist`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: |
            Unprocessable Entity. Possible error messages include:

            - `Invalid role`
            - `Model does not belong to connection`
            - `Only shared and shared_extension models can be assigned model roles`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    get:
      tags:
        - User model roles
      summary: Retrieve user model roles
      description: |
        Retrieve the model role assignments for a user. This includes both direct role assignments and roles inherited from user group memberships.
      security:
        - bearerAuth: []
      operationId: getUserModelRoles
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the user to retrieve model roles for.
          example: 9e8719d9-276a-4964-9395-a493189a247c
        - name: modelId
          in: query
          schema:
            type: string
            format: uuid
          description: Filter results to a specific model ID. If not provided, returns roles for all models the user has access to.
          example: 7d3e4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a
        - name: connectionId
          in: query
          schema:
            type: string
            format: uuid
          description: Filter results to models from a specific connection. If not provided, returns roles for all connections.
          example: bc1f9c9f-208d-48a2-9ae3-ff80f2c79fed
      responses:
        '200':
          description: User model roles retrieved successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  membershipId:
                    type: string
                    format: uuid
                    description: The ID of the user's membership in the organization.
                  results:
                    type: array
                    description: Array of all role assignments for the user, including direct assignments, roles inherited from user groups, and connection base roles.
                    items:
                      type: object
                      properties:
                        modelId:
                          type: string
                          format: uuid
                          description: The ID of the model.
                        connectionId:
                          type: string
                          format: uuid
                          description: The ID of the connection.
                        roleName:
                          type: string
                          description: The role name for this assignment.
                        baseRole:
                          type: string
                          description: The base role for this assignment.
                        priority:
                          type: integer
                          description: The priority of this role assignment. Higher values take precedence.
                        resolved:
                          type: boolean
                          description: If `true`, this is the highest priority role for the model. This is the role that will be used when determining the user's effective permissions.
                        from:
                          type: object
                          description: Information about where this role assignment comes from.
                          properties:
                            type:
                              type: string
                              description: |
                                The type of role assignment:

                                - `User Role` - Direct role assignment to the user
                                - `Group Role` - Role inherited from a user group
                                - `Connection Base Role` - Default role from the connection
                            miniUuid:
                              type: string
                              description: The short ID of the user group. Only present for `Group Role` type.
                            name:
                              type: string
                              description: The name of the user group. Only present for `Group Role` type.
                            depth:
                              type: integer
                              description: The depth of group nesting. Only present for `Group Role` type.
              example:
                membershipId: 9633bd79-7bdf-4773-8952-8fdd4098e51c
                results:
                  - baseRole: MODELER
                    from:
                      type: User Role
                    priority: 350
                    resolved: true
                    roleName: MODELER
                    connectionId: 8a464dc9-1f0e-4a9e-86fa-e1e6d970157c
                    modelId: 5fb90312-67b1-4cca-823a-9a341d549320
                  - baseRole: QUERIER
                    from:
                      depth: 0
                      miniUuid: PgjffoEu
                      name: Super Group
                      type: Group Role
                    priority: 250
                    resolved: false
                    roleName: QUERIER
                    connectionId: 8a464dc9-1f0e-4a9e-86fa-e1e6d970157c
                    modelId: 5fb90312-67b1-4cca-823a-9a341d549320
                  - baseRole: VIEWER
                    from:
                      type: Connection Base Role
                    priority: 50
                    resolved: false
                    roleName: VIEWER
                    connectionId: 8a464dc9-1f0e-4a9e-86fa-e1e6d970157c
                    modelId: 5fb90312-67b1-4cca-823a-9a341d549320
        '404':
          description: User not found in organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: User not found in organization
                status: 404
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v2/documents:
    post:
      tags:
        - Documents v2
      summary: Create document
      description: |
        Create a brand-new document and publish it. Accepts creation metadata (`modelId`, `name`, and optional `identifier` / `description` / `folderId`) plus the same content slice as the draft patch body, including `queryPresentations`, `controls`, `settings`, and `containers`.

        Tiles are addressed by their record key in `queryPresentations.data` (e.g. `"1"`, `"2"`); the server owns internal tile identity, so you never send `miniUuid`. A new document starts with a single empty seed tile at key `"1"` — write to `"1"` (or send it as `null`) to replace the seed.

        Because this is the first publish of brand-new content, it is not subject to the organization's `requirePullRequestToPublish` policy (which gates edits to existing content).
      x-mint:
        content: |
          ## Content sections

          - **`queryPresentations`** — Tabs/tiles keyed by record key. `data` is shallow-merged by key; `order` (when present) replaces the tab order.
          - **`controls`** — Dashboard filters/controls keyed by control ID. `data` is shallow-merged by key (`data.{id}: null` deletes a control); `order` (when present) replaces the control order.
          - **`settings`** — Dashboard settings, shallow-merged per key: `crossfilterEnabled`, `customText` (`queryError` / `queryNoResults`), `facetFilters`, `refreshInterval`, `runQueriesOn`.
          - **`containers`** — The dashboard layout. When present it fully replaces the existing layout (grid / stack / page / reference containers, recursively nested).

          ## Migrating from v1

          The v1 one-shot `PUT /v1/documents/{documentId}` maps to the v2 **Create > Patch draft > Publish** workflow. For a brand-new document, a single request to this endpoint is enough. To edit an existing document, use [Create draft and patch document](/api/documents-v2/create-draft-and-patch-document) then [Publish draft](/api/documents-v2/publish-draft).

          | v1 `PUT` field | v2 location |
          |----------------|-------------|
          | `modelId` | `modelId` (returned on read; accepted but immutable on patch) |
          | `name` / `description` | `name` / `description` |
          | `facetFilters` (top-level) | `settings.facetFilters` |
          | `refreshInterval` (top-level) | `settings.refreshInterval` |
          | `filterConfig` / `filterOrder` | `controls.data` / `controls.order` |
          | `documentMetadata` (presentation) | `settings.*` / `containers` |
          | `queryPresentations` (array) | `queryPresentations.data` (keyed) + `queryPresentations.order` |
          | `clearExistingDraft` | **Removed.** A pre-existing draft now returns `409`; patch that draft or publish/discard it instead. |

          There is no one-shot `PATCH` endpoint for full document updates that includes identifier changes. To rename a published document's identifier, use the [Rename document identifier endpoint](/api/documents-v2/rename-document-identifier).
          
          There is also no `narrative` field — AI summaries attach as `linked`-type query presentations. See [Create draft and patch document](/api/documents-v2/create-draft-and-patch-document#ai-summaries-via-linked-tiles) for more information.
      security:
        - bearerAuth: []
      operationId: documentsV2Create
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentsV2CreateBody'
            examples:
              minimal:
                summary: Create an empty document
                value:
                  modelId: "b81e4679-1234-4abc-9def-0123456789ab"
                  name: "Q2 Revenue"
              withContent:
                summary: Create a document with one tile and a custom identifier
                value:
                  modelId: "b81e4679-1234-4abc-9def-0123456789ab"
                  identifier: "q2-revenue"
                  name: "Q2 Revenue"
                  description: "Quarterly revenue review"
                  queryPresentations:
                    data:
                      "1":
                        type: "query"
                        name: "Revenue by month"
                        topicName: "order_items"
                        prefersChart: true
                        query:
                          fields:
                            - "order_items.created_at[month]"
                            - "order_items.sale_price_sum"
                  settings:
                    crossfilterEnabled: true
                    refreshInterval: 3600
      responses:
        '201':
          description: Document created and published successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentsV2CreateResponse'
              example:
                identifier: "q2-revenue"
                name: "Q2 Revenue"
                description: "Quarterly revenue review"
        '400':
          description: |
            Invalid request body or schema validation error — for example an unknown top-level field, a length cap exceeded (`name must be 254 characters or fewer`), the query-presentation cap exceeded (`A single patch may carry at most 48 query presentations.`), or the `identifier` is already in use. Also returned when `fileUploadId` is provided on a non-csv/spreadsheet tab type, or when the upload ID doesn't correspond to an upload in the caller's organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to create a document on this model.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Base model or branch not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'

  /v2/documents/{documentId}:
    get:
      tags:
        - Documents v2
      summary: Get document state
      description: |
        Retrieve the document's published state. To read a draft's state, use [Get draft state](/api/documents-v2/get-draft-state) instead.

        The response can be used as-is as the request body in a call to the [Patch draft API](/api/documents-v2/patch-draft).

        Tiles in `queryPresentations.data` are keyed by a stable record key (e.g. `"1"`, `"2"`); the server uses that key to identify existing tiles on a later patch, so you never need to track any other identifier. Control IDs and container `instanceKey` / `referenceKey` values also round-trip unchanged.

        A workbook-only document (no dashboard layout yet) returns only the workbook-scoped fields (`name`, `description`, `queryPresentations`); the dashboard-scoped `containers`, `controls`, and `settings` are omitted until a layout exists.
      security:
        - bearerAuth: []
      operationId: documentsV2Get
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: The document's URL slug (e.g. `abc123`) or its canonical workbook UUID.
        - name: pretty
          in: query
          required: false
          schema:
            type: boolean
          description: Format response with 2-space indentation for readability. Accepts `true`, `1`, or no value (e.g., `?pretty`). Default is compact (single-line) JSON.
      responses:
        '200':
          description: Document state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentsV2ReadResponse'
        '401':
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to read the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Document not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: |
            The document cannot be read as a dashboard: a classic-layout dashboard (upgrade to the advanced layout first) or an app document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v2/documents/{documentId}/draft:
    patch:
      tags:
        - Documents v2
      summary: Create draft and patch document
      description: |
        Create a new draft on the published document and apply the patch. There is no auto-publish — the response includes the new `draftIdentifier` for follow-up calls. Pass an optional `branchId` to attach the draft to a branch; omit it for a draft on the main (unpublished) workspace.
      x-mint:
        content: |
          ## Draft and publish workflow

          Editing an existing document is a three-step flow:

          1. **`PATCH /api/v2/documents/{documentId}/draft`** *(this endpoint)* — creates a new draft on the published document and applies your patch. Returns the new `draftIdentifier`.
          2. *(optional)* [**`PATCH /api/v2/documents/{documentId}/draft/{draftId}`**](/api/documents-v2/patch-draft) — apply further patches to that draft.
          3. [**`POST /api/v2/documents/{documentId}/draft/publish`**](/api/documents-v2/publish-draft) — publish the main draft live.

          A document may have only one main draft at a time. If one already exists, this endpoint returns `409` — patch the existing draft (step 2) or publish/discard it first. This replaces v1's `clearExistingDraft` flag, which **does not exist** in v2.

          A `GET` response is a valid `PATCH` body — [read the current state](/api/documents-v2/get-document-state), edit it, and submit it back verbatim.

          ## Addressing tiles (no `miniUuid`)

          Tiles are addressed by their record key in `queryPresentations.data` (e.g. `"2"`). The server owns internal tile identity, so you never send or manage `miniUuid`:

          - **New key** → a new tile is created.
          - **Existing key, identical content** (a verbatim round-trip of a `GET`) → treated as a **no-op** and dropped from the patch.
          - **Existing key, changed content** → the tile is updated (the server regenerates its internal identifier).
          - **`linked`-type tabs** always re-resolve — they are excluded from no-op detection.
          - Query presentations are immutable server-side: replace a tile by key, don't mutate it in place.
          - `model_extension_id` - The query-model binding. This is read-only and managed by Omni.

          `data` is **shallow-merged** by key — omitted keys are untouched; `data.{key}: null` deletes that tile. `order`, when present, replaces the tab order. `controls` and `settings` are likewise shallow-merged; `containers`, when present, fully replaces the layout.

          ## AI summaries via linked tiles

          There is no `narrative` field. To attach an AI-style summary that reuses another tile's query, add a `linked`-type query presentation whose `sourceQueryPresentationKey` points at the source tile's record key (a string such as `"2"` — **not** a positional index into `order`):

          ```json
          {
            "queryPresentations": {
              "data": {
                "3": {
                  "type": "linked",
                  "name": "Summary",
                  "sourceQueryPresentationKey": "2"
                }
              }
            }
          }
          ```

          ## Binding an upload as a Source tab

          To bind an existing upload (created via [POST /v1/uploads](/api/uploads/upload-csv-file)) as a document Source tab:

          1. [**`GET /api/v2/documents/{documentId}`**](/api/documents-v2/get-document-state) to read the document's `workbookModelId`
          2. [**`POST /api/v1/uploads`**](/api/uploads/upload-csv-file) with `modelId` set to the draft's workbook model (the connection decides warehouse staging vs S3-only)
          3. **`PATCH /api/v2/documents/{documentId}/draft`** *(this endpoint)* with a `csv` or `spreadsheet` tab whose `fileUploadId` is the upload ID returned in step 2
          4. [**`POST /api/v2/documents/{documentId}/draft/publish`**](/api/documents-v2/publish-draft) to publish

          ```json
          {
            "queryPresentations": {
              "data": {
                "2": {
                  "type": "csv",
                  "name": "Customer data",
                  "fileUploadId": "a1b2c3d4-5678-90ab-cdef-1234567890ab"
                }
              }
            }
          }
          ```

          Changing `fileUploadId` on an existing tab key repoints the tab at a different upload, regenerating the tab with the new upload's data. The same endpoints for [creating documents](/api/documents-v2/create-document) and [patching existing drafts](/api/documents-v2/patch-draft) accept `fileUploadId` on `csv` / `spreadsheet` tabs.
      security:
        - bearerAuth: []
      operationId: documentsV2PatchDraft
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: The published document's URL slug (e.g. `abc123`) or its canonical workbook UUID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentsV2CreateDraftBody'
            examples:
              metadataOnly:
                summary: Rename the document and tweak a setting
                value:
                  name: "Q2 Revenue (final)"
                  summary: "Rename and enable cross-filtering"
                  settings:
                    crossfilterEnabled: true
              contentOnly:
                summary: Add a tile and delete another
                value:
                  queryPresentations:
                    data:
                      "3":
                        type: "query"
                        name: "Orders by region"
                        topicName: "order_items"
                        query:
                          fields:
                            - "users.state"
                            - "order_items.count"
                      "4": null
              linkedSummary:
                summary: Attach an AI summary linked to tile "2"
                value:
                  queryPresentations:
                    data:
                      "5":
                        type: "linked"
                        name: "Summary"
                        sourceQueryPresentationKey: "2"
              onBranch:
                summary: Create the draft on a branch
                value:
                  branchId: "8b1f0a2c-1234-4abc-9def-0123456789ab"
                  name: "Q2 Revenue (branch edit)"
      responses:
        '200':
          description: Draft created and patch applied successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentsV2PatchDraftResponse'
              example:
                identifier: "q2-revenue"
                draftIdentifier: "q2-revenue-draft-7f3a"
                name: "Q2 Revenue (final)"
                description: "Quarterly revenue review"
        '400':
          description: |
            Invalid request body or schema validation error — for example an unknown top-level field, a length cap exceeded, or the query-presentation cap exceeded (`A single patch may carry at most 48 query presentations.`). Also returned when `modelId` doesn't match the document's current base model, when `fileUploadId` is provided on a non-csv/spreadsheet tab type, or when the upload ID doesn't correspond to an upload in the caller's organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to update the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Document or branch not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '409':
          description: |
            The target is not a published document (drafts only attach to published documents) — for example a draft already exists for this document — or a concurrent request just created its layout; retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: |
            The document cannot satisfy the patch: a classic-layout dashboard (upgrade to the advanced layout first), an app document, or a workbook-only document patched without a `containers` payload (or with an empty one).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v2/documents/{documentId}/draft/publish:
    post:
      tags:
        - Documents v2
      summary: Publish draft
      description: |
        Publish the document's current main (non-branch) draft, promoting it to the published version. No request body — the draft is consumed, so the response echoes the now-published document's metadata.

        Only the main draft is publishable with this endpoint. A branch-attached draft is published by [merging its branch](/api/model-branches/merge-a-branch).
      security:
        - bearerAuth: []
      operationId: documentsV2PublishDraft
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: The published document's URL slug (e.g. `abc123`) or its canonical workbook UUID.
      responses:
        '200':
          description: Draft published successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentsV2PublishDraftResponse'
              example:
                identifier: "q2-revenue"
                name: "Q2 Revenue (final)"
                description: "Quarterly revenue review"
        '400':
          description: |
            The document requires a pull request to publish (detail: "Can't publish because this document can only be edited through a branch").
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to publish the draft.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: |
            Document not found, or it has no main draft to publish (a branch-attached draft is published by merging its branch).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '409':
          description: The target is not a published document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v2/documents/{documentId}/draft/{draftId}:
    get:
      tags:
        - Documents v2
      summary: Get draft state
      description: |
        Read the named draft's state. Returns the same `DocumentsV2ReadResponse` shape as [Get document state](/api/documents-v2/get-document-state), and is round-trippable as a `PATCH` body.
      security:
        - bearerAuth: []
      operationId: documentsV2GetDraft
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: The published document's URL slug (e.g. `abc123`) or its canonical workbook UUID.
        - name: draftId
          in: path
          required: true
          schema:
            type: string
          description: The draft workbook identifier (returned by [Create draft and patch document](/api/documents-v2/create-draft-and-patch-document)).
        - name: pretty
          in: query
          required: false
          schema:
            type: boolean
          description: Format response with 2-space indentation for readability. Accepts `true`, `1`, or no value (e.g., `?pretty`). Default is compact (single-line) JSON.
      responses:
        '200':
          description: Draft state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentsV2ReadResponse'
        '401':
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to read the draft.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Document or draft not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: |
            The draft cannot be read as a dashboard: a classic-layout dashboard (upgrade to the advanced layout first) or an app document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    patch:
      tags:
        - Documents v2
      summary: Patch draft
      description: |
        Apply a patch to an existing draft. Pure apply — no draft creation, no publish. The request body, content sections, field caps, and tile-addressing rules are identical to [Create draft and patch document](/api/documents-v2/create-draft-and-patch-document); this route does not accept `branchId`.
      security:
        - bearerAuth: []
      operationId: documentsV2PatchDraftByIdentifier
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: The published document's URL slug (e.g. `abc123`) or its canonical workbook UUID.
        - name: draftId
          in: path
          required: true
          schema:
            type: string
          description: The draft workbook identifier the patch is applied to.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentsV2PatchDraftBody'
            examples:
              contentOnly:
                summary: Update a tile's subtitle
                value:
                  queryPresentations:
                    data:
                      "2":
                        type: "query"
                        name: "Revenue by month"
                        subTitle: "Trailing 12 months"
                        topicName: "order_items"
                        query:
                          fields:
                            - "order_items.created_at[month]"
                            - "order_items.sale_price_sum"
      responses:
        '200':
          description: Patch applied to draft successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentsV2PatchDraftResponse'
        '400':
          description: |
            Invalid request body or schema validation error — for example an unknown top-level field, a length cap exceeded, or the query-presentation cap exceeded (`A single patch may carry at most 48 query presentations.`). Also returned when `modelId` doesn't match the document's current base model, when `fileUploadId` is provided on a non-csv/spreadsheet tab type, or when the upload ID doesn't correspond to an upload in the caller's organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to update the draft.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Document or draft not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '409':
          description: |
            The target is not a published document (drafts only attach to published documents), or a concurrent request just created its layout; retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: |
            The draft cannot satisfy the patch: a classic-layout dashboard (upgrade to the advanced layout first), an app document, or a workbook-only draft patched without a `containers` payload (or with an empty one).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  
  /v2/documents/{documentId}/draft/{draftId}/dashboard:
    delete:
      description: |-
        Remove the dashboard from an existing draft, leaving a workbook-only document. Documents that are already workbook-only will remain unchanged.

        Use the [Publish draft endpoint](/api/documents-v2/publish-draft) to apply the changes from this endpoint to the published version of the document. **Note**: Schedules will be removed when the document is published.
      operationId: documentsV2RemoveDashboard
      summary: Remove dashboard from document
      tags:
        - Documents
      parameters:
        - schema:
            type: string
            example: def456
          required: true
          description: ID of the existing draft, created from a successful call to the [Create draft and patch document endpoint](/api/documents-v2/create-draft-and-patch-document).
          name: draftId
          in: path
        - schema:
            type: string
            example: abc123
          required: true
          description: ID of the published document associated with the draft.
          name: documentId
          in: path
      responses:
        '200':
          description: Dashboard removed on the draft. The response includes the `draftIdentifier`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentsV2PatchDraftResponse'
        '401':
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError401'
        '403':
          description: Insufficient permissions to update the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError403'
        '404':
          description: Document or draft not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError404'
        '405':
          description: Method not allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: The specified document is not a published document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError409'
        '422':
          description: The document is an app, not a dashboard.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /v2/documents/{documentId}/identifier:
    put:
      tags:
        - Documents v2
      summary: Rename document identifier
      description: |-
        Rename a published document's identifier. The change is applied live and immediately. Changes do not go through the draft/publish workflow. The former identifier is recorded in the document's rename history to enable automatic redirects. Only published documents can be renamed.
        
        The new identifier must be a valid slug and unused by any other document in the organization. If the new value is identical to the existing value (case-sensitive), the API will return a `200` but not write a history record.
      security:
        - bearerAuth: []
      operationId: documentsV2UpdateIdentifier
      parameters:
        - schema:
            type: string
          required: true
          description: Document identifier — either the URL slug (e.g. `abc123`) or the canonical workbook UUID.
          name: identifier
          in: path
          example: q2-revenue
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - identifier
              additionalProperties: false
              properties:
                identifier:
                  type: string
                  minLength: 2
                  maxLength: 48
                  pattern: "^[a-z0-9_-]+$"
                  description: |-
                    Document identifier slug. Must be 2–48 characters, containing only lowercase letters (a-z), digits (0-9), hyphens (-), and underscores (_). Cannot be a reserved value. If omitted when creating a document, an identifier is auto-generated.
                  example: q2-revenue
            examples:
              renameDocument:
                summary: Rename a document identifier
                value:
                  identifier: "new-q2-revenue"
              caseChange:
                summary: Case-only change (case-sensitive rename)
                value:
                  identifier: "Q2-Revenue"
      responses:
        '200':
          description: Identifier renamed successfully.
          content:
            application/json:
              schema:
                type: object
                required:
                  - description
                  - identifier
                  - name
                properties:
                  identifier:
                    type: string
                    description: The document identifier after the rename.
                    example: new-slug
                  name:
                    type: string
                    description: Document name.
                    example: Q2 Revenue Analysis
                  description:
                    type:
                      - string
                      - "null"
                    description: Document description.
              example:
                identifier: "new-q2-revenue"
                name: "Q2 Revenue Analysis"
                description: "Quarterly revenue review"        
        '400':
          description: |-
            Invalid identifier format — must be 2–48 characters, contain only lowercase letters, digits, hyphens, and underscores, and not be a reserved value.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "identifier must be 2-48 characters"
                status: 400
        '401':
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions to rename the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "You do not have permission to update this document"
                status: 403
        '404':
          description: Document not found or archived.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: "Document not found"
                status: 404
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '409':
          description: |-
            The target is a draft rather than a published document, or the requested identifier is already in use by another document in the organization (case-insensitive collision).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                collision:
                  summary: Identifier already in use
                  value:
                    detail: "identifier is already in use"
                    status: 409
                draftTarget:
                  summary: Target is not published
                  value:
                    detail: "Cannot rename identifier of non-published document"
                    status: 409
        '429':
          $ref: '#/components/responses/TooManyRequests'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Can be either an [Organization API Key](/api/authentication#organization-api-keys) or [Personal Access Token (PAT)](/api/authentication#token-types).

        Include in the `Authorization` header as: `Bearer YOUR_TOKEN`
    orgApiKey:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Requires an [Organization API Key](/api/authentication#organization-api-keys). Personal Access Tokens (PATs) are not supported for this endpoint.

        Include in the `Authorization` header as: `Bearer ORGANIZATION_API_KEY`

  parameters:
    sortDirection:
      name: sortDirection
      in: query
      schema:
        type: string
        enum: [asc, desc]
      description: |
        Direction for sorting:

        - `asc` - Ascending order (A-Z, 0-9)
        - `desc` - Descending order (Z-A, 9-0)

  schemas:
    AiCreditControlsResponse:
      type: object
      required:
        - accountCreditLimit
        - creditsUsed
        - downgradeCredits
        - entityGroupDefaultCredits
        - periodEnd
        - periodStart
        - shutoffCredits
        - userDefaultCredits
      properties:
        accountCreditLimit:
          type: number
          minimum: 0
          description: Monthly AI credit limit for the whole Omni account, which is shared across every organization associated with the account. `0` when no limit is configured.
          example: 2000
        creditsUsed:
          type: number
          minimum: 0
          description: This organizations's credit usage for the current billing period.
          example: 450
        downgradeCredits:
          type: number
          nullable: true
          minimum: 0
          description: Downgrade threshold, or `null` if the downgrade control is off.
          example: 800
        entityGroupDefaultCredits:
          type: number
          nullable: true
          minimum: 0
          description: Default per-entity-group AI credit limit, or `null` when entity groups are unlimited by default.
          example: 100
        periodEnd:
          type: integer
          minimum: 0
          description: End of the current billing period as a Unix ms timestamp (UTC calendar-month boundary).
        periodStart:
          type: integer
          minimum: 0
          description: Start of the current billing period as a Unix ms timestamp (UTC calendar-month boundary).
        shutoffCredits:
          type: number
          nullable: true
          minimum: 0
          description: Shutoff threshold, or `null` if the shutoff control is off.
          example: 1200
        userDefaultCredits:
          type: number
          nullable: true
          minimum: 0
          description: Default per-user AI credit limit, or `null` when users are unlimited by default.
          example: 100

    DocumentAbilities:
      type: object
      description: |
        Document-level ability values, as stored on the document. These settings control what actions users can perform on a document, and correspond to the toggles in the **Abilities** section of the document's settings.
      required:
        - canAnalyze
        - canDownload
        - canDrill
        - canDuplicate
        - canRequestAccess
        - canSaveSpreadsheets
        - canSchedule
        - canUpload
        - canUseDashboardAi
        - canUseTimezoneOverride
        - canViewWorkbook
        - requirePullRequestToPublish
      properties:
        canAnalyze:
          type: boolean
          description: Allow exploring from this document
        canDownload:
          type: boolean
          description: Allow downloading
        canDrill:
          type: boolean
          description: Allow drill-down
        canDuplicate:
          type: boolean
          description: Allow duplicating
        canRequestAccess:
          type: boolean
          description: Allow requesting access
        canSaveSpreadsheets:
          type: boolean
          description: Allow creating spreadsheets
        canSchedule:
          type: boolean
          description: Allow scheduling
        canUpload:
          type: boolean
          description: Allow uploads
        canUseDashboardAi:
          type: boolean
          description: Allow using dashboard AI
        canUseTimezoneOverride:
          type: boolean
          description: Allow timezone override
        canViewWorkbook:
          type: boolean
          description: Allow viewing workbook
        requirePullRequestToPublish:
          type: boolean
          description: Require pull request to publish changes

    DocumentsV2CreateBody:
      type: object
      required:
        - modelId
        - name
      additionalProperties: false
      properties:
        modelId:
          type: string
          format: uuid
          description: Base workbook model the document is built on — a SHARED model, or a SHARED_EXTENSION with `allowAsWorkbookBase = true`.
        name:
          type: string
          minLength: 1
          maxLength: 254
          description: Document name.
        identifier:
          type: string
          pattern: "^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$"
          description: |
            Identifier (URL slug) for the new document. Must be unique within the organization; auto-generated when omitted. Lowercase letters, numbers, hyphens, and underscores only; cannot start or end with a hyphen or underscore.
        description:
          type:
            - string
            - "null"
          description: Document description.
        folderId:
          type:
            - string
            - "null"
          format: uuid
          description: Folder to create the document in. When omitted, defaults to the caller's personal "My documents" (requires permission to save personal content — otherwise the request is rejected).
        summary:
          type: string
          minLength: 1
          maxLength: 255
          description: Caller-supplied note describing the create, written to the history audit trail. When omitted, Omni auto-fills it with `"Created document"`.
        queryPresentations:
          $ref: '#/components/schemas/DocumentsV2QueryPresentationsPatch'
        controls:
          $ref: '#/components/schemas/DocumentsV2ControlsPatch'
        settings:
          $ref: '#/components/schemas/DocumentsV2SettingsPatch'
        containers:
          $ref: '#/components/schemas/DocumentsV2Containers'

    DocumentsV2PatchDraftBody:
      type: object
      additionalProperties: false
      description: The content/metadata diff applied to a draft. All fields optional — only the sections present are changed.
      properties:
        modelId:
          type: string
          format: uuid
          description: Immutable model identifier. Must match the document's current base model or the API will return a 400 error.
        workbookModelId:
          type: string
          format: uuid
          description: Immutable model identifier. Must match the document's current workbook model or the API will return a 400 error.
        name:
          type: string
          minLength: 1
          maxLength: 254
          description: Document name.
        description:
          type:
            - string
            - "null"
          description: Document description.
        summary:
          type: string
          minLength: 1
          maxLength: 255
          description: Caller-supplied description of what this patch changes, written to the history audit trail. When omitted, Omni auto-generates one from the updated sections.
        queryPresentations:
          $ref: '#/components/schemas/DocumentsV2QueryPresentationsPatch'
        controls:
          $ref: '#/components/schemas/DocumentsV2ControlsPatch'
        settings:
          $ref: '#/components/schemas/DocumentsV2SettingsPatch'
        containers:
          allOf:
            - $ref: '#/components/schemas/DocumentsV2Containers'
            - description: Container layout. When present, fully replaces the existing layout.

    DocumentsV2CreateDraftBody:
      type: object
      additionalProperties: false
      description: The draft-route patch body plus an optional `branchId` selecting the branch the new draft is attached to.
      properties:
        branchId:
          type: string
          format: uuid
          description: Branch the draft is created on. Omit for a draft on the main (unpublished) workspace.
        modelId:
          type: string
          format: uuid
          description: Immutable model identifier. Must match the document's current base model or the API will return a 400 error.
        workbookModelId:
          type: string
          format: uuid
          description: Immutable model identifier. Must match the document's current workbook model or the API will return a 400 error.
        name:
          type: string
          minLength: 1
          maxLength: 254
          description: Document name.
        description:
          type:
            - string
            - "null"
          description: Document description.
        summary:
          type: string
          minLength: 1
          maxLength: 255
          description: Caller-supplied description of what this patch changes, written to the history audit trail. When omitted, Omni auto-generates one from the updated sections.
        queryPresentations:
          $ref: '#/components/schemas/DocumentsV2QueryPresentationsPatch'
        controls:
          $ref: '#/components/schemas/DocumentsV2ControlsPatch'
        settings:
          $ref: '#/components/schemas/DocumentsV2SettingsPatch'
        containers:
          allOf:
            - $ref: '#/components/schemas/DocumentsV2Containers'
            - description: Container layout. When present, fully replaces the existing layout.

    DocumentsV2QueryPresentationsPatch:
      type: object
      description: Query presentations (tabs/tiles) keyed by record key.
      properties:
        data:
          type: object
          description: |
            Tabs keyed by record key (a positive-integer string, e.g. `"1"`, `"2"`). Shallow-merged by key — omitted keys are untouched; set a key to `null` to delete that tab. Capped at 48 non-null entries per patch.
          additionalProperties:
            oneOf:
              - $ref: '#/components/schemas/DocumentsV2QueryPresentation'
              - type: "null"
        order:
          type: array
          description: Tab display order. When present, replaces the existing order.
          items:
            type: string
            pattern: "^[1-9][0-9]*$"

    DocumentsV2QueryPresentationsRead:
      type: object
      required:
        - data
        - order
      properties:
        data:
          type: object
          description: Tabs keyed by record key (a positive-integer string).
          additionalProperties:
            $ref: '#/components/schemas/DocumentsV2QueryPresentation'
        order:
          type: array
          description: Tab display order.
          items:
            type: string
            pattern: "^[1-9][0-9]*$"

    DocumentsV2QueryPresentation:
      type: object
      required:
        - type
      description: |
        A single tab/tile. The server owns internal tile identity — callers never send `miniUuid`; tiles are addressed by their record key in `queryPresentations.data`.
      properties:
        type:
          type: string
          enum:
            - blank
            - csv
            - query
            - dataset
            - spreadsheet
            - sql
            - dbt
            - query-view
            - linked
            - app
          description: The query presentation type. `query` is a semantic query; `sql` is raw SQL; `linked` reuses another tile's query (see `sourceQueryPresentationKey`).
        name:
          type: string
          minLength: 1
          maxLength: 144
          description: User-provided tab name.
        subTitle:
          type:
            - string
            - "null"
          maxLength: 250
          description: User-provided tab subtitle.
        description:
          type:
            - string
            - "null"
          maxLength: 500
          description: User-provided tab description.
        sourceQueryPresentationKey:
          type:
            - string
            - "null"
          pattern: "^[1-9][0-9]*$"
          description: |
            For `linked`-type tabs, the record key — the same identifier used as a `queryPresentations.data` key — of the source tile whose query this tab reuses. Null for all other tab types. This is a tile record key, **not** a positional index into `order`.
        topicName:
          type:
            - string
            - "null"
          description: The topic (explore) this query is built on.
        isSql:
          type:
            - boolean
            - "null"
          description: Whether this tab is in raw SQL mode.
        prefersChart:
          type: boolean
          description: When true, the chart view is shown by default instead of the data table.
        automaticVis:
          type:
            - boolean
            - "null"
          description: When true, the system automatically selects the best visualization type.
        filterOrder:
          type: array
          items:
            type: string
          description: Ordered list of filter field names controlling display order on this tab.
        editingModelObjectName:
          type:
            - string
            - "null"
          description: Model object (view/topic) currently being edited via the dataset/query-view editor. Applies only to `dataset` / `query-view` tabs — omitted from reads and rejected on patches for other tab types.
        editingModelObjectNameChange:
          type:
            - string
            - "null"
          description: Pending rename of the model object being edited. Applies only to `dataset` / `query-view` tabs — omitted from reads and rejected on patches for other tab types.
        fileUploadId:
          type:
            - string
            - "null"
          format: uuid
          description: The ID of the upload (from POST /v1/uploads) backing this tab's data. Applies only to `csv` and `spreadsheet` tabs — omitted from reads and rejected on patches for other tab types. On patch, providing it on a new tab key binds that upload as the tab's Source; changing it on an existing key repoints the tab at a different upload (regenerating the tab, same semantics as any other content change).
        query:
          type:
            - object
            - "null"
          additionalProperties: true
          description: The semantic query for this tab (fields, filters, sorts, limits, etc.), minus the server-owned workbook model anchors. Omitted on a no-op replay; the server anchors new/changed tiles to the draft.
        visConfig:
          type:
            - object
            - "null"
          additionalProperties: true
          description: Visualization configuration for the tile.
        resultConfig:
          type: object
          additionalProperties: true
          description: Result display configuration (column widths, frozen columns, conditional formatting, number formatting, etc.).
        aiConfig:
          type:
            - object
            - "null"
          additionalProperties: true
          description: AI-generated metadata config (subtitle/description auto-generation settings).
        model_extension_id:
          type:
            - string
            - "null"
          description: |
            Identifies the query model bound to this tile. Returned on read (omitted for `linked`-type tiles, whose query resolves from the source tile). This value is read-only and managed by Omni - any value provided in write requests will be ignored.

    DocumentsV2ControlsPatch:
      type: object
      description: Dashboard filters/controls keyed by control ID.
      properties:
        data:
          type: object
          description: Controls keyed by control ID. Shallow-merged by key — omitted keys are untouched; set to `null` to delete.
          additionalProperties:
            oneOf:
              - $ref: '#/components/schemas/DocumentsV2Control'
              - type: "null"
        order:
          type: array
          description: Display order for controls. When present, replaces the existing order.
          items:
            type: string

    DocumentsV2ControlsRead:
      type: object
      required:
        - data
        - order
      properties:
        data:
          type: object
          description: Controls keyed by control ID.
          additionalProperties:
            $ref: '#/components/schemas/DocumentsV2Control'
        order:
          type: array
          description: Display order for controls.
          items:
            type: string

    DocumentsV2Control:
      type: object
      additionalProperties: true
      description: |
        A dashboard control (filter). The full config grammar — control type, field bindings, filter values, and (for multi-field filters) the recursive composite-filter grammar — is validated by the server on apply. See the dashboard filters & controls reference for the complete structure.

    DocumentsV2SettingsPatch:
      type: object
      description: Document settings. Shallow-merged with the existing settings.
      properties:
        crossfilterEnabled:
          type: boolean
          description: When true, clicking a value in one tile filters all other tiles on the dashboard.
        customText:
          type:
            - object
            - "null"
          description: Custom text replacing default UI strings on the dashboard.
          properties:
            queryError:
              type: string
              description: Custom text shown when a query errors, replacing the default error text.
            queryNoResults:
              type: string
              description: Custom text shown when a query returns no results, replacing the default empty state.
        facetFilters:
          type: boolean
          description: When true, dashboard filters are applied per-facet when faceting is active.
        refreshInterval:
          type:
            - number
            - "null"
          description: Auto-refresh interval in seconds. Null disables auto-refresh.
        runQueriesOn:
          type:
            - string
            - "null"
          enum:
            - current-page
            - all-pages
            - null
          description: Controls whether dashboard queries execute on the visible page or across all pages.

    DocumentsV2SettingsRead:
      type: object
      required:
        - crossfilterEnabled
        - customText
        - facetFilters
        - refreshInterval
        - runQueriesOn
      properties:
        crossfilterEnabled:
          type: boolean
          description: When true, clicking a value in one tile filters all other tiles on the dashboard.
        customText:
          type:
            - object
            - "null"
          description: Custom text replacing default UI strings on the dashboard.
          properties:
            queryError:
              type: string
            queryNoResults:
              type: string
        facetFilters:
          type: boolean
          description: When true, dashboard filters are applied per-facet when faceting is active.
        refreshInterval:
          type:
            - number
            - "null"
          description: Auto-refresh interval in seconds. Null disables auto-refresh.
        runQueriesOn:
          type:
            - string
            - "null"
          enum:
            - current-page
            - all-pages
            - null
          description: Controls whether dashboard queries execute on the visible page or across all pages.

    DocumentsV2Containers:
      type: array
      description: |
        Container layout array (grid / stack / page / reference containers, recursively nested). On a patch, when present this **fully replaces** the existing layout. The server validates the full structure on apply; container `instanceKey` / `referenceKey` values round-trip unchanged. See the dashboard layout reference for the complete grammar.
      items:
        type: object
        additionalProperties: true

    DocumentsV2ReadResponse:
      type: object
      required:
        - description
        - name
        - queryPresentations
      description: |
        Current document (or draft) state. Round-trippable: submit this verbatim as a draft `PATCH` body. `containers`, `controls`, and `settings` are dashboard-scoped and omitted for workbook-only documents with no layout yet.
      properties:
        name:
          type: string
          maxLength: 254
          description: Document name.
        description:
          type:
            - string
            - "null"
          description: Document description.
        modelId:
          type: string
          format: uuid
          description: |
            The `SHARED` or `SHARED_EXTENSION` model the workbook is built on.
        workbookModelId:
          type: string
          format: uuid
          description: |
            The workbook-layer model ID. Each document has its own workbook model for document-specific fields and calculations. For drafts, this returns the draft's workbook model ID, not the published document's.
        queryPresentations:
          $ref: '#/components/schemas/DocumentsV2QueryPresentationsRead'
        controls:
          $ref: '#/components/schemas/DocumentsV2ControlsRead'
        settings:
          $ref: '#/components/schemas/DocumentsV2SettingsRead'
        containers:
          $ref: '#/components/schemas/DocumentsV2Containers'

    DocumentsV2CreateResponse:
      type: object
      required:
        - description
        - identifier
        - name
      properties:
        identifier:
          type: string
          description: Identifier of the newly created document.
        name:
          type: string
          description: Document name.
        description:
          type:
            - string
            - "null"
          description: Document description.

    DocumentsV2PatchDraftResponse:
      type: object
      required:
        - description
        - draftIdentifier
        - identifier
        - name
      properties:
        identifier:
          type: string
          description: Published document identifier the draft targets.
          example: "def456"
        draftIdentifier:
          type: string
          description: Identifier of the draft the patch was applied to.
          example: "abc123"
        name:
          type: string
          description: Document name.
          example: "Blob Sales"
        description:
          type:
            - string
            - "null"
          description: Document description.
          example: "Overview of daily Blobs R Us Sales"

    DocumentsV2PublishDraftResponse:
      type: object
      required:
        - description
        - identifier
        - name
      properties:
        identifier:
          type: string
          description: Published document identifier.
        name:
          type: string
          description: Document name.
        description:
          type:
            - string
            - "null"
          description: Document description.

    AiPickTopicBody:
      type: object
      required:
        - prompt
        - modelId
      properties:
        branchId:
          type: string
          format: uuid
          description: Optional branch ID for the model. Must be a branch of the shared model specified by modelId.
          example: 550e8400-e29b-41d4-a716-446655440000
        currentTopicName:
          type: string
          description: The name of the current topic to scope query generation. If not provided, AI will automatically select the best topic for the prompt.
          example: order_items
        modelId:
          type: string
          format: uuid
          description: |
            The UUID of the shared model to query against. Note that:
            
            - Only shared models are supported
            - The user must have **Querier** permissions or higher on the model
          example: 770e8400-e29b-41d4-a716-446655440002
        potentialTopicNames:
          type: array
          items:
            type: string
          description: Optional list of topic names to limit consideration to. If not provided, all topics the user has access to in the model will be evaluated.
          example:
            - order_items
            - customers
            - products
        prompt:
          type: string
          description: The natural language prompt to analyze. The AI will determine which topic best matches the data described in this prompt.
          example: How many orders were placed last month?
        userId:
          type: string
          format: uuid
          description: |
            User ID to evaluate topic access as. Their permissions will be used for permission-aware topic selection.

            **Only valid with Organization API keys.** PATs always act as the authenticated user.
          example: 990e8400-e29b-41d4-a716-446655440004
        queryAllViews:
          type: boolean
          default: false
          description: |
            When `true` and the model's [`query_all_views_and_fields`](/modeling/models/parameters/ai-settings/query-all-views-and-fields) setting is enabled, allows the AI to access views that are not included in topics when selecting a topic. If the setting is disabled, this parameter has no effect.

            When both the API parameter and model setting are enabled:

            - The AI can select from any view in the model, including views not in topics
            - The AI will prefer topic-organized views unless the question clearly targets a standalone view

            **Note:** Users with topic-locked permissions cannot use this parameter, even if the model setting is enabled.

    AiPickTopicResponse:
      type: object
      required:
        - topicId
      properties:
        topicId:
          type: string
          description: |
            The name of the topic that best matches the prompt.

            Use this as the `topicName` parameter when calling the [Generate a query](/api/ai/generate-a-query) or the [Create AI job](/api/ai/create-ai-job) endpoints.
          example: order_items

    Dialect:
      type: string
      description: |
        The database dialect.
      enum:
        - athena
        - bigquery
        - clickhouse
        - databricks
        - databricks_lakebase
        - exasol
        - mariadb
        - motherduck
        - mssql
        - mysql
        - oracle
        - postgres
        - redshift
        - sap_hana
        - snowflake
        - starrocks
        - trino

    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true

    SchemaRefreshSchedule:
      type: object
      properties:
        scheduleId:
          type: string
          format: uuid
          description: The unique identifier of the schema refresh schedule
        connectionId:
          type: string
          format: uuid
          description: The unique identifier of the connection this schedule belongs to
        schedule:
          type: string
          description: |
            A 6-field cron expression in AWS EventBridge format defining when the schema refresh should run.

            Format: `minute hour day-of-month month day-of-week year`
        timezone:
          type: string
          description: The IANA timezone identifier for when the schedule should run
        description:
          type: string
          description: A human-readable description of the schedule, automatically generated from the cron expression and timezone
        hardRefresh:
          type: boolean
          default: false
          description: Whether the scheduled refresh performs a hard refresh (removes dropped objects) or soft refresh (additive only). Defaults to false (soft refresh).
        createdAt:
          type: string
          format: date-time
          description: The timestamp when the schedule was created
        updatedAt:
          type: string
          format: date-time
          description: The timestamp when the schedule was last updated
        disabledAt:
          type: string
          format: date-time
          nullable: true
          description: The timestamp when the schedule was disabled, or `null` if active

    DbtEnvironment:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier of the dbt environment
        name:
          type: string
          description: The name of the dbt environment
        isDefaultEnvironment:
          type: boolean
          description: Whether this is the default environment
        isDeferralEnabled:
          type: boolean
          description: |
            Whether deferral is enabled for this environment. Ignored (forced to `false`) for the default (production) environment.
        ownerId:
          type: string
          format: uuid
          nullable: true
          description: The user ID of the owner of the dbt environment, or null
        targetDatabase:
          type: string
          nullable: true
          description: Target database override
        targetName:
          type: string
          nullable: true
          description: Target name override
        targetRole:
          type: string
          nullable: true
          description: Target role override
        targetSchema:
          type: string
          description: Target schema for the environment
        variables:
          type: array
          description: Environment variables
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
                description: Variable identifier
              name:
                type: string
                description: Variable name
              value:
                type: string
                nullable: true
                description: |
                  Variable value. If `isSecret: true`, this value will be `null` in responses to protect sensitive information.
              isSecret:
                type: boolean
                description: |
                  Whether the variable value is secret.

    Error:
      type: object
      properties:
        error:
          type: string
          description: HTTP response code for the error
          example: "<response_code>"
        message:
          type: string
          description: Detailed error description
          example: "<error_reason>"

    DocumentsUpgradeLayoutBody:
      type: object
      properties:
        clearExistingDraft:
          type: boolean
          default: false
          description: When upgrading a published document, discard any existing draft instead of failing with a conflict.
    DocumentsUpgradeLayoutResponse:
      type: object
      properties:
        identifier:
          type: string
          description: Document identifier
        upgraded:
          type: boolean
          description: True when the layout was upgraded, false when the document already had advanced layout (no-op).
      required:
        - identifier
        - upgraded

    ApiError400:
      type: object
      properties:
        detail:
          type: string
          description: Human-readable error message describing what went wrong.
          # example: "Bad Request: prompt: Required"
        status:
          type: integer
          description: HTTP status code of the error.
          example: 400
      required:
        - detail
        - status

    ApiError401:
      type: object
      properties:
        detail:
          type: string
          description: Human-readable error message describing what went wrong.
          example: "Unauthorized: Missing or invalid API key"
        status:
          type: integer
          description: HTTP status code of the error.
          example: 401
      required:
        - detail
        - status

    ApiError403:
      type: object
      properties:
        detail:
          type: string
          description: Human-readable error message describing what went wrong.
          # example: "Forbidden: AI query generation is not enabled for this organization"
        status:
          type: integer
          description: HTTP status code of the error.
          example: 403
      required:
        - detail
        - status

    ApiError404:
      type: object
      properties:
        detail:
          type: string
          description: Human-readable error message describing what went wrong.
          # example: "Model 770e8400-e29b-41d4-a716-446655440002 not found"
        status:
          type: integer
          description: HTTP status code of the error.
          example: 404
      required:
        - detail
        - status

    ApiError410:
      description: |
        Endpoint has been removed; the organization does not hold a migration extension.

        This response includes the following headers:

        - `Deprecation` - `true`, indicating the endpoint is deprecated
        - `Sunset` - The date the endpoint was removed (`2026-07-31`)
        - `Link` - Link to the replacement API. For example, `</api/documents-v2/create-document>; rel="successor-version"`
      headers:
        Deprecation:
          description: Indicates that this endpoint is deprecated
          schema:
            type: string
            example: "true"
        Sunset:
          description: The date this endpoint will be removed (RFC 9110)
          schema:
            type: string
            format: date
            example: "2026-07-31"
        Link:
          description: Link to the replacement API (RFC 8288)
          schema:
            type: string
            example: |
              </api/documents-v2/create-document>; rel="successor-version"

    ApiError409:
      type: object
      properties:
        detail:
          type: string
          description: Human-readable error message describing what went wrong.
          # example: "An active job already exists for this conversation"
        status:
          type: integer
          description: HTTP status code of the error.
          example: 409
      required:
        - detail
        - status

    ApiError429:
      type: object
      properties:
        detail:
          type: string
          description: Human-readable error message describing what went wrong.
          # example: User has reached the maximum of 100 routines
        status:
          type: integer
          description: HTTP status code of the error.
          example: 429
      required:
        - detail
        - status

    QueryTimeoutResponse:
      type: object
      properties:
        detail:
          type: string
          description: Error message indicating the request timed out
          example: "Request timeout"
        remaining_job_ids:
          type: array
          items:
            type: string
            format: uuid
          description: IDs of jobs still processing when the request timed out. Poll the [Wait for query results endpoint](/api/queries/wait-for-query-results) with these IDs to retrieve results.
        timed_out:
          type: boolean
          description: Indicates that the request timed out. Will be `true` for 408 responses.

    ScheduleRecipientsEmail:
      type: object
      description: Response for schedules with email destinations.
      properties:
        type:
          type: string
          enum: [email]
          description: The destination type.
        recipients:
          type: array
          items:
            type: object
            properties:
              email:
                type: string
                format: email
                description: The recipient's email address.
              emailOnly:
                type: boolean
                description: Indicates whether the recipient is an email-only user.
              id:
                type: string
                format: uuid
                description: The recipient's unique identifier.
              name:
                type: string
                description: The recipient's name.

    ScheduleRecipientsSftp:
      type: object
      description: Response for schedules with SFTP destinations.
      properties:
        type:
          type: string
          enum: [sftp]
          description: The destination type.
        address:
          type: string
          description: The SFTP server address.
        port:
          type: integer
          description: The SFTP server port.
        username:
          type: string
          description: The SFTP username.

    ScheduleRecipientsSlack:
      type: object
      description: Response for schedules with Slack destinations.
      properties:
        type:
          type: string
          enum: [slack]
          description: The destination type.
        recipients:
          type: array
          items:
            type: object
            properties:
              recipientType:
                type: string
                enum: [channel, users]
                description: |
                  The type of Slack recipient:
                  - `channel` - A Slack channel
                  - `users` - A Slack user
              slackId:
                type: string
                description: The Slack channel or user ID.

    ScheduleRecipientsWebhook:
      type: object
      description: Response for schedules with webhook destinations.
      properties:
        type:
          type: string
          enum: [webhook]
          description: The destination type.
        url:
          type: string
          format: uri
          description: The webhook URL.

    ScheduleRecipientsS3:
      type: object
      description: Response for schedules with Amazon S3 destinations.
      properties:
        type:
          type: string
          enum: [s3]
          description: The destination type.
        bucketName:
          type: string
          description: The name of the S3 bucket.
        region:
          type: string
          description: The AWS region of the S3 bucket.
        roleArn:
          type: string
          description: The ARN of the customer's IAM role.
        externalId:
          type: string
          description: The external ID for IAM trust policy configuration.
        keyPrefix:
          type: string
          description: The folder path prefix for uploaded files.
        filename:
          type: string
          description: The filename template using Mustache syntax.

    DashboardFiltersResponse:
      type: object
      properties:
        identifier:
          type: string
          description: The dashboard identifier
        filters:
          type: object
          description: Map of filter ID to filter configuration
          additionalProperties:
            $ref: '#/components/schemas/DashboardFilter'
        controls:
          type: array
          description: Array of control configurations
          items:
            $ref: '#/components/schemas/DashboardControl'
        filterOrder:
          type: array
          description: Ordered list of filter and control IDs for display
          items:
            type: string

    DashboardFilter:
      type: object
      description: |
        Filter configuration.
      properties:
        type:
          type: string
          description: |
            The filter data type.

            - `string`: Text-based filters (equals, contains, etc.)
            - `number`: Numeric filters (equals, between, etc.)
            - `date`: Date range filters
            - `boolean`: True/false filters
            - `null`: Null check filters
            - `by_query`: Query-based dynamic filters
            - `user_attribute`: Filters based on user attributes
            - `composite`: Combined filters
          enum:
            - string
            - number
            - date
            - boolean
            - "null"
            - by_query
            - user_attribute
            - composite
        kind:
          type: string
          description: The filter operation kind (e.g., `EQUALS`, `WITHIN_RANGE`)
        values:
          type: array
          description: Default values for string/number filters
          items:
            type: string
        left_side:
          type: string
          description: Start value for date range filters
        right_side:
          type: string
          description: End value for date range filters
        label:
          type: string
          description: Display label
        description:
          type: string
          description: Help text description
        required:
          type: boolean
          description: Whether the filter is required
        requiredScope:
          type: string
          enum:
            - dashboard
            - tiles
          description: |
            Controls what an unset required filter blocks. Only meaningful when `required` is `true`.

            - `dashboard` - An unset filter blocks the entire dashboard
            - `tiles` - An unset filter only blocks the tiles the filter is mapped to
        hidden:
          type: boolean
          description: Whether the filter is hidden

    DashboardControl:
      type: object
      description: |
        Control configuration.
      properties:
        id:
          type: string
          description: Control identifier
        type:
          type: string
          description: |
            The control type:

            - `FIELD_SELECTION`: Single field selector dropdown
            - `MULTI_FIELD_SELECTION`: Parent control for grouping child controls
            - `FIELD_PICKER`: Multi-select field picker
            - `PERIOD_OVER_PERIOD`: Time comparison control
          enum:
            - FIELD_SELECTION
            - MULTI_FIELD_SELECTION
            - FIELD_PICKER
            - PERIOD_OVER_PERIOD
        kind:
          type: string
          description: |
            The control kind, ex: `FIELD`
        label:
          type: string
          description: Display label
        fieldOrder:
          type: string
          enum:
            - query
            - control
          description: |
            Controls how fields are ordered when selected from the control. When `control`, fields are added in the order they are selected from the control. When `query`, fields already in the query retain their position and the control only adds or removes fields. See the [Dashboard control documentation](/visualize-present/dashboards/controls#multi-field-reordering) for more information.
        description:
          type: string
          description: Help text description
        hidden:
          type: boolean
          description: Whether the control is hidden
        field:
          type: string
          description: |
            **Applicable to `FIELD_SELECTION` controls.** The currently selected field.
        options:
          type: array
          description: Available options for field selection
          items:
            type: object
            properties:
              label:
                type: string
                description: Option display label
              value:
                type: string
                description: Option value

    DashboardFiltersUpdateRequest:
      type: object
      description: Request body for updating dashboard filters and controls. At least one of `filters`, `controls`, or `filterOrder` must be provided with at least one entry.
      properties:
        clearExistingDraft:
          type: boolean
          default: false
          description: When `true`, discards any existing draft before applying updates
        filters:
          type: object
          description: Map of filter ID to partial update
          additionalProperties:
            $ref: '#/components/schemas/DashboardFilterUpdate'
        controls:
          type: object
          description: Map of control ID to partial update
          additionalProperties:
            $ref: '#/components/schemas/DashboardControlUpdate'
        filterOrder:
          type: array
          description: New display order (filter and control IDs)
          items:
            type: string

    DashboardFilterUpdate:
      type: object
      description: Partial update for a dashboard filter
      properties:
        values:
          type: array
          description: New default values for string/number filters
          items:
            type: string
        left_side:
          type: string
          description: New start value for date filters
        right_side:
          type: string
          description: New end value for date filters
        label:
          type: string
          description: Display label
        description:
          type: string
          description: Help text description
        required:
          type: boolean
          description: Whether the filter is required
        requiredScope:
          type: string
          enum:
            - dashboard
            - tiles
          description: |
            Controls what an unset required filter blocks. Only meaningful when `required` is `true`.

            - `dashboard` - An unset filter blocks the entire dashboard
            - `tiles` - An unset filter only blocks the tiles the filter is mapped to
        hidden:
          type: boolean
          description: Whether to hide the filter

    DashboardControlUpdate:
      type: object
      description: Partial update for a dashboard control
      properties:
        label:
          type: string
          description: Display label
        description:
          type: string
          description: Help text description
        hidden:
          type: boolean
          description: Whether to hide the control

    PageInfo:
      type: object
      description: Pagination information for paginated responses.
      properties:
        hasNextPage:
          type: boolean
          description: Indicates if there are more records available.
        nextCursor:
          type: string
          nullable: true
          description: Cursor for the next page of results. `null` if no more results.
        pageSize:
          type: integer
          description: Number of records per page.
        totalRecords:
          type: integer
          description: Total number of records matching the query.

    DocumentsListResponse:
      type: object
      properties:
        pageInfo:
          allOf:
            - $ref: '#/components/schemas/PageInfo'
            - description: Pagination information
        records:
          type: array
          items:
            $ref: '#/components/schemas/Document'
          description: List of documents
      required:
        - pageInfo
        - records

    Document:
      type: object
      properties:
        _count:
          type: object
          properties:
            favorites:
              type: number
              description: Number of users who favorited this document
            views:
              type: number
              description: Number of views
          required:
            - favorites
            - views
          description: "Document counts (included when `_count` is in `include` param)"
        connectionId:
          type: string
          description: Connection ID the document is associated with
        deleted:
          type: boolean
          description: Whether the document is deleted (archived)
        folder:
          $ref: '#/components/schemas/DocumentFolder'
        hasDashboard:
          type: boolean
          description: Whether the document has an associated dashboard
        identifier:
          type: string
          description: Document identifier
          example: "abc123"
        labels:
          type: array
          items:
            type: string
          description: "Labels applied to the document (included when `labels` is in `include` param)"
        name:
          type: string
          description: Document name
        description:
          type: string
          description: Description of the document
        owner:
          $ref: '#/components/schemas/DocumentOwner'
        scope:
          type: string
          enum:
            - restricted
            - organization
          description: Document access scope
        type:
          type: string
          enum:
            - document
          description: Content type
        updatedAt:
          type:
            - string
            - "null"
          format: date-time
          description: Last updated timestamp
        url:
          type: string
          description: "URL to view the document. Returns dashboard URL if document has a dashboard, otherwise workbook URL."
          example: "https://org.omni.co/dashboards/abc123"
      required:
        - connectionId
        - deleted
        - folder
        - hasDashboard
        - identifier
        - name
        - owner
        - scope
        - type
        - updatedAt
        - url

    DocumentFolder:
      type:
        - object
        - "null"
      description: Folder containing the document
      properties:
        id:
          type: string
          description: Folder ID
        name:
          type: string
          description: Folder name
        path:
          type: string
          description: Folder path
        scope:
          type: string
          enum:
            - restricted
            - organization
          description: Folder access scope
      required:
        - id
        - name
        - path
        - scope

    DocumentOwner:
      type: object
      description: Document owner
      properties:
        id:
          type: string
          description: Owner membership ID
        name:
          type: string
          description: Owner display name
      required:
        - id
        - name

    PaginatedResponse:
      type: object
      properties:
        records:
          type: array
          items:
            type: object
        pageInfo:
          $ref: '#/components/schemas/PageInfo'

    Connection:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: "c0f12353-4817-4398-bcc0-d501e6dd2f64"
          description: The connection's unique ID
        name:
          type: string
          example: "Production Database"
          description: A descriptive name for the connection
        dialect:
          type: string
          example: "postgres"
          description: The type of database
        database:
          type: string
          example: "prod_db"
          description: The default database/catalog to connect to.
        baseRole:
          type: string
          example: "NO_ACCESS"
          description: The default role for users accessing the connection.
        createdAt:
          type: string
          format: date-time
          example: "2026-06-01T14:30:00.000Z"
          description: The time the connection was created.
        updatedAt:
          type: string
          format: date-time
          example: "2026-06-11T10:15:30.000Z"
          description: The time the connection was last updated.
        deletedAt:
          type: string
          format: date-time
          example: null
          description: The time the database was deleted.
        userAttributeNameForConnectionEnvironments:
          type: string
          example: "environment"
        userAttributeValuesForDefaultEnvironment:
          type: string
        branchConnectionEnvironmentOverridesUserAttr:
          type: boolean
          example: "false"
        environmentConnectionSwitchesSchemaModel:
          type: boolean
          example: "false"
        defaultSchema:
          type: string
          example: "public"
          description: The default schema to use.

    Folder:
      type: object
      description: Represents a folder in Omni.
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the folder.
        name:
          type: string
          description: Display name of the folder.
        path:
          type: string
          description: Full path to the folder.
        scope:
          type: string
          enum: [organization, restricted]
          description: |
            Visibility scope of the folder.

            - `organization` - Organization-wide access
            - `restricted` - Limited access
        owner:
          type: object
          description: Information about the folder owner.
          properties:
            id:
              type: string
              description: ID of the folder owner.
            name:
              type: string
              description: Display name of the owner.
        labels:
          type: array
          items:
            type: string
          description: List of labels associated with the folder. Only included when requested via the `include` parameter.
        _count:
          type: object
          description: Contains count information. Only included when requested via the `include` parameter.
          properties:
            documents:
              type: integer
              description: Number of documents contained in the folder.
            favorites:
              type: integer
              description: Number of users who favorited this folder.
        url:
          type: string
          format: uri
          description: Direct link to the folder in the Omni UI.
          example: https://blobsrus.omni.co/f/blob-sales-reports

    Label:
      type: object
      description: A label for organizing and categorizing documents and folders
      properties:
        name:
          type: string
          description: |
            The label name (2-25 characters).

            Names are case-insensitive: `"Production"` and `"production"` are considered the same.
          example: "Production"
        verified:
          type: boolean
          description: Whether the label is verified/curated
          example: true
        homepage:
          type: boolean
          description: Whether the label appears on the organization homepage
          example: false
        usage_count:
          type: integer
          description: Total number of documents and folders using this label
          example: 12
        color:
          type: string
          maxLength: 9
          default: null
          description: |
            Hex color for the label
          example: "#0366d6"
        description:
          type: string
          maxLength: 500
          default: null
          description: Description of the label
          example: "Documents based on dev schemas"

    Model:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: ID of the model.
        baseModelId:
          type: string
          format: uuid
          nullable: true
          description: ID of the base model, if applicable.
        connectionId:
          type: string
          format: uuid
          nullable: true
          description: ID of the connection the model is based on.
        modelKind:
          type: string
          nullable: true
          description: The type of model.
        name:
          type: string
          nullable: true
          description: The name of the model.
        createdAt:
          type: string
          format: date-time
          description: The time the model was created.
        updatedAt:
          type: string
          format: date-time
          description: The time the model was last updated.
        deletedAt:
          type: string
          format: date-time
          nullable: true
          description: The time the model was deleted, if applicable.

    ModelGitConfig:
      type: object
      properties:
        authMethod:
          type: string
          enum: [ssh, https_token]
          description: Authentication method. `ssh` for deploy key, `https_token` for deploy token/PAT.
          example: "ssh"
        cloneUrl:
          type: string
          description: Clone URL of the git repository (SSH or HTTPS)
          example: "git@github.com:org/repo.git"
        baseBranch:
          type: string
          description: The target branch for Omni pull requests.
          example: "main"
        branchPerPullRequest:
          type: boolean
          description: If `true`, all pull requests will create a branch in Omni, even those created outside of the tool.
          example: false
        gitFollower:
          type: boolean
          description: If `true`, the shared model is read-only and can only be updated by merging pull requests to the base branch.
          example: false
        gitServiceProvider:
          type: string
          enum: [github, gitlab, azure_devops, bitbucket]
          description: |
            The git provider type.

            - `github` - GitHub
            - `gitlab` - GitLab
            - `azure_devops` - Azure DevOps
            - `bitbucket` - Bitbucket
          example: "github"
        modelPath:
          type: string
          nullable: true
          description: Path to model files in the repository.
          example: "omni/blobs_r_us"
        publicKey:
          type: [string, null]
          description: |
            SSH public key for repository access (deploy key). Null for HTTPS token auth (`authMethod: https_token`).
          example: "ssh-ed25519 AAAA..."
        requirePullRequest:
          type: string
          enum: [always, users-only, never]
          description: |
            Controls when pull requests are required for changes:

            - `always` - Required for all changes
            - `users-only` - Required only for user-initiated changes
            - `never` - Never required
          example: "users-only"
        sshUrl:
          type: string
          deprecated: true
          description: |
            **Deprecated. Use `cloneUrl` instead**. SSH URL of the git repository.
          example: "git@github.com:org/repo.git"
        webUrl:
          type: string
          nullable: true
          description: Custom web URL for the git repository, or null if not set.
          example: "https://github.com/org/repo"
        webhookSecret:
          type: string
          description: Webhook secret for signature verification. Only included if requested via `?include=webhookSecret`.
        webhookUrl:
          type: string
          description: Webhook URL to configure in your git provider.
          example: "https://app.omni.co/api/webhooks/model/..."

    ModelSuggestion:
      type: object
      required:
        - id
        - category
        - title
        - rationale
        - priority
        - proposedChanges
        - evidence
        - ignoreReason
        - ignoredAt
        - ignoredBy
        - aiModifiedAt
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the suggestion.
        category:
          type: string
          description: Suggestion category, e.g. `missing_context`.
          example: "missing_context"
        title:
          type: string
          description: Short human-readable title.
        rationale:
          type: string
          description: Explanation of why the suggestion was made.
        priority:
          type: integer
          minimum: 1
          maximum: 10
          description: Priority from 1 (highest) to 10 (lowest).
          example: 1
        proposedChanges:
          $ref: '#/components/schemas/SuggestionProposedChanges'
        evidence:
          type: array
          nullable: true
          description: Source evidence for the suggestion. `null` for rows created before evidence was tracked; `[]` when none was cited.
          items:
            $ref: '#/components/schemas/SuggestionEvidenceItem'
        ignoreReason:
          type: string
          nullable: true
          description: Optional free-text reason recorded when the suggestion was dismissed.
        ignoredAt:
          type: string
          format: date-time
          nullable: true
          description: ISO 8601 timestamp of dismissal, or `null` if active.
        ignoredBy:
          type: string
          format: uuid
          nullable: true
          description: User ID that dismissed the suggestion, or `null` if active.
        aiModifiedAt:
          type: string
          format: date-time
          description: ISO 8601 timestamp of the last AI write (create or AI update). Unaffected by dismiss/restore.
        createdAt:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the suggestion was created.
        updatedAt:
          type: string
          format: date-time
          description: ISO 8601 timestamp of the last write of any kind, including dismiss/restore.

    SuggestionEvidenceItem:
      type: object
      required:
        - type
        - chatAiSessionId
        - capturedAt
      properties:
        type:
          type: string
          enum: [ai_chat]
          description: The kind of evidence. Currently always `ai_chat`.
        chatAiSessionId:
          type: string
          format: uuid
          description: Chat session that motivated the suggestion.
        capturedAt:
          type: string
          description: ISO 8601 timestamp of when the evidence was captured.

    SuggestionProposedChanges:
      type: object
      description: The change(s) the suggestion would apply to the model.
      required:
        - kind
        - edits
      properties:
        kind:
          type: string
          enum: [context_edits]
          description: The kind of change. Currently always `context_edits`.
        edits:
          type: array
          items:
            $ref: '#/components/schemas/SuggestionContextEdit'

    SuggestionRun:
      type: object
      description: Represents a single AI model suggestion generation run.
      required:
        - id
        - status
        - triggerSource
        - triggeredBy
        - createdAt
        - executionStartedAt
        - completedAt
        - error
      properties:
        id:
          type: string
          format: uuid
          description: The run ID.
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
        status:
          type: string
          enum:
            - queued
            - executing
            - complete
            - failed
          description: Run status. Terminal states are `complete` and `failed`.
          example: complete
        triggerSource:
          type: string
          enum:
            - manual
            - scheduled
          description: Whether the run was triggered manually or by the schedule.
          example: manual
        triggeredBy:
          type: object
          nullable: true
          description: The user who triggered a manual run; `null` for scheduled runs.
          properties:
            userId:
              type: string
              format: uuid
              description: The user's ID.
              example: b2c3d4e5-f6a7-8901-bcde-f12345678901
            name:
              type: string
              nullable: true
              description: The user's name.
              example: Jane Doe
          required:
            - userId
            - name
        createdAt:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the run was created (queued).
          example: "2026-07-15T14:30:00Z"
        executionStartedAt:
          type: string
          nullable: true
          format: date-time
          description: ISO 8601 timestamp when the worker started executing; `null` while queued.
          example: "2026-07-15T14:30:05Z"
        completedAt:
          type: string
          nullable: true
          format: date-time
          description: ISO 8601 timestamp when the run reached a terminal state.
          example: "2026-07-15T14:32:30Z"
        error:
          type: object
          nullable: true
          additionalProperties: {}
          description: Failure details when `status` is `failed`; `null` otherwise.

    WhoamiModelRole:
      type: object
      required:
        - baseRole
        - connectionId
        - permissions
        - roleName
      properties:
        baseRole:
          type: string
          description: The resolved base role. For custom roles, the base role they extend.
          example: QUERIER
        connectionId:
          type: string
          description: The connection this model belongs to
        permissions:
          type: array
          items:
            type: string
            enum:
              - QUERY_FULL_MODEL
              - QUERY_SQL
              - VIEW_SQL
              - QUERY_TOPICS
              - RUN_CONTENT_QUERIES
              - DOWNLOAD_CONTENT_QUERY
              - UPLOAD_CSV
              - SCHEDULE
              - SAVE_SPREADSHEETS
              - USE_AI
              - USE_WORKBOOKS
              - UPDATE
              - UPDATE_RESTRICTED
          description: |
            The caller''s resolved/effective permissions on this model, reflecting custom roles. This is a capability signal for the directly-roleable model kinds (schema / shared / extension). 
            
            It does not enumerate the permissions you derive on branch, workbook, and query models from your role on the base model they descend from - bsence here does not mean you lack access on those derived models.`MANAGE_MODEL`, `READ`, and `REFRESH_SCHEMA` are also not reported: they derive from connection / sibling-model roles rather than a per-model rule.
          example:
            - QUERY_TOPICS
            - QUERY_SQL
            - USE_WORKBOOKS
        roleName:
          type: string
          description: The resolved role name; may be a custom role. Use `permissions` to decide capability.
          example: QUERIER
  
    WhoamiUser:
      type: object
      required:
        - id
        - membershipId
      properties:
        id:
          type: string
          description: The caller's user ID
        membershipId:
          type: string
          description: The caller's own membership ID within this organization. This is the ID accepted by the [Get model roles endpoint](/api/user-model-roles/retrieve-user-model-roles) and is distinct from the user ID.
      
    SuggestionContextEdit:
      type: object
      required:
        - field
        - target
        - value
      properties:
        field:
          type: string
          description: The model field being edited (e.g. `ai_context`).
          example: "ai_context"
        target:
          type: string
          description: Dot-path identifying what the edit applies to, e.g. `views.orders.fields.status`.
          example: "views.orders"
        value:
          anyOf:
            - type: string
            - type: array
              items:
                type: string
          description: The proposed value for the field.

    SuggestionsCooldownResponse:
      type: object
      required:
        - detail
        - lastCompletedAt
        - retryAfterSeconds
        - status
      properties:
        detail:
          type: string
          description: Human-readable error message.
        lastCompletedAt:
          type: string
          format: date-time
          description: When the most recent run completed.
        retryAfterSeconds:
          type: integer
          description: Seconds to wait before a new run is allowed.
        status:
          type: integer
          example: 429

    Permission:
      type: object
      properties:
        role:
          type: string
          enum: [VIEWER, EDITOR, MANAGER]
        accessBoost:
          type: boolean
        direct:
          type: boolean
        inherited:
          type: boolean

    FolderPermission:
      type: object
      description: Represents a folder permission in Omni.
      properties:
        id:
          type: string
          description: The ID of the user or user group.
        name:
          type: string
          description: The name of the user or user group.
        description:
          type: string
          description: Description of the permission source.
        type:
          type: string
          description: The type of the permission holder.
          enum: [user, userGroup]
        direct:
          type: object
          description: Direct permission details for this folder.
          properties:
            accessBoost:
              type: boolean
              description: If `true`, [AccessBoost](/share#boosting-permissions-with-accessboost) is enabled for the user.
            isOwner:
              type: boolean
              description: If `true`, the user is the owner of the folder.
            role:
              type: string
              description: The content role associated with the user.
              enum: [NO_ACCESS, VIEWER, EDITOR, MANAGER]

    DocumentAccessPrincipal:
      type: object
      description: Represents a user or group with access to a document.
      properties:
        id:
          type: string
          description: The ID of the user or user group.
        name:
          type: string
          description: Display name of the user or user group.
        email:
          type: string
          description: Email address. Only present for users, not user groups.
        type:
          type: string
          enum: [user, userGroup]
          description: The type of principal.
        role:
          type: string
          enum: [VIEWER, INTERACTOR, EDITOR, MANAGER]
          description: Permission level assigned to this principal.
        accessBoost:
          type: boolean
          description: Whether elevated access is enabled for this principal.
        accessSource:
          type: string
          enum: [direct, folder]
          description: |
            How access was granted:
            - `direct` — Explicit document permissions
            - `folder` — Inherited from folder permissions
        isOwner:
          type: boolean
          description: Whether this user owns the document. Only present for users, not user groups.
        folderInfo:
          type: object
          description: Information about the folder that grants access. Only present when `accessSource` is `folder`.
          properties:
            id:
              type: string
              description: The ID of the folder.
            name:
              type: string
              description: The name of the folder.
            path:
              type: string
              description: The full path of the folder.

    ScimGroup:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the group
        displayName:
          type: string
          description: The name of the group
        members:
          type: array
          description: The list of users in the group
          items:
            type: object
            properties:
              value:
                type: string
                description: The user's unique identifier
              display:
                type: string
                description: The user's email address
        meta:
          type: object
          description: Metadata about the group resource
          properties:
            resourceType:
              type: string
              example: "Group"
              description: The type of resource. This will be `Group`.
            created:
              type: string
              format: date-time
              description: The time the group was created
            lastModified:
              type: string
              format: date-time
              description: The time the group was last updated
        schemas:
          type: array
          description: SCIM schema information for the group
          items:
            type: string

    ScimUser:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the user
        userName:
          type: string
          description: The user's email address
        displayName:
          type: string
          description: The user's display name
        active:
          type: boolean
          description: Whether the user is active
        emails:
          type: array
          description: The user's email addresses
          items:
            type: object
            properties:
              primary:
                type: boolean
                description: Whether this is the user's primary email
              value:
                type: string
                description: The email address
        groups:
          type: array
          description: User groups that the user belongs to
          items:
            type: object
            properties:
              display:
                type: string
                description: The group's display name
              value:
                type: string
                description: The group's unique identifier
        meta:
          type: object
          properties:
            resourceType:
              type: string
              example: "User"
              description: The resource type. This will be `User`.
            created:
              type: string
              format: date-time
              description: The time the user was created
            lastModified:
              type: string
              format: date-time
              description: The time the user was last updated
        schemas:
          type: array
          items:
            type: string
          description: |
            SCIM information about the type of schemas used in the API. For example, `urn:ietf:params:scim:schemas:core:2.0:User`
        urn:omni:params:1.0:UserAttribute:
          type: object
          description: |
            [User attributes](/administration/users/attributes) as key/value pairs, where keys map to the IDs of user attributes defined in Omni. This is the **Reference** column in the **User attributes** page.
        urn:omni:params:scim:schemas:extension:user:2.0:
          type: object
          description: Omni SCIM extension schema containing additional user metadata
          properties:
            lastLogin:
              type: string
              format: date-time
              nullable: true
              description: The timestamp of the user's last login. Will be `null` if the user has never logged in.

    ScimEmbedUser:
      type: object
      description: An embed user object (SCIM 2.0)
      properties:
        id:
          type: string
          description: Unique identifier for the user
        userName:
          type: string
          description: The user's email address
        displayName:
          type: string
          description: The user's display name
        active:
          type: boolean
          description: Whether the user is active
        emails:
          type: array
          description: The user's email addresses
          items:
            type: object
            properties:
              primary:
                type: boolean
                description: Whether this is the user's primary email
              value:
                type: string
                description: The email address
        groups:
          type: array
          description: User groups that the user belongs to
          items:
            type: object
            properties:
              display:
                type: string
                description: The group's display name
              value:
                type: string
                description: The group's unique identifier
        meta:
          type: object
          properties:
            resourceType:
              type: string
              example: "User"
              description: The type of resource. This will be `User`.
            created:
              type: string
              format: date-time
              description: The time the user was created
            lastModified:
              type: string
              format: date-time
              description: The time the user was last uodated
        schemas:
          type: array
          items:
            type: string
          description: |
            SCIM information about the type of schemas used in the API. For example, `urn:ietf:params:scim:schemas:core:2.0:User`
        embedEmail:
          type: string
          nullable: true
          description: The embed user's email address
        embedEntity:
          type: string
          description: The embed entity identifier
        embedExternalId:
          type: string
          description: The external ID for the embed user

    EvalPrompt:
      type: object
      properties:
        created_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the prompt was created.
          example: '2025-01-15T10:00:00.000Z'
        expectation:
          type:
            - string
            - 'null'
          description: The expectation the analysis judge scores the analysis against, or null when none was set.
          example: The top product by revenue should be Aniseed Syrup.
        id:
          type: string
          format: uuid
          description: Unique identifier for the prompt.
          example: 770e8400-e29b-41d4-a716-446655440002
        prompt_text:
          type: string
          description: The natural language prompt text the AI is evaluated on.
          example: What are the top 5 products by revenue?
        updated_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the prompt was last updated.
          example: '2025-01-15T10:00:00.000Z'
      required:
        - created_at
        - expectation
        - id
        - prompt_text
        - updated_at
    EvalPromptSet:
      type: object
      properties:
        created_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the prompt set was created.
          example: '2025-01-15T10:00:00.000Z'
        description:
          type:
            - string
            - 'null'
          description: Optional human-readable description of the prompt set.
          example: Regression suite for the orders topic
        id:
          type: string
          format: uuid
          description: Unique identifier for the prompt set.
          example: 550e8400-e29b-41d4-a716-446655440000
        is_archived:
          type: boolean
          description: Whether the prompt set has been archived.
          example: false
        model_id:
          type: string
          format: uuid
          description: The shared model this prompt set is bound to.
          example: 880e8400-e29b-41d4-a716-446655440003
        name:
          type: string
          description: Human-readable name for the prompt set.
          example: Orders regression
        prompts:
          type: array
          items:
            $ref: '#/components/schemas/EvalPrompt'
          description: Prompts that make up the set.
        slug:
          type: string
          description: URL-safe identifier for the prompt set. Unique per `model_id`.
          example: orders-regression
        updated_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the prompt set was last updated.
          example: '2025-01-15T10:00:00.000Z'
      required:
        - created_at
        - description
        - id
        - is_archived
        - model_id
        - name
        - prompts
        - slug
        - updated_at
    EvalPromptSetListItem:
      type: object
      properties:
        created_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the prompt set was created.
          example: '2025-01-15T10:00:00.000Z'
        description:
          type:
            - string
            - 'null'
          description: Optional human-readable description of the prompt set.
          example: Regression suite for the orders topic
        id:
          type: string
          format: uuid
          description: Unique identifier for the prompt set.
          example: 550e8400-e29b-41d4-a716-446655440000
        is_archived:
          type: boolean
          description: Whether the prompt set has been archived.
          example: false
        model_id:
          type: string
          format: uuid
          description: The shared model this prompt set is bound to.
          example: 880e8400-e29b-41d4-a716-446655440003
        name:
          type: string
          description: Human-readable name for the prompt set.
          example: Orders regression
        slug:
          type: string
          description: URL-safe identifier for the prompt set. Unique per `model_id`.
          example: orders-regression
        updated_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the prompt set was last updated.
          example: '2025-01-15T10:00:00.000Z'
        latest_run_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp of the most recent run on this prompt set, if any.
          example: '2025-01-15T10:05:00.000Z'
        prompt_count:
          type: integer
          description: Number of prompts in the set.
          example: 12
      required:
        - created_at
        - description
        - id
        - is_archived
        - model_id
        - name
        - slug
        - updated_at
        - latest_run_at
        - prompt_count
    EvalPromptSetsCreateBody:
      type: object
      properties:
        description:
          type:
            - string
            - 'null'
          maxLength: 1024
          description: Optional human-readable description of the prompt set. Max 1024 characters.
          example: Regression suite for the orders topic
        model_id:
          type: string
          format: uuid
          description: The shared model this prompt set is bound to.
          example: 880e8400-e29b-41d4-a716-446655440003
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: Human-readable name for the prompt set. 255 characters or fewer.
          example: Orders regression
        prompts:
          type: array
          items:
            type: object
            properties:
              expectation:
                type:
                  - string
                  - 'null'
                maxLength: 16000
                description: Optional expectation the analysis judge scores the analysis against. Max 16000 characters.
                example: The top product by revenue should be Aniseed Syrup.
              prompt_text:
                type: string
                minLength: 1
                maxLength: 8000
                description: The natural language prompt text. Max 8000 characters.
                example: What are the top 5 products by revenue?
            required:
              - prompt_text
          maxItems: 25
          default: []
          description: Initial prompts for the set. Defaults to an empty list. At most 25 prompts.
        slug:
          type: string
          maxLength: 255
          pattern: ^[a-z][a-z0-9-]*$
          description: URL-safe identifier for the prompt set. Must be unique per `model_id` and match `^[a-z][a-z0-9-]*$`. Max 255 characters.
          example: orders-regression
      required:
        - model_id
        - name
        - slug
    EvalPromptSetsCreateResponse:
      type: object
      properties:
        prompt_set:
          $ref: '#/components/schemas/EvalPromptSet'
      required:
        - prompt_set
    EvalPromptSetsDeleteResponse:
      type: object
      properties:
        cancelled_job_count:
          type: integer
          description: Number of in-flight agentic jobs associated with this prompt set that were cancelled as part of the archive.
          example: 0
        is_archived:
          type: boolean
          enum:
            - true
          description: Always `true` on success — archives the prompt set.
      required:
        - cancelled_job_count
        - is_archived
    EvalPromptSetsGetResponse:
      type: object
      properties:
        prompt_set:
          $ref: '#/components/schemas/EvalPromptSet'
      required:
        - prompt_set
    EvalPromptSetsListResponse:
      type: object
      properties:
        prompt_sets:
          type: array
          items:
            $ref: '#/components/schemas/EvalPromptSetListItem'
          description: Prompt sets matching the query, sorted alphabetically by name.
      required:
        - prompt_sets
    EvalPromptSetsUnarchiveResponse:
      type: object
      properties:
        prompt_set:
          $ref: '#/components/schemas/EvalPromptSet'
      required:
        - prompt_set
    EvalPromptSetsUpdateBody:
      type: object
      properties:
        description:
          type:
            - string
            - 'null'
          maxLength: 1024
          description: New description for the prompt set. Pass `null` to clear. Max 1024 characters.
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: New human-readable name for the prompt set. 255 characters or fewer.
        prompts:
          type: array
          items:
            type: object
            properties:
              expectation:
                type:
                  - string
                  - 'null'
                maxLength: 16000
                description: Optional expectation the analysis judge scores the analysis against. Pass `null` to clear. Max 16000 characters.
                example: The top product by revenue should be Aniseed Syrup.
              id:
                type: string
                format: uuid
                description: Existing prompt id. When provided, updates that prompt; when omitted, a new prompt is created. Prompts not included in this list are removed.
              prompt_text:
                type: string
                minLength: 1
                maxLength: 8000
                description: Updated or new prompt text. Max 8000 characters.
                example: What are the top 10 products by revenue this quarter?
            required:
              - prompt_text
          maxItems: 25
          description: Full desired set of prompts after the update. Prompts omitted from this list are deleted; new prompts (no `id`) are appended in body order. Existing prompts retain their original position — reordering is not supported on this endpoint. At most 25 prompts total.
    EvalPromptSetsUpdateResponse:
      type: object
      properties:
        prompt_set:
          $ref: '#/components/schemas/EvalPromptSet'
      required:
        - prompt_set
    EvalRunDetail:
      type: object
      properties:
        branch_id:
          type:
            - string
            - 'null'
          format: uuid
          description: Optional branch ID the run was executed against. Null when run against the main shared model.
          example: null
        branch_name:
          type:
            - string
            - 'null'
          description: Display name for the branch, if `branch_id` is set.
          example: null
        completed_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the run reached a terminal state.
          example: null
        created_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the run was created.
          example: '2025-01-15T10:00:00.000Z'
        description:
          type:
            - string
            - 'null'
          description: Optional human-readable description for the run.
          example: null
        id:
          type: string
          format: uuid
          description: Unique identifier for the run.
          example: 660e8400-e29b-41d4-a716-446655440001
        is_archived:
          type: boolean
          description: Whether the run has been archived.
          example: false
        model_id:
          type: string
          format: uuid
          description: The shared model this run was executed against.
          example: 880e8400-e29b-41d4-a716-446655440003
        prompt_set_id:
          type: string
          format: uuid
          description: The prompt set this run was created from.
          example: 550e8400-e29b-41d4-a716-446655440000
        results:
          type: array
          items:
            $ref: '#/components/schemas/EvalRunResult'
          description: Per-prompt results for this run, ordered by their creation order in the prompt set.
        run_number:
          type: integer
          description: Sequential, per-prompt-set run number.
          example: 3
        status:
          type: string
          enum:
            - RUNNING
            - COMPLETE
            - CANCELLED
          description: Run-level lifecycle. Flips to a terminal state (COMPLETE or CANCELLED) exactly once.
          example: RUNNING
      required:
        - branch_id
        - branch_name
        - completed_at
        - created_at
        - description
        - id
        - is_archived
        - model_id
        - prompt_set_id
        - results
        - run_number
        - status
      description: The newly created run with its initial results.
    EvalRunListItem:
      type: object
      properties:
        branch_id:
          type:
            - string
            - 'null'
          format: uuid
          description: Optional branch ID the run was executed against. Null when run against the main shared model.
          example: null
        branch_name:
          type:
            - string
            - 'null'
          description: Display name for the branch, if `branch_id` is set.
          example: null
        completed_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the run reached a terminal state.
          example: null
        created_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp when the run was created.
          example: '2025-01-15T10:00:00.000Z'
        description:
          type:
            - string
            - 'null'
          description: Optional human-readable description for the run.
          example: null
        id:
          type: string
          format: uuid
          description: Unique identifier for the run.
          example: 660e8400-e29b-41d4-a716-446655440001
        is_archived:
          type: boolean
          description: Whether the run has been archived.
          example: false
        model_id:
          type: string
          format: uuid
          description: The shared model this run was executed against.
          example: 880e8400-e29b-41d4-a716-446655440003
        prompt_set_id:
          type: string
          format: uuid
          description: The prompt set this run was created from.
          example: 550e8400-e29b-41d4-a716-446655440000
        run_number:
          type: integer
          description: Sequential, per-prompt-set run number.
          example: 3
        stats:
          $ref: '#/components/schemas/EvalRunStats'
        status:
          type: string
          enum:
            - RUNNING
            - COMPLETE
            - CANCELLED
          description: Run-level lifecycle. Flips to a terminal state (COMPLETE or CANCELLED) exactly once.
          example: RUNNING
      required:
        - branch_id
        - branch_name
        - completed_at
        - created_at
        - description
        - id
        - is_archived
        - model_id
        - prompt_set_id
        - run_number
        - stats
        - status
    EvalRunResult:
      type: object
      properties:
        agentic_job:
          $ref: '#/components/schemas/EvalRunResultAgenticJob'
        cost:
          type:
            - number
            - 'null'
          description: Total LLM cost (USD) for this prompt, if available.
          example: 0.0021
        error_reason:
          type:
            - string
            - 'null'
          description: Failure reason string for prompts whose underlying job failed.
          example: null
        id:
          type: string
          format: uuid
          description: Unique identifier for the run result row.
          example: aa0e8400-e29b-41d4-a716-446655440005
        prompt:
          type: string
          description: The prompt text that was evaluated.
          example: What are the top 5 products by revenue?
        score:
          type:
            - number
            - 'null'
          description: Numeric judge score for this prompt result, if scoring ran.
          example: 0.9
        scoring_cost:
          type:
            - number
            - 'null'
          description: Total LLM cost (USD) for scoring this prompt result.
          example: 0.0004
        timing_ms:
          type:
            - integer
            - 'null'
          description: Wall-clock duration of the underlying job in milliseconds.
          example: 4321
      required:
        - agentic_job
        - cost
        - error_reason
        - id
        - prompt
        - score
        - scoring_cost
        - timing_ms
    EvalRunResultAgenticJob:
      type: object
      properties:
        conversation_id:
          type:
            - string
            - 'null'
          format: uuid
          description: Conversation the agentic job belongs to.
          example: 770e8400-e29b-41d4-a716-446655440002
        id:
          type: string
          format: uuid
          description: Agentic job identifier.
          example: 990e8400-e29b-41d4-a716-446655440004
        state:
          type: string
          enum:
            - CANCELLED
            - COMPLETE
            - DELIVERING
            - EXECUTING
            - FAILED
            - QUEUED
          description: Current state of the agentic job that ran this prompt.
          example: COMPLETE
      required:
        - conversation_id
        - id
        - state
    EvalRunStats:
      type: object
      properties:
        terminal:
          type: integer
          description: Number of per-prompt jobs that have reached a terminal state (COMPLETE, FAILED, or CANCELLED).
          example: 8
        total:
          type: integer
          description: Total number of per-prompt jobs in the run.
          example: 12
      required:
        - terminal
        - total
    EvalRunsCancelResponse:
      type: object
      properties:
        cancelled:
          type: integer
          description: Number of per-prompt agentic jobs that were cancelled by this request.
          example: 4
        run:
          allOf:
            - $ref: '#/components/schemas/EvalRunDetail'
            - description: 'The cancelled run. `status: CANCELLED` and `is_archived: true` after this call.'
        total:
          type: integer
          description: Total number of per-prompt jobs in the run.
          example: 12
      required:
        - cancelled
        - run
        - total
    EvalRunsCreateBody:
      type: object
      properties:
        description:
          type:
            - string
            - 'null'
          maxLength: 1024
          description: Optional human-readable description for the run. Omit or pass `null` to leave it unset. Max 1024 characters.
          example: Re-running after switching to gpt-4o for query generation
        prompt_set_id:
          type: string
          format: uuid
          description: The prompt set to execute.
          example: 550e8400-e29b-41d4-a716-446655440000
        run_config:
          type: object
          properties:
            branch_id:
              type: string
              format: uuid
              description: Optional branch ID to run against. Must be a branch of the prompt set's model.
              example: 440e8400-e29b-41d4-a716-446655440006
          description: Per-run configuration. Optional — omit if no overrides.
      required:
        - prompt_set_id
    EvalRunsCreateResponse:
      type: object
      properties:
        job_count:
          type: integer
          description: Number of per-prompt agentic jobs created for this run (one per prompt that fanned out successfully). Enqueue onto the work queue happens after creation and is best-effort, so this count reflects jobs created, not necessarily those successfully enqueued.
          example: 12
        run:
          $ref: '#/components/schemas/EvalRunDetail'
      required:
        - job_count
        - run
    EvalRunsDeleteResponse:
      type: object
      properties:
        is_archived:
          type: boolean
          enum:
            - true
          description: Always `true` on success — the run has been archived.
      required:
        - is_archived
    EvalRunsGetResponse:
      type: object
      properties:
        run:
          $ref: '#/components/schemas/EvalRunDetail'
      required:
        - run
    EvalRunsListResponse:
      type: object
      properties:
        runs:
          type: array
          items:
            $ref: '#/components/schemas/EvalRunListItem'
          description: Runs for the prompt set, newest first, filtered to those whose model the caller can access.
      required:
        - runs
    EvalRunsUnarchiveResponse:
      type: object
      properties:
        is_archived:
          type: boolean
          enum:
            - false
          description: Always `false` on success — the run has been unarchived.
      required:
        - is_archived
    EvalApiError:
      type: object
      description: Error response returned by the AI Eval endpoints.
      properties:
        detail:
          type: string
          description: Human-readable error message describing what went wrong.
        status:
          type: integer
          description: HTTP status code of the error.
      required:
        - detail
        - status

    # =========================================================================
    # AI Routines
    # =========================================================================
    Routine:
      type: object
      properties:
        branchId:
          type: string
          nullable: true
          format: uuid
          description: Branch of the shared model the prompt runs against, or null.
        createdAt:
          type: string
          description: ISO 8601 timestamp when the routine was created.
        description:
          type: string
          nullable: true
          description: Display-only notes about the routine, or null.
        destination:
          $ref: '#/components/schemas/RoutineDestination'
        disabled:
          type: boolean
          description: Whether the owner has paused the routine.
        id:
          type: string
          format: uuid
          description: The unique identifier of the routine.
        lastRun:
          $ref: '#/components/schemas/RoutineLastRun'
        modelId:
          type: string
          format: uuid
          description: The shared model the prompt runs against.
        name:
          type: string
          description: Customer-visible name of the routine, used as the email subject.
        prompt:
          type: string
          description: Natural language prompt Omni runs on each scheduled run.
        recipientCount:
          type: integer
          description: Number of distinct deliverable recipients after expanding user groups and removing duplicates.
        schedule:
          type: string
          description: Six-field cron expression (minute, hour, day-of-month, month, day-of-week, year; use `?` for an unspecified day field).
        systemDisabled:
          type: boolean
          description: Whether Omni disabled the routine because it could no longer run successfully or safely.
        systemDisabledReason:
          type: string
          nullable: true
          description: Reason Omni disabled the routine, or null.
        timezone:
          type: string
          description: IANA timezone identifier used to evaluate the schedule.
        topicName:
          type: string
          nullable: true
          description: Topic scoping query generation, or null.
        updatedAt:
          type: string
          description: ISO 8601 timestamp when the routine was last updated.
      required:
        - branchId
        - createdAt
        - description
        - destination
        - disabled
        - id
        - lastRun
        - modelId
        - name
        - prompt
        - recipientCount
        - schedule
        - systemDisabled
        - systemDisabledReason
        - timezone
        - topicName
        - updatedAt
    
    RoutineEmailDestination:
      type: object
      required:
        - type
      additionalProperties: false
      description: Email delivery configuration for the routine.
      properties:
        recipientEmails:
          type: array
          items:
            type: string
            format: email
          maxItems: 100
          default: []
          description: Email addresses that receive each scheduled run of the routine.
          example:
            - blob.ross@blobsrus.com
            - blob.the.builder@blobsrus.com
        type:
          type: string
          enum:
            - email
          description: Selects email delivery — each scheduled run is sent to the listed email recipients and user groups.
          example: email
        userGroupIds:
          type: array
          items:
            type: string
            format: uuid
          maxItems: 100
          default: []
          description: User group IDs whose active members receive each scheduled run. Omni expands each group to the members' current email addresses when the routine runs.
          example:
            - 550e8400-e29b-41d4-a716-446655440000

    RoutineSlackDestination:
      type: object
      required:
        - recipientId
        - slackRecipientType
        - type
      additionalProperties: false
      description: Slack delivery configuration for the routine. Requires a Slack workspace connected to Omni; to deliver to a private channel, invite the Omni Slackbot to it first.
      properties:
        recipientId:
          type: string
          minLength: 1
          description: The Slack channel ID (e.g. `C01234567`) or user ID (e.g. `U01234567`) that receives each scheduled run. Exactly one recipient per Slack routine.
          example: C01234567
        slackRecipientType:
          type: string
          enum:
            - channel
            - users
          description: Whether `recipientId` is a Slack channel or a user (delivered as a direct message).
          example: channel
        type:
          type: string
          enum:
            - slack
          description: Selects Slack delivery — each scheduled run is posted to one Slack channel or sent as a direct message to one user.
          example: slack

    RoutineDestination:
      oneOf:
        - $ref: '#/components/schemas/RoutineEmailDestination'
        - $ref: '#/components/schemas/RoutineSlackDestination'
      discriminator:
        propertyName: type
        mapping:
          email: '#/components/schemas/RoutineEmailDestination'
          slack: '#/components/schemas/RoutineSlackDestination'
      description: Single delivery destination for the routine — email or Slack. To send results to multiple destinations, create one routine per destination.

    RoutineLastRun:
      type: object
      nullable: true
      required:
        - completedAt
        - label
        - state
      description: Most recent completed run, or null if the routine has never completed a run.
      properties:
        completedAt:
          type: string
          nullable: true
          description: ISO 8601 timestamp the last completed run finished.
        label:
          type: string
          description: User-visible status of the last completed run.
          example: Delivered
        state:
          type: string
          description: Machine-readable status of the last completed run.
          example: COMPLETE

    UploadDeleteResponse:
      type: object
      properties:
        success:
          type: boolean
          description: Whether the deletion was successful
      required:
        - success

  responses:
    BadRequest:
      description: Bad Request - Invalid parameters or request body
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    Forbidden:
      description: Forbidden - Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    NotFound:
      description: Not Found - Resource does not exist
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    TooManyRequests:
      description: Too Many Requests - Rate limit exceeded (60 requests/minute)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    MethodNotAllowed:
      description: Method Not Allowed - Invalid HTTP method for this endpoint
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    InternalServerError:
      description: Internal Server Error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    QueryTimeoutError:
      description: Timeout
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/QueryTimeoutResponse'
