openapi: '3.0.0'
info:
  title: 'Donobu API'
  version: '3.19.1'
  description: |
    The Donobu API provides a comprehensive suite of endpoints for automating web browser interactions
    through AI-driven and deterministic flows. A **flow** is Donobu's core resource: it represents a
    sequence of browser actions executed against a target website, driven by an AI agent, explicit user
    instructions, or a predetermined script.

    ## Key Capabilities

    - **Flow Management** — Create, monitor, query, rename, and delete automation flows.
    - **AI-Powered Automation** — Leverage large language models (via configurable GPT configurations)
      to autonomously navigate websites and accomplish high-level objectives.
    - **Deterministic Replay** — Re-run previously recorded flows exactly as they occurred, or export
      them as Playwright test scripts for integration into CI/CD pipelines.
    - **Tool Call Inspection** — Retrieve the full chronological history of actions taken during a flow,
      including parameters, outcomes, timing, and screenshots.
    - **Media Retrieval** — Access screenshots and video recordings captured during flow execution.
    - **Configuration Management** — Manage GPT provider configurations, agent-to-configuration
      mappings, and environment variables consumed by flows.
    - **Tool Discovery** — Query the set of browser automation tools available in the Donobu runtime.

    ## Run Modes

    Flows operate in one of three execution modes:

    | Mode            | Description |
    |-----------------|-------------|
    | `AUTONOMOUS`    | An AI agent decides what actions to take based on the overall objective. Requires a GPT configuration. |
    | `INSTRUCT`      | The flow waits for explicit user-submitted tool calls at each step. No GPT configuration required. |
    | `DETERMINISTIC` | The flow executes a fixed sequence of tool calls provided at creation time. No AI decision-making. |

    ## Flow Lifecycle

    A flow transitions through the following states:

    `UNSTARTED` → `INITIALIZING` → (`RUNNING_ACTION` ↔ `QUERYING_LLM_FOR_NEXT_ACTION` | `WAITING_ON_USER_FOR_NEXT_ACTION`) → `SUCCESS` or `FAILED`

    Flows may also be `PAUSED` and later `RESUMING` during execution.

    ## Getting Started

    1. **Configure a GPT provider** via `POST /api/gpt-configs/{name}` (required for `AUTONOMOUS` mode).
    2. **Assign the configuration to an agent** via `POST /api/agents/{name}`.
    3. **Create a flow** via `POST /api/flows` with a `targetWebsite` and `overallObjective`.
    4. **Poll the flow** via `GET /api/flows/{flowId}` until it reaches a terminal state (`SUCCESS` or `FAILED`).
    5. **Inspect results** via tool calls, screenshots, video, or the flow's `result` field.

servers:
  - url: 'http://localhost:31000'
    description: 'Local Donobu app server'

tags:
  - name: 'flows'
    description: 'Create, manage, query, and inspect automation flows. Flows are the core resource in Donobu — each flow represents a sequence of browser actions executed against a target website to achieve an objective.'
  - name: 'tools'
    description: 'Discover the browser automation tools available in the Donobu runtime. Tools represent individual actions (e.g., clicking, typing, navigating) that can be invoked during flow execution.'
  - name: 'agents'
    description: 'Manage the mapping between Donobu agents and GPT configurations. Agents are the AI decision-makers that drive autonomous flows; each agent is linked to a specific GPT configuration that determines which model and provider it uses.'
  - name: 'configs'
    description: 'Manage GPT provider configurations and environment variables. GPT configurations define connection parameters and model settings for AI providers (Anthropic, OpenAI, Google, etc.). Environment variables provide key-value data accessible to flows at runtime.'
  - name: 'system'
    description: 'System-level endpoints for health checks and API documentation retrieval.'

