TextQL v2 API

The REST-native second generation of the TextQL Platform API. Standard HTTP methods, resource IDs in the path, query-parameter filtering and cursor pagination, a plain JSON error envelope, and SSE streaming over text/event-stream. Covers chats with Ana, connectors, playbooks, dashboards, sandcastles (managed Python sandboxes), Ontology changes, members, RBAC roles and platform API keys.

Operations 55

GET /v2/chats List Chats #
POST /v2/chats Create Chat #
POST /v2/chats/stream Stream Chat #
GET /v2/chats/{id} Get Chat #
GET /v2/chats/{id}/cells Get Chat Cells #
GET /v2/chats/{id}/cells/{cellId} Get Chat Cell #
POST /v2/chats/{id}/cancel Cancel Stream #
GET /v2/models List Models #
GET /v2/connectors List Connectors #
POST /v2/connectors Create Connector #
GET /v2/connectors/types List Connector Types #
POST /v2/connectors/test Test Connector #
PATCH /v2/connectors/{id} Update Connector #
DELETE /v2/connectors/{id} Delete Connector #
GET /v2/connectors/{id}/access Get Connector Access #
PUT /v2/connectors/{id}/access Update Connector Access #
GET /v2/playbooks List Playbooks #
POST /v2/playbooks Create Playbook #
GET /v2/playbooks/{id} Get Playbook #
PATCH /v2/playbooks/{id} Update Playbook #
DELETE /v2/playbooks/{id} Delete Playbook #
POST /v2/playbooks/{id}/deploy Deploy Playbook #
POST /v2/playbooks/{id}/run Run Playbook #
GET /v2/sandcastles List Sandcastles #
POST /v2/sandcastles Start Sandcastle #
GET /v2/sandcastles/{id} Get Sandcastle Status #
DELETE /v2/sandcastles/{id} Stop Sandcastle #
GET /v2/sandcastles/{id}/executions List Executions #
POST /v2/sandcastles/{id}/execute Execute Code #
POST /v2/sandcastles/{id}/query Load Connector Data #
GET /v2/sandcastles/{id}/files List Files #
POST /v2/sandcastles/{id}/files Upload File #
GET /v2/sandcastles/{id}/files/{path} Download File #
DELETE /v2/sandcastles/{id}/files/{path} Delete File #
POST /v2/sandcastles/{id}/exec Exec Command #
GET /v2/sandcastles/{id}/ontology/diff Diff Ontology (dry run) #
POST /v2/sandcastles/{id}/ontology/changes Create Ontology Change (writeback) #
GET /v2/changes List Changes #
GET /v2/changes/{id} Get Change #
POST /v2/changes/{id}/approve Approve Change #
POST /v2/changes/{id}/deny Deny Change #
POST /v2/changes/{id}/restore Restore Change #
GET /v2/members List Members #
POST /v2/members/invite Invite Member #
DELETE /v2/members/{id} Remove Member #
GET /v2/members/{id}/roles Get Member Roles #
POST /v2/members/{id}/roles Assign Role to Member #
DELETE /v2/members/{id}/roles/{roleId} Remove Role from Member #
GET /v2/roles List Roles #
POST /v2/roles Create Role #
PATCH /v2/roles/{id} Update Role #
POST /v2/api-keys Create API Key #
GET /v2/api-keys List API Keys #
POST /v2/api-keys/{id}/rotate Rotate API Key #
DELETE /v2/api-keys/{id} Revoke API Key #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/textql-v2-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no email required.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

textql-v2-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: TextQL v2 API
  version: "2.0"
  description: |
    REST API for TextQL platform operations. All endpoints require Bearer token authentication.

servers:
  - url: https://app.textql.com

security:
  - bearerAuth: []

tags:
  - name: Chat
    description: Create and manage AI chat sessions
  - name: Connectors
    description: List available data connectors
  - name: Playbooks
    description: Create, configure, and run automated playbooks
  - name: Sandcastles
    description: Manage Python sandbox environments for code execution
  - name: Changes
    description: Review, approve, and deny Ontology changes
  - name: API Keys
    description: Mint and revoke scoped platform API keys