paths:
  #
  # ── Flows ──────────────────────────────────────────────────────────────────
  #
  /api/flows:
    get:
      operationId: 'listFlows'
      summary: 'List flows'
      description: |
        Retrieves a paginated list of flows with optional filtering. Results are ordered by
        `startedAt` in descending order (newest first). Use the `nextPageToken` from the response
        to fetch subsequent pages.
      tags:
        - 'flows'
      parameters:
        - name: 'name'
          in: 'query'
          description: 'Filter by exact flow name match.'
          required: false
          schema:
            type: 'string'
        - name: 'startedAfter'
          in: 'query'
          description: 'Include only flows whose `startedAt` is greater than or equal to this value. Unix epoch timestamp in milliseconds.'
          required: false
          schema:
            type: 'number'
        - name: 'startedBefore'
          in: 'query'
          description: 'Include only flows whose `startedAt` is less than or equal to this value. Unix epoch timestamp in milliseconds.'
          required: false
          schema:
            type: 'number'
        - name: 'runMode'
          in: 'query'
          description: 'Filter by execution mode.'
          required: false
          schema:
            $ref: '#/components/schemas/RunMode'
        - name: 'state'
          in: 'query'
          description: 'Filter by current flow state.'
          required: false
          schema:
            $ref: '#/components/schemas/FlowState'
        - name: 'limit'
          in: 'query'
          description: 'Maximum number of flows to return per page. Valid range is 1–100.'
          required: false
          schema:
            type: 'integer'
            minimum: 1
            maximum: 100
        - name: 'pageToken'
          in: 'query'
          description: "Opaque pagination token returned in a previous response's `nextPageToken` field. Pass it as-is to retrieve the next page of results."
          required: false
          schema:
            type: 'string'
      responses:
        '200':
          description: 'A paginated list of flows matching the query criteria.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                  - 'flows'
                properties:
                  flows:
                    type: 'array'
                    description: 'The list of flow metadata objects for this page.'
                    items:
                      $ref: '#/components/schemas/FlowMetadata'
                  nextPageToken:
                    type: 'string'
                    nullable: true
                    description: 'An opaque token to pass as the `pageToken` query parameter to retrieve the next page. Absent or null when there are no more results.'
        '400':
          $ref: '#/components/responses/ValidationError'

    post:
      operationId: 'createFlow'
      summary: 'Create a flow'
      description: |
        Creates and starts a new Donobu flow. The flow begins executing asynchronously after
        creation. Poll `GET /api/flows/{flowId}` to track progress.

        At a minimum, `targetWebsite` is required. For `AUTONOMOUS` mode, an `overallObjective`
        and a valid GPT configuration (either via the default agent or `gptConfigNameOverride`)
        are also required.
      tags:
        - 'flows'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateFlowRequest'
      responses:
        '200':
          description: 'The newly created flow. Its state will be `UNSTARTED` or `INITIALIZING`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlowMetadata'
        '400':
          $ref: '#/components/responses/ValidationError'

  /api/flows/{flowId}:
    get:
      operationId: 'getFlow'
      summary: 'Get a flow'
      description: 'Retrieves the complete metadata for a specific flow, including its current state, configuration, execution statistics, and result.'
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
      responses:
        '200':
          description: 'The flow metadata.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlowMetadata'
        '404':
          $ref: '#/components/responses/NotFoundError'

    delete:
      operationId: 'deleteFlow'
      summary: 'Delete a flow'
      description: |
        Permanently deletes a flow and all associated data, including metadata, tool calls,
        screenshots, and video recordings. This operation cannot be undone.

        A flow that is currently running cannot be deleted; cancel it first.
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
      responses:
        '200':
          description: 'The flow was successfully deleted.'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '409':
          description: 'The flow is still running and cannot be deleted.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/flows/{flowId}/rename:
    post:
      operationId: 'renameFlow'
      summary: 'Rename a flow'
      description: 'Updates the display name of an existing flow.'
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
                - 'name'
              properties:
                name:
                  type: 'string'
                  description: 'The new name for the flow.'
      responses:
        '200':
          description: 'The updated flow metadata.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlowMetadata'
        '400':
          $ref: '#/components/responses/ValidationError'
        '404':
          $ref: '#/components/responses/NotFoundError'

  /api/flows/{flowId}/rerun:
    get:
      operationId: 'getFlowAsRerun'
      summary: 'Get flow as rerun payload'
      description: |
        Converts a completed flow into a `CreateFlowRequest` payload suitable for deterministic
        replay. The returned object includes the original tool calls configured for sequential
        re-execution and can be submitted directly to `POST /api/flows`.
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
      responses:
        '200':
          description: 'A `CreateFlowRequest` payload that will deterministically replay the flow.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateFlowRequest'
        '404':
          $ref: '#/components/responses/NotFoundError'

  /api/flows/{flowId}/code:
    get:
      operationId: 'getFlowAsCode'
      summary: 'Generate Playwright code for a flow'
      description: |
        Generates a Playwright test script that replays the actions of a completed flow.
        The generated code uses the Donobu Playwright extension and can be executed
        independently as part of a test suite.
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
        - name: 'areElementIdsVolatile'
          in: 'query'
          description: "If `true`, ID-only selectors (e.g. `#submit-btn`) are dropped because the element's `id` attribute is considered volatile. When every candidate selector is ID-based, the list is left unchanged."
          required: false
          schema:
            type: 'boolean'
        - name: 'disableSelectorFailover'
          in: 'query'
          description: 'If `true`, only the first (most specific) selector is used, disabling automatic fail-over to broader selectors.'
          required: false
          schema:
            type: 'boolean'
        - name: 'disableSelfHealingTests'
          in: 'query'
          description: 'If `true`, disables self-healing test behavior where selectors are automatically updated on mismatch.'
          required: false
          schema:
            type: 'boolean'
        - name: 'disablePullRequestCreation'
          in: 'query'
          description: 'If `true`, disables automatic pull request creation for tests that have self-healed.'
          required: false
          schema:
            type: 'boolean'
        - name: 'runInHeadedMode'
          in: 'query'
          description: 'If `true`, the generated test runs with a visible browser window instead of headless mode. Useful for debugging and demos.'
          required: false
          schema:
            type: 'boolean'
        - name: 'slowMotionDelay'
          in: 'query'
          description: 'Delay in milliseconds between actions in the generated test. Set to `0` to disable. Useful for debugging and demos.'
          required: false
          schema:
            type: 'number'
            minimum: 0
        - name: 'playwrightScriptVariant'
          in: 'query'
          description: 'Choose whether the generated test uses `page.ai()` calls (`ai`) or classic tool-by-tool Playwright API calls (`classic`).'
          required: false
          schema:
            type: 'string'
            enum:
              - 'ai'
              - 'classic'
      responses:
        '200':
          description: 'The generated Playwright test script.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                  - 'script'
                properties:
                  script:
                    type: 'string'
                    description: 'The generated Playwright test script source code.'
        '404':
          $ref: '#/components/responses/NotFoundError'

  /api/flows/project:
    post:
      operationId: 'getFlowsAsProject'
      summary: 'Generate a Playwright project for multiple flows'
      description: |
        Generates a complete Playwright project structure containing test files for one or more
        flows, along with configuration and dependency files. The resulting project can be
        written to disk and executed directly with Playwright.
      tags:
        - 'flows'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
                - 'flowIds'
              properties:
                flowIds:
                  type: 'array'
                  description: 'The IDs of flows to include in the project.'
                  items:
                    type: 'string'
                options:
                  $ref: '#/components/schemas/CodeGenerationOptions'
      responses:
        '200':
          description: 'The generated project files.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                  - 'files'
                properties:
                  files:
                    type: 'array'
                    description: 'The list of generated project files.'
                    items:
                      $ref: '#/components/schemas/ProjectFile'
        '400':
          $ref: '#/components/responses/ValidationError'
        '404':
          $ref: '#/components/responses/NotFoundError'

  #
  # ── Flow Tool Calls ────────────────────────────────────────────────────────
  #
  /api/flows/{flowId}/tool-calls:
    get:
      operationId: 'listToolCalls'
      summary: 'List tool calls for a flow'
      description: |
        Retrieves the complete chronological history of tool calls executed within a flow.
        Each tool call includes the action name, input parameters, outcome, timing, the
        page URL at the time of execution, and a reference to a post-call screenshot (if available).
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
      responses:
        '200':
          description: 'The ordered list of tool calls.'
          content:
            application/json:
              schema:
                type: 'array'
                items:
                  $ref: '#/components/schemas/ToolCall'
        '404':
          $ref: '#/components/responses/NotFoundError'

    post:
      operationId: 'proposeToolCall'
      summary: 'Propose a tool call'
      description: |
        Submits a tool call for execution in an active flow. This is primarily used during
        `INSTRUCT` mode to direct the flow's next action. The proposed tool call is validated
        and queued; execution happens asynchronously.

        The target flow must be actively running and the tool name must correspond to a valid,
        available tool whose parameters conform to its expected schema.
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposedToolCall'
      responses:
        '200':
          description: 'The tool call was accepted and queued for execution.'
        '400':
          $ref: '#/components/responses/ValidationError'
        '404':
          $ref: '#/components/responses/NotFoundError'

  /api/flows/{flowId}/tool-calls/{toolCallId}:
    get:
      operationId: 'getToolCall'
      summary: 'Get a specific tool call'
      description: 'Retrieves detailed information about a single tool call within a flow, including its input parameters, outcome, timing, and associated metadata.'
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
        - name: 'toolCallId'
          in: 'path'
          required: true
          description: 'The unique identifier of the tool call.'
          schema:
            type: 'string'
      responses:
        '200':
          description: 'The tool call details.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToolCall'
        '404':
          $ref: '#/components/responses/NotFoundError'

  #
  # ── Flow Files ─────────────────────────────────────────────────────────────
  #
  /api/flows/{flowId}/images/{imageId}:
    get:
      operationId: 'getFlowImage'
      summary: 'Get a flow screenshot'
      description: 'Retrieves a screenshot image captured during flow execution. The response is the raw image binary in PNG or JPEG format.'
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
        - name: 'imageId'
          in: 'path'
          required: true
          description: "The unique identifier of the image. Corresponds to a tool call's `postCallImageId`."
          schema:
            type: 'string'
      responses:
        '200':
          description: 'The screenshot image.'
          content:
            image/png:
              schema:
                type: 'string'
                format: 'binary'
            image/jpeg:
              schema:
                type: 'string'
                format: 'binary'
        '404':
          description: 'The flow or image was not found.'

  /api/flows/{flowId}/video:
    get:
      operationId: 'getFlowVideo'
      summary: 'Get a flow video recording'
      description: |
        Retrieves the video recording of a flow execution in WebM format. Supports HTTP range
        requests for efficient streaming and seeking.

        To stream the video, send a `Range` header (e.g. `Range: bytes=0-1048575`). The server
        will respond with `206 Partial Content` and the requested byte range.
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
        - name: 'Range'
          in: 'header'
          required: false
          description: 'Standard HTTP range header for partial content requests (e.g. `bytes=0-1048575`).'
          schema:
            type: 'string'
      responses:
        '200':
          description: 'The full video recording.'
          headers:
            Accept-Ranges:
              description: 'Indicates that the server supports range requests.'
              schema:
                type: 'string'
                example: 'bytes'
            Content-Length:
              description: 'The size of the video in bytes.'
              schema:
                type: 'integer'
          content:
            video/webm:
              schema:
                type: 'string'
                format: 'binary'
        '206':
          description: 'A partial video segment in response to a range request.'
          headers:
            Accept-Ranges:
              description: 'Indicates that the server supports range requests.'
              schema:
                type: 'string'
                example: 'bytes'
            Content-Range:
              description: 'The byte range and total size of the video (e.g. `bytes 0-1048575/5242880`).'
              schema:
                type: 'string'
            Content-Length:
              description: 'The size of the returned segment in bytes.'
              schema:
                type: 'integer'
          content:
            video/webm:
              schema:
                type: 'string'
                format: 'binary'
        '404':
          description: 'The flow or video recording was not found.'
        '416':
          description: 'The requested byte range is not satisfiable (e.g. the end offset is before the start offset).'

  /api/flows/{flowId}/browser-state:
    get:
      operationId: 'getFlowBrowserState'
      summary: 'Get browser state for a flow'
      description: |
        Retrieves the persisted browser storage state for a flow. This includes cookies,
        localStorage, and sessionStorage data captured from the browser context. The state
        is only available if the flow was configured with `browser.persistState: true`.
      tags:
        - 'flows'
      parameters:
        - $ref: '#/components/parameters/flowId'
      responses:
        '200':
          description: 'The browser storage state.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BrowserStorageState'
        '404':
          description: 'The flow or browser state was not found.'

  #
  # ── Tools ──────────────────────────────────────────────────────────────────
  #
  /api/tools:
    get:
      operationId: 'listTools'
      summary: 'List available tools'
      description: |
        Returns the set of browser automation tools available for use within flows. Each tool
        includes its name, description, input JSON Schema, and whether it requires a GPT
        configuration to operate.

        Use tool names from this list when specifying `allowedTools` or `toolCallsOnStart`
        in a `CreateFlowRequest`, or when proposing tool calls via `POST /api/flows/{flowId}/tool-calls`.
      tags:
        - 'tools'
      responses:
        '200':
          description: 'The list of available tools.'
          content:
            application/json:
              schema:
                type: 'array'
                items:
                  $ref: '#/components/schemas/SupportedTool'

  #
  # ── Agents ─────────────────────────────────────────────────────────────────
  #
  /api/agents:
    get:
      operationId: 'listAgents'
      summary: 'List all agent mappings'
      description: |
        Retrieves a mapping of all Donobu agents to their assigned GPT configuration names.
        Agents without an assigned configuration will have a `null` value.
      tags:
        - 'agents'
      responses:
        '200':
          description: 'A mapping of agent names to their assigned GPT configuration names.'
          content:
            application/json:
              schema:
                type: 'object'
                description: 'A key-value map where keys are agent names and values are GPT configuration names (or null).'
                additionalProperties:
                  type: 'string'
                  nullable: true
                example:
                  flow-runner: 'my-anthropic-config'

  /api/agents/{name}:
    get:
      operationId: 'getAgent'
      summary: "Get an agent's configuration"
      description: 'Retrieves the GPT configuration name currently assigned to a specific agent.'
      tags:
        - 'agents'
      parameters:
        - $ref: '#/components/parameters/agentName'
      responses:
        '200':
          description: "The agent's current GPT configuration assignment."
          content:
            application/json:
              schema:
                type: 'object'
                required:
                  - 'gptConfigName'
                properties:
                  gptConfigName:
                    type: 'string'
                    nullable: true
                    description: 'The name of the GPT configuration assigned to this agent, or `null` if none is assigned.'

    post:
      operationId: 'setAgent'
      summary: "Set an agent's configuration"
      description: |
        Assigns a GPT configuration to a specific agent. The referenced GPT configuration must
        already exist. Setting `gptConfigName` to `null` unlinks the agent from any configuration,
        which will prevent it from being used in autonomous flows.
      tags:
        - 'agents'
      parameters:
        - $ref: '#/components/parameters/agentName'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
                - 'gptConfigName'
              properties:
                gptConfigName:
                  type: 'string'
                  nullable: true
                  description: 'The name of the GPT configuration to assign, or `null` to unlink.'
      responses:
        '200':
          description: 'The updated agent assignment.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                  - 'gptConfigName'
                properties:
                  gptConfigName:
                    type: 'string'
                    nullable: true
                    description: 'The name of the GPT configuration now assigned to this agent.'
        '400':
          $ref: '#/components/responses/ValidationError'

  #
  # ── GPT Configs ────────────────────────────────────────────────────────────
  #
  /api/gpt-configs:
    get:
      operationId: 'listGptConfigs'
      summary: 'List all GPT configurations'
      description: |
        Retrieves all stored GPT configurations. Sensitive fields such as API keys and
        secret access keys are automatically redacted in the response.
      tags:
        - 'configs'
      responses:
        '200':
          description: 'A mapping of configuration names to their settings. Sensitive fields are redacted.'
          content:
            application/json:
              schema:
                type: 'object'
                description: 'A key-value map where keys are configuration names and values are GPT configuration objects.'
                additionalProperties:
                  $ref: '#/components/schemas/GptConfig'

  /api/gpt-configs/{name}:
    get:
      operationId: 'getGptConfig'
      summary: 'Get a GPT configuration'
      description: 'Retrieves a specific GPT configuration by name. Sensitive fields are automatically redacted.'
      tags:
        - 'configs'
      parameters:
        - $ref: '#/components/parameters/gptConfigName'
      responses:
        '200':
          description: 'The GPT configuration with sensitive fields redacted.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GptConfig'
        '404':
          $ref: '#/components/responses/NotFoundError'

    post:
      operationId: 'setGptConfig'
      summary: 'Create or update a GPT configuration'
      description: |
        Creates or updates a GPT configuration. The configuration is validated against the
        provider's requirements and a test request is made to verify connectivity and
        authentication before the configuration is saved.

        Sensitive fields are redacted in the response — the original credentials are securely
        stored but never returned.
      tags:
        - 'configs'
      parameters:
        - $ref: '#/components/parameters/gptConfigName'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GptConfig'
      responses:
        '200':
          description: 'The saved GPT configuration with sensitive fields redacted.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GptConfig'
        '400':
          $ref: '#/components/responses/ValidationError'

    delete:
      operationId: 'deleteGptConfig'
      summary: 'Delete a GPT configuration'
      description: |
        Permanently deletes a GPT configuration. Any agents that reference this configuration
        are automatically unlinked (their `gptConfigName` is set to `null`).
      tags:
        - 'configs'
      parameters:
        - $ref: '#/components/parameters/gptConfigName'
      responses:
        '200':
          description: 'The configuration was successfully deleted.'
          content:
            application/json:
              schema:
                type: 'object'
                description: 'An empty object indicating success.'

  #
  # ── Environment Data ───────────────────────────────────────────────────────
  #
  /api/env:
    get:
      operationId: 'listEnvData'
      summary: 'List all environment data'
      description: 'Retrieves all environment key-value pairs that are available for use by flows.'
      tags:
        - 'configs'
      responses:
        '200':
          description: 'A key-value map of all environment data.'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties:
                  type: 'string'
                description: 'A key-value map where keys are environment variable names and values are their string values.'

  /api/env/{key}:
    get:
      operationId: 'getEnvDatum'
      summary: 'Get an environment datum'
      description: 'Retrieves a single environment datum by its key.'
      tags:
        - 'configs'
      parameters:
        - $ref: '#/components/parameters/envKey'
      responses:
        '200':
          description: 'The environment datum.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                  - 'key'
                  - 'value'
                properties:
                  key:
                    type: 'string'
                    description: 'The environment variable name.'
                  value:
                    type: 'string'
                    description: 'The environment variable value.'
        '404':
          description: 'The environment datum was not found.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  error:
                    type: 'string'
                    description: 'A message indicating the datum was not found.'

    post:
      operationId: 'setEnvDatum'
      summary: 'Create or update an environment datum'
      description: 'Creates a new environment datum or updates the value of an existing one.'
      tags:
        - 'configs'
      parameters:
        - $ref: '#/components/parameters/envKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
                - 'value'
              properties:
                value:
                  type: 'string'
                  description: 'The value to set for the environment datum.'
      responses:
        '201':
          description: 'The environment datum was created or updated.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                  - 'key'
                  - 'value'
                properties:
                  key:
                    type: 'string'
                    description: 'The environment variable name.'
                  value:
                    type: 'string'
                    description: 'The environment variable value.'
        '400':
          $ref: '#/components/responses/ValidationError'

    delete:
      operationId: 'deleteEnvDatum'
      summary: 'Delete an environment datum'
      description: 'Permanently removes an environment datum by its key.'
      tags:
        - 'configs'
      parameters:
        - $ref: '#/components/parameters/envKey'
      responses:
        '200':
          description: 'The environment datum was successfully deleted.'

  #
  # ── System ─────────────────────────────────────────────────────────────────
  #
  /api/ping:
    get:
      operationId: 'ping'
      summary: 'Health check'
      description: 'A lightweight endpoint that returns HTTP 200 to confirm the server is running and accepting requests.'
      tags:
        - 'system'
      responses:
        '200':
          description: 'The server is healthy.'

  /api/schema:
    get:
      operationId: 'getSchema'
      summary: 'Get OpenAPI schema'
      description: |
        Returns the OpenAPI specification for the Donobu API. The response format depends on
        the `Accept` header: `text/html` returns an interactive HTML documentation page,
        otherwise the raw YAML specification is returned.
      tags:
        - 'system'
      parameters:
        - name: 'Accept'
          in: 'header'
          required: false
          description: 'Set to `text/html` to receive an interactive HTML documentation page. Otherwise, the raw YAML specification is returned.'
          schema:
            type: 'string'
      responses:
        '200':
          description: 'The OpenAPI schema in YAML or HTML format.'
          content:
            application/yaml:
              schema:
                type: 'string'
                description: 'The raw OpenAPI YAML specification.'
            text/html:
              schema:
                type: 'string'
                description: 'An interactive HTML documentation page.'