paths:
  /v2/chats:
    get:
      tags:
        - Chat
      summary: List Chats
      description: List chats with optional search and pagination. Returns chats owned by the authenticated API key.
      operationId: v2.listChats
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 20
            minimum: 1
            maximum: 100
          description: Maximum number of chats to return (default 20, max 100)
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            minimum: 0
          description: Number of chats to skip
        - name: search_term
          in: query
          schema:
            type: string
          description: Filter chats by summary or first message content
        - name: sort_by
          in: query
          schema:
            type: string
            enum:
              - name
              - created_at
              - updated_at
            default: updated_at
          description: Field to sort by
        - name: sort_direction
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
          description: Sort direction
      responses:
        "200":
          description: Paginated list of chats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListChatsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - Chat
      summary: Create Chat
      description: |
        Send a question and receive a synchronous response. Supports JSON or multipart
        form-data (for file uploads). The response includes the model's answer and any
        generated assets.
      operationId: v2.createChat
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatRequest"
          multipart/form-data:
            schema:
              type: object
              required:
                - question
              properties:
                question:
                  type: string
                  description: The question to ask
                chat_id:
                  type: string
                  format: uuid
                  description: Existing chat ID to continue a conversation
                model:
                  type: string
                  description: >-
                    Optional model `id` from `GET /v2/models` (e.g.
                    `gemini_3_5_flash`). Omit for the org default. New chats only.
                  example: gemini_3_5_flash
                connector_ids:
                  type: array
                  items:
                    type: integer
                    format: int32
                  description: Connector IDs to query
                files:
                  type: array
                  items:
                    type: string
                    format: binary
                  maxItems: 10
                  description: One or more files to upload with the question
      responses:
        "200":
          description: Chat response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/chats/stream:
    post:
      tags:
        - Chat
      summary: Stream Chat
      description: |
        Send a question and receive a streaming response via Server-Sent Events.
        Supports the same request format as Create Chat. The stream emits metadata,
        text deltas, execution cells, assets, and a final done event.
      operationId: v2.streamChat
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatRequest"
          multipart/form-data:
            schema:
              type: object
              required:
                - question
              properties:
                question:
                  type: string
                chat_id:
                  type: string
                  format: uuid
                model:
                  type: string
                  description: >-
                    Optional model `id` from `GET /v2/models` (e.g.
                    `gemini_3_5_flash`). Omit for the org default. New chats only.
                  example: gemini_3_5_flash
                connector_ids:
                  type: array
                  items:
                    type: integer
                    format: int32
                files:
                  type: array
                  items:
                    type: string
                    format: binary
                  maxItems: 10
      responses:
        "200":
          description: Server-Sent Events stream
          content:
            text/event-stream:
              schema:
                type: string
                description: |
                  SSE stream with JSON data payloads. Event types:
                  - `{"type":"metadata","id":"...","created_at":"...","model":"...","chat_id":"...","is_continuation":bool}`
                  - `{"type":"text","text":"..."}`
                  - `{"type":"cell","cell":{...}}` — an execution step (same shape as ChatCell), emitted once when the step starts running (carrying the generated SQL or code) and again when it finishes (carrying outputs, result previews, and timing)
                  - `{"type":"asset","asset":{...}}`
                  - `{"type":"done","status":"completed|failed","error":"..."}`
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/chats/{id}:
    get:
      tags:
        - Chat
      summary: Get Chat
      description: Retrieve a chat by ID, including its messages and generated assets.
      operationId: v2.getChat
      parameters:
        - $ref: "#/components/parameters/ChatId"
      responses:
        "200":
          description: Chat details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetChatResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/chats/{id}/cells:
    get:
      tags:
        - Chat
      summary: Get Chat Cells
      description: "Retrieve a chat's cells, the per-step execution detail behind each answer: user and assistant messages, generated SQL and Python with their outputs, and the assets each step produced, in conversation order. Paginated newest-first: the default page returns the most recent cells, and offset skips past them toward older ones. Pages extend backward to the start of a conversation turn, so a page can contain slightly more than limit cells."
      operationId: v2.getChatCells
      parameters:
        - $ref: "#/components/parameters/ChatId"
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 200
            maximum: 500
          description: Maximum cells per page (values outside 1-500 fall back to 200)
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            default: 0
          description: Number of most-recent cells to skip
      responses:
        "200":
          description: Chat cells
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetChatCellsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/chats/{id}/cells/{cellId}:
    get:
      tags:
        - Chat
      summary: Get Chat Cell
      description: Retrieve a single cell from a chat by ID.
      operationId: v2.getChatCell
      parameters:
        - $ref: "#/components/parameters/ChatId"
        - $ref: "#/components/parameters/CellId"
      responses:
        "200":
          description: Chat cell
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatCell"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/chats/{id}/cancel:
    post:
      tags:
        - Chat
      summary: Cancel Stream
      description: Cancel a running chat stream.
      operationId: v2.cancelStream
      parameters:
        - $ref: "#/components/parameters/ChatId"
      responses:
        "200":
          description: Cancellation result
          content:
            application/json:
              schema:
                type: object
                properties:
                  cancelled:
                    type: boolean
                    description: Whether the stream was successfully cancelled
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/models:
    get:
      tags:
        - Chat
      summary: List Models
      description: >-
        List the models the authenticated organization may run chats on. The
        result is scoped to the org's enabled-model catalog and the caller's role
        allow-list, so it reflects exactly what `POST /v2/chats` will accept in
        its `model` field. Pass an entry's `id` back as that field.
      operationId: v2.listModels
      responses:
        "200":
          description: Available models
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListModelsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/connectors:
    get:
      tags:
        - Connectors
      summary: List Connectors
      description: List all data connectors available to the authenticated organization.
      operationId: v2.listConnectors
      responses:
        "200":
          description: List of connectors
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Connector"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - Connectors
      summary: Create Connector
      description: |
        Create a new data connector from the supplied configuration. Creation
        validates the config but does not open a connection — call
        `POST /v2/connectors/test` first if you want to verify reachability.
      operationId: v2.createConnector
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateConnectorRequest"
      responses:
        "201":
          description: Created connector
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Connector"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/connectors/types:
    get:
      tags:
        - Connectors
      summary: List Connector Types
      description: |
        Enumerate every supported connector type and the fields each requires,
        so you can build a valid `config` without reading the proto. For each
        type, `connector_type` is the value to set as `config.connector_type`
        and `config_key` is the metadata object to nest under `config`.

        Fields flagged `confidential` are write-only (passwords, keys, tokens) —
        they are never returned by read endpoints, and when `optional_on_update`
        is true they may be omitted on `PATCH` to preserve the stored value.
      operationId: v2.listConnectorTypes
      responses:
        "200":
          description: Supported connector types and their field schemas
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListConnectorTypesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/connectors/test:
    post:
      tags:
        - Connectors
      summary: Test Connector
      description: |
        Test a connector configuration without persisting it. A failed
        connection is reported as `200` with `{"success": false, "error": "..."}`
        — the request itself succeeded, only the downstream connection failed.
        HTTP error statuses are reserved for an invalid config (`400`) or
        auth/permission failures.

        Pass `connector_id` to test changes against an existing connector:
        confidential fields left empty in the request are filled in from the
        stored connector before the connection is attempted.
      operationId: v2.testConnector
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestConnectorRequest"
      responses:
        "200":
          description: Test result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TestConnectorResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/connectors/{id}:
    patch:
      tags:
        - Connectors
      summary: Update Connector
      description: |
        Update an existing connector. The `connector_type` in the body must
        match the stored connector's type. Confidential fields (passwords, keys,
        tokens) left empty are preserved from the stored connector, so you only
        need to send the fields you are changing.
      operationId: v2.updateConnector
      parameters:
        - $ref: "#/components/parameters/ConnectorId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateConnectorRequest"
      responses:
        "200":
          description: Updated connector
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Connector"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - Connectors
      summary: Delete Connector
      description: |
        Delete a connector by id. Example/system connectors cannot be deleted
        (TextQL Usage connectors return `400`; example connectors are hidden
        rather than removed).
      operationId: v2.deleteConnector
      parameters:
        - $ref: "#/components/parameters/ConnectorId"
      responses:
        "200":
          description: Deletion result
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                    format: int32
                  success:
                    type: boolean
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/connectors/{id}/access:
    get:
      tags:
        - Connectors
      summary: Get Connector Access
      description: |
        Get a connector's access configuration: its org-wide visibility and the
        member, role, and group grants on it. Requires read access to the
        connector.
      operationId: v2.getConnectorAccess
      parameters:
        - $ref: "#/components/parameters/ConnectorId"
      responses:
        "200":
          description: Current access configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectorAccess"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    put:
      tags:
        - Connectors
      summary: Update Connector Access
      description: |
        Declaratively replace a connector's access configuration. Grants not in
        the request are revoked, new ones are created, and `is_public` sets
        org-wide visibility. The caller's own owner grant is always preserved,
        even when omitted from `grants`.

        Requires owner access to the connector (or org admin). Grants are
        validated (members, roles, and groups must exist in the organization)
        up front, and the replacement is applied atomically in a single
        transaction, so a failed request leaves access unchanged.
      operationId: v2.updateConnectorAccess
      parameters:
        - $ref: "#/components/parameters/ConnectorId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateConnectorAccessRequest"
      responses:
        "200":
          description: Resulting access configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectorAccess"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/playbooks:
    get:
      tags:
        - Playbooks
      summary: List Playbooks
      description: List playbooks with optional filtering, sorting, and pagination.
      operationId: v2.listPlaybooks
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            format: int64
          description: Maximum number of playbooks to return
        - name: offset
          in: query
          schema:
            type: integer
            format: int64
          description: Number of playbooks to skip
        - name: search_term
          in: query
          schema:
            type: string
          description: Filter playbooks by name
        - name: sort_by
          in: query
          schema:
            type: string
            enum:
              - name
              - created_at
              - updated_at
          description: Field to sort by
        - name: sort_direction
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
          description: Sort direction
        - name: status_filter
          in: query
          schema:
            type: string
            enum:
              - draft
              - deployed
          description: Filter by playbook status
      responses:
        "200":
          description: Paginated list of playbooks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListPlaybooksResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - Playbooks
      summary: Create Playbook
      description: Create a new empty playbook with default settings.
      operationId: v2.createPlaybook
      responses:
        "201":
          description: Created playbook
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Playbook"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/playbooks/{id}:
    get:
      tags:
        - Playbooks
      summary: Get Playbook
      description: Retrieve a playbook by ID, including its recent reports.
      operationId: v2.getPlaybook
      parameters:
        - $ref: "#/components/parameters/PlaybookId"
        - name: limit
          in: query
          schema:
            type: integer
            format: int64
          description: Maximum number of reports to return
        - name: offset
          in: query
          schema:
            type: integer
            format: int64
          description: Number of reports to skip
      responses:
        "200":
          description: Playbook details with reports
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetPlaybookResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - Playbooks
      summary: Update Playbook
      description: |
        Update a playbook's configuration. All fields are optional; only provided
        fields are updated.
      operationId: v2.updatePlaybook
      parameters:
        - $ref: "#/components/parameters/PlaybookId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePlaybookRequest"
      responses:
        "200":
          description: Updated playbook
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Playbook"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - Playbooks
      summary: Delete Playbook
      description: Delete a playbook by ID.
      operationId: v2.deletePlaybook
      parameters:
        - $ref: "#/components/parameters/PlaybookId"
      responses:
        "200":
          description: Deletion confirmation
          content:
            application/json:
              schema:
                type: object
                properties:
                  playbook_id:
                    type: string
                    format: uuid
                  deleted_at:
                    type: string
                    format: date-time
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/playbooks/{id}/deploy:
    post:
      tags:
        - Playbooks
      summary: Deploy Playbook
      description: Deploy a playbook, making it active and ready to run on its schedule.
      operationId: v2.deployPlaybook
      parameters:
        - $ref: "#/components/parameters/PlaybookId"
      responses:
        "200":
          description: Deployment confirmation
          content:
            application/json:
              schema:
                type: object
                properties:
                  playbook_id:
                    type: string
                    format: uuid
                  deployed_at:
                    type: string
                    format: date-time
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/playbooks/{id}/run:
    post:
      tags:
        - Playbooks
      summary: Run Playbook
      description: |
        Execute a playbook and return the generated report. Use `dry_run: true` to
        validate without executing. Returns 504 if execution times out.
      operationId: v2.runPlaybook
      parameters:
        - $ref: "#/components/parameters/PlaybookId"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                dry_run:
                  type: boolean
                  default: false
                  description: If true, validate without executing
      responses:
        "200":
          description: Playbook execution result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunPlaybookResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "504":
          description: Execution timed out
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v2/sandcastles:
    get:
      tags:
        - Sandcastles
      summary: List Sandcastles
      description: |
        List sandboxes for the authenticated organization with cursor-based
        pagination.

        Each item reports a `status`:
        - `running` — a live worker record was seen recently. Liveness is
          eventually consistent: a sandbox that died abruptly may continue to
          report `running` for a short window (up to ~1 hour).
        - `stale` — the lease is open but no live worker record exists; the
          worker is likely gone. Call `DELETE /v2/sandcastles/{id}` (Stop Sandbox)
          to clear it.
        - `unknown` — liveness could not be determined (cache unavailable); the
          lease is open.
        - `stopped` — the sandbox has been released.

        `GET /v2/sandcastles/{id}` is the authoritative live check for a single
        sandbox.
      operationId: v2.listSandboxes
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum:
              - running
              - stopped
              - all
            default: running
          description: |
            Filter by lease state. `running` (default) returns sandboxes with an
            open lease — individual items may report `running`, `stale`, or
            `unknown`. `stopped` returns released sandboxes. `all` returns both.
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            minimum: 1
            maximum: 200
            default: 50
          description: Maximum number of sandboxes to return (default 50, max 200)
        - name: cursor
          in: query
          schema:
            type: string
          description: |
            Opaque pagination cursor from a previous response's `next_cursor`.
            Omit to start from the first page.
      responses:
        "200":
          description: Paginated list of sandboxes
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListSandboxesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/respons

# --- truncated at 32 KB (125 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/textql/refs/heads/main/openapi/textql-v2-openapi.yml