#
# ═══════════════════════════════════════════════════════════════════════════════
# COMPONENTS
# ═══════════════════════════════════════════════════════════════════════════════
#
components:
  # ── Parameters ─────────────────────────────────────────────────────────────
  parameters:
    flowId:
      name: 'flowId'
      in: 'path'
      required: true
      description: 'The unique identifier of the flow.'
      schema:
        type: 'string'

    agentName:
      name: 'name'
      in: 'path'
      required: true
      description: 'The agent name. Currently only `flow-runner` is supported.'
      schema:
        type: 'string'
        enum:
          - 'flow-runner'

    gptConfigName:
      name: 'name'
      in: 'path'
      required: true
      description: 'The name of the GPT configuration. Must be 1–100 characters and contain only alphanumeric characters, spaces, underscores, and hyphens.'
      schema:
        type: 'string'
        minLength: 1
        maxLength: 100
        pattern: '^[a-zA-Z0-9_ -]+$'

    envKey:
      name: 'key'
      in: 'path'
      required: true
      description: 'The environment variable name.'
      schema:
        type: 'string'

  # ── Responses ──────────────────────────────────────────────────────────────
  responses:
    ValidationError:
      description: 'The request contained invalid or malformed parameters.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationErrorResponse'

    NotFoundError:
      description: 'The requested resource was not found.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  # ── Schemas ────────────────────────────────────────────────────────────────
  schemas:
    # ── Flow Lifecycle ─────────────────────────────────────────────────────
    FlowState:
      type: 'string'
      description: |
        The possible states of a flow:
        - `UNSTARTED` — Created but not yet initialized.
        - `INITIALIZING` — Setting up the browser context and initial state.
        - `RUNNING_ACTION` — Executing a tool call.
        - `QUERYING_LLM_FOR_NEXT_ACTION` — The AI agent is determining the next action (AUTONOMOUS mode).
        - `WAITING_ON_USER_FOR_NEXT_ACTION` — Waiting for user input (INSTRUCT mode).
        - `PAUSED` — Execution temporarily suspended.
        - `RESUMING` — Transitioning from paused to active state.
        - `FAILED` — Terminated unsuccessfully.
        - `SUCCESS` — Completed successfully.
      enum:
        - 'UNSTARTED'
        - 'INITIALIZING'
        - 'QUERYING_LLM_FOR_NEXT_ACTION'
        - 'WAITING_ON_USER_FOR_NEXT_ACTION'
        - 'PAUSED'
        - 'RESUMING'
        - 'RUNNING_ACTION'
        - 'FAILED'
        - 'SUCCESS'

    RunMode:
      type: 'string'
      description: |
        The execution mode that determines how a flow operates and makes decisions:
        - `AUTONOMOUS` — The flow is driven by an AI agent that autonomously decides what actions
          to take based on the overall objective. Requires a valid GPT configuration.
        - `INSTRUCT` — The flow waits for explicit user-submitted tool calls at each step.
          No GPT configuration is required.
        - `DETERMINISTIC` — The flow executes a predetermined sequence of tool calls without any
          AI decision-making. Commonly used for replaying previously recorded flows.
      enum:
        - 'AUTONOMOUS'
        - 'INSTRUCT'
        - 'DETERMINISTIC'

    # ── Flow Metadata ──────────────────────────────────────────────────────
    FlowMetadata:
      type: 'object'
      description: 'Complete metadata describing a Donobu flow, including its configuration, current state, execution statistics, and result.'
      required:
        - 'id'
        - 'name'
        - 'createdWithDonobuVersion'
        - 'browser'
        - 'envVars'
        - 'gptConfigName'
        - 'hasGptConfigNameOverride'
        - 'customTools'
        - 'defaultMessageDuration'
        - 'runMode'
        - 'isControlPanelEnabled'
        - 'callbackUrl'
        - 'targetWebsite'
        - 'overallObjective'
        - 'allowedTools'
        - 'resultJsonSchema'
        - 'result'
        - 'inputTokensUsed'
        - 'completionTokensUsed'
        - 'maxToolCalls'
        - 'startedAt'
        - 'completedAt'
        - 'state'
        - 'nextState'
      properties:
        id:
          type: 'string'
          description: 'The unique identifier of this flow.'
        name:
          type: 'string'
          nullable: true
          description: 'The display name of this flow.'
        createdWithDonobuVersion:
          type: 'string'
          description: 'The version of Donobu that was used to create this flow.'
        browser:
          $ref: '#/components/schemas/BrowserConfig'
        envVars:
          type: 'array'
          nullable: true
          description: 'The names of environment variables that were made available to this flow.'
          items:
            type: 'string'
        gptConfigName:
          type: 'string'
          nullable: true
          description: 'The name of the GPT configuration used for this flow.'
        hasGptConfigNameOverride:
          type: 'boolean'
          description: 'If `true`, the `gptConfigName` was explicitly overridden when the flow was created (via `gptConfigNameOverride`).'
        customTools:
          type: 'array'
          nullable: true
          description: 'Custom user-defined tools that were available to this flow.'
          items:
            $ref: '#/components/schemas/CustomTool'
        defaultMessageDuration:
          type: 'number'
          nullable: true
          description: 'The default duration (in milliseconds) that in-browser messages from the flow agent are displayed to the user.'
        runMode:
          $ref: '#/components/schemas/RunMode'
        isControlPanelEnabled:
          type: 'boolean'
          description: 'Whether the in-browser control panel was enabled for this flow.'
        callbackUrl:
          type: 'string'
          nullable: true
          description: 'The URL that receives an HTTP POST when the flow completes. The POST body contains `{ "id": "<flowId>" }`.'
        targetWebsite:
          type: 'string'
          description: 'The website URL that the flow started at.'
        overallObjective:
          type: 'string'
          nullable: true
          description: 'The high-level objective the flow was tasked to accomplish.'
        allowedTools:
          type: 'array'
          description: 'The complete set of tool names this flow is permitted to use. Automatically populated by combining explicitly specified tools, custom tools, and tools referenced in `toolCallsOnStart`.'
          items:
            type: 'string'
        resultJsonSchema:
          nullable: true
          description: 'If non-null, the JSON Schema that the `result` field conforms to when the flow succeeds.'
        result:
          type: 'object'
          nullable: true
          additionalProperties: true
          description: |
            The final output of the flow, populated when the flow reaches a terminal state. The content
            depends on how the flow completes: if `resultJsonSchema` is specified and extraction succeeds,
            this contains a conforming object; if extraction fails, this contains error details; otherwise,
            this contains metadata from the final tool call, or `null` if no tools were executed.
        inputTokensUsed:
          type: 'number'
          description: 'The total number of LLM input tokens consumed during this flow.'
        completionTokensUsed:
          type: 'number'
          description: 'The total number of LLM completion tokens consumed during this flow.'
        maxToolCalls:
          type: 'number'
          nullable: true
          description: 'If non-null, the maximum number of tool calls allowed for this flow.'
        startedAt:
          type: 'number'
          nullable: true
          description: 'The Unix epoch millisecond timestamp of when the flow started.'
        completedAt:
          type: 'number'
          nullable: true
          description: 'The Unix epoch millisecond timestamp of when the flow completed.'
        state:
          $ref: '#/components/schemas/FlowState'
        nextState:
          nullable: true
          description: 'The planned next state of the flow, if a state transition is pending.'
          allOf:
            - $ref: '#/components/schemas/FlowState'
        videoDisabled:
          type: 'boolean'
          nullable: true
          description: 'If `true`, video recording was disabled for this flow. Useful for long-running flows where recordings may become too large.'

    # ── Create Flow Request ────────────────────────────────────────────────
    CreateFlowRequest:
      type: 'object'
      description: 'The request payload for creating a new Donobu flow.'
      required:
        - 'targetWebsite'
      properties:
        name:
          type: 'string'
          nullable: true
          maxLength: 256
          description: 'An optional display name for the flow. Must be fewer than 256 characters.'
        browser:
          nullable: true
          description: 'Browser configuration for the flow. If omitted, a default local Chromium browser is used.'
          allOf:
            - $ref: '#/components/schemas/BrowserConfig'
        envVars:
          type: 'array'
          nullable: true
          description: 'Names of environment variables (previously stored via `/api/env`) to make available to the flow.'
          items:
            type: 'string'
        customTools:
          type: 'array'
          nullable: true
          description: 'Custom user-defined tools to make available during the flow.'
          items:
            $ref: '#/components/schemas/CustomTool'
        defaultMessageDuration:
          type: 'number'
          nullable: true
          description: 'Default duration (in milliseconds) for in-browser messages from the flow agent.'
        callbackUrl:
          type: 'string'
          nullable: true
          description: 'A URL to receive an HTTP POST when the flow completes. The POST body will contain `{ "id": "<flowId>" }`.'
        targetWebsite:
          type: 'string'
          description: 'The website URL to navigate to when starting the flow.'
        overallObjective:
          type: 'string'
          nullable: true
          description: 'The high-level objective for the flow to accomplish. Required when `initialRunMode` is `AUTONOMOUS`; may be omitted for `INSTRUCT` or `DETERMINISTIC` modes.'
        allowedTools:
          type: 'array'
          nullable: true
          description: 'An explicit list of tool names the flow is permitted to use. If omitted, the default tool set is used.'
          items:
            type: 'string'
        resultJsonSchema:
          nullable: true
          description: "A JSON Schema defining the expected structure of the flow's result. When specified, Donobu will attempt to extract a conforming result object upon flow completion."
        maxToolCalls:
          type: 'number'
          nullable: true
          description: 'The maximum number of tool calls the flow may execute. Only used when `initialRunMode` is `AUTONOMOUS`.'
        videoDisabled:
          type: 'boolean'
          nullable: true
          description: 'If `true`, disables video recording for the flow. Useful for long-running flows.'
        gptConfigNameOverride:
          type: 'string'
          nullable: true
          description: "The name of a specific GPT configuration to use instead of the agent's default. If the configuration does not exist, the default agent configuration is used."
        initialRunMode:
          nullable: true
          description: 'The execution mode for the flow. If omitted, defaults to `AUTONOMOUS`.'
          allOf:
            - $ref: '#/components/schemas/RunMode'
        isControlPanelEnabled:
          type: 'boolean'
          nullable: true
          description: 'If `true`, enables the in-browser control panel overlay. Ignored when the browser is running in headless mode.'
        toolCallsOnStart:
          type: 'array'
          nullable: true
          description: 'An ordered list of tool calls to execute immediately when the flow starts. Commonly used with `DETERMINISTIC` mode for replaying recorded flows.'
          items:
            $ref: '#/components/schemas/ProposedToolCall'

    # ── Browser Config ─────────────────────────────────────────────────────
    BrowserConfig:
      type: 'object'
      description: 'Configuration for the browser instance used by a flow.'
      required:
        - 'using'
      properties:
        initialState:
          description: 'A reference to a source of browser state (cookies, localStorage, sessionStorage) to restore when starting the flow.'
          $ref: '#/components/schemas/BrowserStateReference'
        persistState:
          type: 'boolean'
          description: 'If `true`, the browser state is saved at the end of the flow and can be retrieved via `GET /api/flows/{flowId}/browser-state`.'
        using:
          description: 'The browser backend configuration. Exactly one of `device`, `remoteInstance`, or `browserBase`.'
          oneOf:
            - $ref: '#/components/schemas/DeviceBrowserConfig'
            - $ref: '#/components/schemas/RemoteInstanceBrowserConfig'
            - $ref: '#/components/schemas/BrowserBaseBrowserConfig'
          discriminator:
            propertyName: 'type'
            mapping:
              device: '#/components/schemas/DeviceBrowserConfig'
              remoteInstance: '#/components/schemas/RemoteInstanceBrowserConfig'
              browserBase: '#/components/schemas/BrowserBaseBrowserConfig'

    DeviceBrowserConfig:
      type: 'object'
      description: 'Configuration for a locally launched browser using a device profile.'
      required:
        - 'type'
      properties:
        type:
          type: 'string'
          enum:
            - 'device'
          description: 'Identifies this as a local device browser configuration.'
        deviceName:
          type: 'string'
          description: 'The name of the device profile to emulate (e.g. `Desktop Chromium`, `iPhone 14`). Defaults to `Desktop Chromium`.'
        headless:
          type: 'boolean'
          description: 'If `true`, the browser runs without a visible window.'
        proxy:
          $ref: '#/components/schemas/ProxyConfig'

    RemoteInstanceBrowserConfig:
      type: 'object'
      description: 'Configuration for connecting to a remote browser instance via the Chrome DevTools Protocol (CDP).'
      required:
        - 'type'
        - 'url'
      properties:
        type:
          type: 'string'
          enum:
            - 'remoteInstance'
          description: 'Identifies this as a remote browser instance configuration.'
        url:
          type: 'string'
          description: 'The Chrome DevTools Protocol (CDP) URL of the remote browser instance to connect to.'

    BrowserBaseBrowserConfig:
      type: 'object'
      description: 'Configuration for using a BrowserBase cloud browser session.'
      required:
        - 'type'
        - 'sessionArgs'
      properties:
        type:
          type: 'string'
          enum:
            - 'browserBase'
          description: 'Identifies this as a BrowserBase cloud browser configuration.'
        sessionArgs:
          $ref: '#/components/schemas/BrowserBaseSessionArgs'

    ProxyConfig:
      type: 'object'
      description: 'Network proxy configuration for the browser.'
      required:
        - 'server'
      properties:
        server:
          type: 'string'
          description: 'The proxy server URL (e.g. `http://proxy.example.com:8080`).'
        bypass:
          type: 'string'
          description: 'A comma-separated list of domains to bypass the proxy for.'
        username:
          type: 'string'
          description: 'The proxy authentication username.'
        password:
          type: 'string'
          description: 'The proxy authentication password.'

    BrowserBaseSessionArgs:
      type: 'object'
      description: 'Configuration arguments for a BrowserBase cloud browser session. See the [BrowserBase documentation](https://docs.browserbase.com/reference/api/create-a-session) for detailed field descriptions.'
      required:
        - 'projectId'
      properties:
        projectId:
          type: 'string'
          description: 'The BrowserBase project ID.'
        extensionId:
          type: 'string'
          description: 'An optional browser extension ID to load.'
        browserSettings:
          type: 'object'
          description: 'Browser-specific settings.'
          properties:
            context:
              type: 'object'
              description: 'Browser context settings.'
              properties:
                id:
                  type: 'string'
                  description: 'The context ID.'
            extensionId:
              type: 'string'
              description: 'Extension ID within browser settings.'
            viewport:
              type: 'object'
              description: 'Viewport dimensions.'
              properties:
                width:
                  type: 'integer'
                  description: 'Viewport width in pixels.'
                height:
                  type: 'integer'
                  description: 'Viewport height in pixels.'
            blockAds:
              type: 'boolean'
              description: 'If `true`, advertisements are blocked.'
            solveCaptchas:
              type: 'boolean'
              description: 'If `true`, captchas are automatically solved.'
            advancedStealth:
              type: 'boolean'
              description: 'If `true`, advanced stealth mode is enabled to reduce bot detection.'
        timeout:
          type: 'integer'
          description: 'Duration in seconds after which the session will automatically end.'
        keepAlive:
          type: 'boolean'
          description: 'If `true`, the session is kept alive even after disconnections.'
        proxies:
          description: "Proxy configuration. Set to `true` to use BrowserBase's built-in proxies, or provide an array of proxy configurations."
          oneOf:
            - type: 'boolean'
              description: "Set to `true` to use BrowserBase's default proxies."
            - type: 'array'
              description: 'An array of proxy configurations.'
              items:
                oneOf:
                  - $ref: '#/components/schemas/BrowserBaseProxy'
                  - $ref: '#/components/schemas/ExternalProxy'
        region:
          type: 'string'
          description: 'Geographic region for the browser session.'
        userMetadata:
          type: 'object'
          description: 'Custom metadata as key-value string pairs.'
          additionalProperties:
            type: 'string'

    BrowserBaseProxy:
      type: 'object'
      description: 'A BrowserBase managed proxy configuration.'
      required:
        - 'type'
      properties:
        type:
          type: 'string'
          enum:
            - 'browserbase'
          description: 'Identifies this as a BrowserBase managed proxy.'
        geolocation:
          type: 'object'
          description: 'Geographic targeting for the proxy.'
          required:
            - 'country'
          properties:
            country:
              type: 'string'
              minLength: 2
              maxLength: 2
              description: 'Two-letter ISO country code.'
            state:
              type: 'string'
              minLength: 2
              maxLength: 2
              description: 'Two-letter state code.'
            city:
              type: 'string'
              description: 'City name.'
        domainPattern:
          type: 'string'
          description: 'A domain pattern to route through this proxy.'

    ExternalProxy:
      type: 'object'
      description: 'An externally managed proxy configuration.'
      required:
        - 'type'
        - 'server'
      properties:
        type:
          type: 'string'
          enum:
            - 'external'
          description: 'Identifies this as an external proxy.'
        server:
          type: 'string'
          description: 'The proxy server URL.'
        username:
          type: 'string'
          description: 'The proxy authentication username.'
        password:
          type: 'string'
          description: 'The proxy authentication password.'
        domainPattern:
          type: 'string'
          description: 'A domain pattern to route through this proxy.'

    BrowserStateReference:
      description: |
        Specifies how to reference a source of browser state (cookies, localStorage, sessionStorage)
        to restore when starting a flow. Exactly one variant should be provided:
        - `id` — Reference a flow by its ID and restore its persisted browser state.
        - `name` — Reference a flow by its name and restore its persisted browser state.
        - `testId` — Reference a test by its ID and restore browser state from the most recent successful flow belonging to that test.
        - `json` — Provide the browser state inline as a JSON object.
      oneOf:
        - type: 'object'
          description: 'Reference a flow by ID to restore its browser state.'
          required:
            - 'type'
            - 'value'
          properties:
            type:
              type: 'string'
              enum:
                - 'id'
              description: 'Indicates the value is a flow ID.'
            value:
              type: 'string'
              description: 'The ID of the flow whose browser state should be restored.'
        - type: 'object'
          description: 'Reference a flow by name to restore its browser state.'
          required:
            - 'type'
            - 'value'
          properties:
            type:
              type: 'string'
              enum:
                - 'name'
              description: 'Indicates the value is a flow name.'
            value:
              type: 'string'
              description: 'The name of the flow whose browser state should be restored.'
        - type: 'object'
          description: 'Reference a test by ID to restore browser state from its most recent successful flow.'
          required:
            - 'type'
            - 'value'
          properties:
            type:
              type: 'string'
              enum:
                - 'testId'
              description: 'Indicates the value is a test ID.'
            value:
              type: 'string'
              description: 'A test ID. The browser state will be restored from the most recent successful flow belonging to this test.'
        - type: 'object'
          description: 'Provide browser state inline as a JSON object.'
          required:
            - 'type'
            - 'value'
          properties:
            type:
              type: 'string'
              enum:
                - 'json'
              description: 'Indicates the value is an inline browser storage state object.'
            value:
              $ref: '#/components/schemas/BrowserStorageState'
      discriminator:
        propertyName: 'type'

    BrowserStorageState:
      type: 'object'
      description: "The browser storage state, including cookies and per-origin localStorage/sessionStorage. Compatible with Playwright's `storageState` format."
      required:
        - 'cookies'
        - 'origins'
      properties:
        cookies:
          type: 'array'
          description: 'The browser cookies.'
          items:
            $ref: '#/components/schemas/Cookie'
        origins:
          type: 'array'
          description: 'Per-origin storage data (localStorage and optionally sessionStorage).'
          items:
            $ref: '#/components/schemas/StorageOrigin'

    Cookie:
      type: 'object'
      description: 'A browser cookie.'
      required:
        - 'name'
        - 'value'
        - 'domain'
        - 'path'
        - 'expires'
        - 'httpOnly'
        - 'secure'
        - 'sameSite'
      properties:
        name:
          type: 'string'
          description: 'The cookie name.'
        value:
          type: 'string'
          description: 'The cookie value.'
        domain:
          type: 'string'
          description: 'The domain the cookie belongs to.'
        path:
          type: 'string'
          description: 'The URL path the cookie is scoped to.'
        expires:
          type: 'number'
          description: 'The cookie expiration as a Unix epoch timestamp in seconds. A value of `-1` indicates a session cookie.'
        httpOnly:
          type: 'boolean'
          description: 'If `true`, the cookie is not accessible via JavaScript.'
        secure:
          type: 'boolean'
          description: 'If `true`, the cookie is only sent over HTTPS.'
        sameSite:
          type: 'string'
          enum:
            - 'Strict'
            - 'Lax'
            - 'None'
          description: "The cookie's SameSite policy."

    StorageOrigin:
      type: 'object'
      description: 'Storage data for a specific origin.'
      required:
        - 'origin'
        - 'localStorage'
      properties:
        origin:
          type: 'string'
          description: 'The origin URL (e.g. `https://example.com`).'
        localStorage:
          type: 'array'
          description: 'The localStorage entries for this origin.'
          items:
            $ref: '#/components/schemas/NameValuePair'
        sessionStorage:
          type: 'array'
          description: 'The sessionStorage entries for this origin.'
          items:
            $ref: '#/components/schemas/NameValuePair'

    NameValuePair:
      type: 'object'
      description: 'A simple name-value string pair, used for localStorage and sessionStorage entries.'
      required:
        - 'name'
        - 'value'
      properties:
        name:
          type: 'string'
          description: 'The storage key.'
        value:
          type: 'string'
          description: 'The storage value.'

    # ── Tool Calls ─────────────────────────────────────────────────────────
    ToolCall:
      type: 'object'
      description: 'A record of a completed tool call executed during a flow.'
      required:
        - 'id'
        - 'toolName'
        - 'parameters'
        - 'outcome'
        - 'postCallImageId'
        - 'page'
        - 'startedAt'
        - 'completedAt'
      properties:
        id:
          type: 'string'
          description: 'The unique identifier of this tool call.'
        toolName:
          type: 'string'
          description: 'The name of the tool that was called.'
        parameters:
          type: 'object'
          additionalProperties: true
          description: 'The parameters that were supplied to the tool.'
        outcome:
          $ref: '#/components/schemas/ToolCallResult'
        postCallImageId:
          type: 'string'
          nullable: true
          description: 'The ID of the screenshot taken after the tool call completed, or `null` if the page was closed. Use `GET /api/flows/{flowId}/images/{imageId}` to retrieve the image.'
        page:
          type: 'string'
          description: 'The URL of the webpage the tool was operating on.'
        startedAt:
          type: 'number'
          description: 'The Unix epoch millisecond timestamp of when the tool call started.'
        completedAt:
          type: 'number'
          description: 'The Unix epoch millisecond timestamp of when the tool call completed.'

    ToolCallResult:
      type: 'object'
      description: 'The outcome of a tool call execution.'
      required:
        - 'isSuccessful'
        - 'forLlm'
        - 'metadata'
      properties:
        isSuccessful:
          type: 'boolean'
          description: 'Whether the tool call completed successfully.'
        forLlm:
          type: 'string'
          description: 'A summary of the outcome intended for the AI agent to understand what happened.'
        metadata:
          type: 'object'
          nullable: true
          additionalProperties: true
          description: 'Additional metadata about the tool call outcome. Not shared with the AI agent.'

    ProposedToolCall:
      type: 'object'
      description: 'A tool call to be submitted for execution in an active flow.'
      required:
        - 'name'
        - 'parameters'
      properties:
        name:
          type: 'string'
          description: 'The name of the tool to call. Must match a tool from `GET /api/tools`.'
        parameters:
          type: 'object'
          additionalProperties: true
          description: "The input parameters for the tool. Must conform to the tool's input JSON Schema."
        toolCallId:
          type: 'string'
          nullable: true
          description: 'An optional identifier used to correlate proposed tool calls with their results.'

    CustomTool:
      type: 'object'
      description: 'A user-defined tool that extends the built-in tool set for a flow.'
      required:
        - 'name'
        - 'description'
        - 'inputSchema'
        - 'javascript'
      properties:
        name:
          type: 'string'
          description: 'The name of the custom tool.'
        description:
          type: 'string'
          description: 'A description of what the custom tool does. This is shown to the AI agent to help it decide when to use the tool.'
        inputSchema:
          type: 'object'
          additionalProperties: true
          description: 'A JSON Schema defining the expected input parameters for the tool.'
        javascript:
          type: 'string'
          description: "The JavaScript source code that implements the tool's functionality."

    SupportedTool:
      type: 'object'
      description: 'A browser automation tool available in the Donobu runtime.'
      required:
        - 'name'
        - 'description'
        - 'inputSchema'
        - 'requiresGpt'
      properties:
        name:
          type: 'string'
          description: 'The unique name of the tool.'
        description:
          type: 'string'
          description: 'A human-readable description of what the tool does.'
        inputSchema:
          type: 'object'
          additionalProperties: true
          description: 'A JSON Schema defining the expected input parameters for the tool.'
        requiresGpt:
          type: 'boolean'
          description: 'If `true`, this tool requires a GPT configuration to operate (it makes LLM calls internally).'

    # ── Code Generation ────────────────────────────────────────────────────
    CodeGenerationOptions:
      type: 'object'
      description: 'Options that control how Playwright test code is generated.'
      properties:
        areElementIdsVolatile:
          type: 'boolean'
          description: 'If `true`, ID-only selectors are dropped because element `id` attributes are considered volatile.'
        disableSelectorFailover:
          type: 'boolean'
          description: 'If `true`, only the first (most specific) selector is used, disabling automatic fail-over to broader selectors.'
        disableSelfHealingTests:
          type: 'boolean'
          description: 'If `true`, disables self-healing test behavior.'
        disablePullRequestCreation:
          type: 'boolean'
          description: 'If `true`, disables automatic pull request creation for self-healed tests.'
        runInHeadedMode:
          type: 'boolean'
          description: 'If `true`, generates tests that run with a visible browser window.'
        slowMotionDelay:
          type: 'number'
          minimum: 0
          description: 'Delay in milliseconds between actions. Set to `0` to disable.'
        playwrightScriptVariant:
          type: 'string'
          enum:
            - 'ai'
            - 'classic'
          description: 'The style of generated Playwright script. `ai` uses `page.ai()` calls; `classic` uses tool-by-tool Playwright API calls.'
        flowAnnotations:
          type: 'object'
          description: 'Optional annotations keyed by flow ID. Each annotation is added to `testInfo.annotations` at runtime for reporting.'
          additionalProperties:
            type: 'array'
            items:
              type: 'object'
              required:
                - 'type'
                - 'description'
              properties:
                type:
                  type: 'string'
                  description: 'The annotation type.'
                description:
                  type: 'string'
                  description: 'The annotation description.'

    ProjectFile:
      type: 'object'
      description: 'A file within a generated Playwright project.'
      required:
        - 'path'
        - 'content'
      properties:
        path:
          type: 'string'
          description: 'The relative file path within the project (e.g. `tests/login-flow.spec.ts`).'
        content:
          type: 'string'
          description: 'The file content.'

    # ── GPT Configurations ─────────────────────────────────────────────────
    GptConfig:
      description: |
        A configuration object that defines connection parameters and model settings for an
        AI provider. The `type` field determines which provider-specific fields are expected.
        Sensitive fields (such as `apiKey` and `secretAccessKey`) are automatically redacted
        in API responses.
      oneOf:
        - $ref: '#/components/schemas/AnthropicConfig'
        - $ref: '#/components/schemas/AnthropicAwsBedrockConfig'
        - $ref: '#/components/schemas/DonobuConfig'
        - $ref: '#/components/schemas/GoogleGeminiConfig'
        - $ref: '#/components/schemas/GoogleVertexConfig'
        - $ref: '#/components/schemas/OpenAiConfig'
        - $ref: '#/components/schemas/VercelAiConfig'
      discriminator:
        propertyName: 'type'
        mapping:
          ANTHROPIC: '#/components/schemas/AnthropicConfig'
          ANTHROPIC_AWS_BEDROCK: '#/components/schemas/AnthropicAwsBedrockConfig'
          DONOBU: '#/components/schemas/DonobuConfig'
          GOOGLE_GEMINI: '#/components/schemas/GoogleGeminiConfig'
          GOOGLE_VERTEX: '#/components/schemas/GoogleVertexConfig'
          OPENAI: '#/components/schemas/OpenAiConfig'
          VERCEL_AI: '#/components/schemas/VercelAiConfig'

    AnthropicConfig:
      type: 'object'
      description: 'GPT configuration for the Anthropic API.'
      required:
        - 'type'
        - 'apiKey'
        - 'modelName'
      properties:
        type:
          type: 'string'
          enum:
            - 'ANTHROPIC'
          description: 'Identifies this as an Anthropic configuration.'
        apiKey:
          type: 'string'
          description: 'The Anthropic API key. Redacted in responses.'
        modelName:
          type: 'string'
          description: 'The Anthropic model to use (e.g. `claude-sonnet-4-20250514`).'

    AnthropicAwsBedrockConfig:
      type: 'object'
      description: 'GPT configuration for Anthropic models accessed via AWS Bedrock.'
      required:
        - 'type'
        - 'modelName'
      properties:
        type:
          type: 'string'
          enum:
            - 'ANTHROPIC_AWS_BEDROCK'
          description: 'Identifies this as an Anthropic AWS Bedrock configuration.'
        region:
          type: 'string'
          description: 'The AWS region for the Bedrock endpoint.'
        accessKeyId:
          type: 'string'
          description: 'The AWS access key ID. If omitted, the default AWS credentials provider chain is used.'
        secretAccessKey:
          type: 'string'
          description: 'The AWS secret access key. Redacted in responses. Required if `accessKeyId` is provided.'
        modelName:
          type: 'string'
          description: 'The Bedrock model ID to use.'

    DonobuConfig:
      type: 'object'
      description: 'GPT configuration for the Donobu managed AI service.'
      required:
        - 'type'
        - 'apiKey'
      properties:
        type:
          type: 'string'
          enum:
            - 'DONOBU'
          description: 'Identifies this as a Donobu configuration.'
        apiKey:
          type: 'string'
          description: 'The Donobu API key. Redacted in responses.'

    GoogleGeminiConfig:
      type: 'object'
      description: 'GPT configuration for the Google Gemini (Generative AI) API.'
      required:
        - 'type'
        - 'apiKey'
        - 'modelName'
      properties:
        type:
          type: 'string'
          enum:
            - 'GOOGLE_GEMINI'
          description: 'Identifies this as a Google Gemini configuration.'
        apiKey:
          type: 'string'
          description: 'The Google Generative AI API key. Redacted in responses.'
        modelName:
          type: 'string'
          description: 'The Gemini model name to use.'

    GoogleVertexConfig:
      type: 'object'
      description: 'GPT configuration for Google Vertex AI.'
      required:
        - 'type'
        - 'modelName'
      properties:
        type:
          type: 'string'
          enum:
            - 'GOOGLE_VERTEX'
          description: 'Identifies this as a Google Vertex AI configuration.'
        modelName:
          type: 'string'
          description: 'The Vertex AI model name to use.'
        project:
          type: 'string'
          description: 'The Google Cloud project ID.'
        location:
          type: 'string'
          description: 'The Google Cloud region (e.g. `us-central1`).'
        baseURL:
          type: 'string'
          description: 'A custom base URL for the Vertex AI endpoint.'

    OpenAiConfig:
      type: 'object'
      description: 'GPT configuration for the OpenAI API.'
      required:
        - 'type'
        - 'apiKey'
        - 'modelName'
      properties:
        type:
          type: 'string'
          enum:
            - 'OPENAI'
          description: 'Identifies this as an OpenAI configuration.'
        apiKey:
          type: 'string'
          description: 'The OpenAI API key. Redacted in responses.'
        modelName:
          type: 'string'
          description: 'The OpenAI model to use (e.g. `gpt-4o`).'

    VercelAiConfig:
      type: 'object'
      description: 'GPT configuration for the Vercel AI SDK, supporting any provider available through the Vercel AI ecosystem.'
      required:
        - 'type'
        - 'provider'
        - 'modelName'
      properties:
        type:
          type: 'string'
          enum:
            - 'VERCEL_AI'
          description: 'Identifies this as a Vercel AI configuration.'
        provider:
          type: 'string'
          description: 'The Vercel AI provider identifier (e.g. `openai`, `anthropic`).'
        modelName:
          type: 'string'
          description: 'The model name to use with the specified provider.'

    # ── Error Responses ────────────────────────────────────────────────────
    ErrorResponse:
      type: 'object'
      description: 'A structured error response returned by the API for business logic errors.'
      required:
        - 'code'
        - 'message'
      properties:
        code:
          type: 'string'
          description: 'A machine-readable error code identifying the type of error.'
        message:
          type: 'string'
          description: 'A human-readable description of the error.'

    ValidationErrorResponse:
      type: 'object'
      description: 'Returned when request parameters or body fail validation.'
      required:
        - 'code'
        - 'errors'
      properties:
        code:
          type: 'string'
          enum:
            - 'INVALID_PARAM_VALUES'
          description: 'Always `INVALID_PARAM_VALUES` for validation errors.'
        errors:
          type: 'array'
          description: 'A list of individual validation issues.'
          items:
            type: 'object'
            required:
              - 'code'
              - 'message'
              - 'path'
            properties:
              code:
                type: 'string'
                description: 'A machine-readable code for the specific validation issue.'
              message:
                type: 'string'
                description: 'A human-readable description of the validation issue.'
              path:
                type: 'array'
                description: 'The path to the field that failed validation (e.g. `["browser", "using", "deviceName"]`).'
                items:
                  type: 'string'
