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/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/RateLimited" "500": $ref: "#/components/responses/InternalError" post: tags: - Sandcastles summary: Start Sandcastle description: Start a Python sandcastle for code execution. Omit the body to create a new sandcastle, or pass back a sandbox_id from create/list to restart that one. operationId: v2.startSandbox requestBody: required: false content: application/json: schema: type: object properties: sandbox_id: type: string description: Restart an existing sandcastle by passing back the sandbox_id returned by create or list; omit to create a new sandcastle. You can only restart your own sandcastles. responses: "201": description: Started sandbox content: application/json: schema: type: object properties: sandbox_id: type: string description: Unique sandbox identifier created_at: type: string format: date-time "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": description: The sandbox_id to restart is malformed or does not belong to your organization content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "429": $ref: "#/components/responses/RateLimited" "500": $ref: "#/components/responses/InternalError" /v2/sandcastles/{id}: get: tags: - Sandcastles summary: Get Sandcastle Status description: Get the current status of a sandbox, including memory usage and loaded dataframes. operationId: v2.getSandboxStatus parameters: - $ref: "#/components/parameters/SandboxId" responses: "200": description: Sandbox status content: application/json: schema: $ref: "#/components/schemas/SandboxStatus" "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: - Sandcastles summary: Stop Sandcastle description: Stop and destroy a running sandbox. operationId: v2.stopSandbox parameters: - $ref: "#/components/parameters/SandboxId" responses: "200": description: Stop confirmation content: application/json: schema: type: object properties: success: type: boolean "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/sandcastles/{id}/executions: get: tags: - Sandcastles summary: List Executions description: | List a sandbox's recorded executions (Python, bash, SQL, TQL), newest first, with cursor-based pagination. Every execution run in a sandbox — via Execute Code, Load Connector Data, or chat — is recorded here. Readable even after the sandbox is stopped, so it doubles as a post-mortem audit trail. Retention: records are kept for **30 days**, then purged. `input` is stored in full; `output_preview` is truncated (large output ends with a `…[truncated]` marker) and never includes generated files or dataframes. operationId: v2.listSandboxExecutions parameters: - $ref: "#/components/parameters/SandboxId" - name: limit in: query schema: type: integer format: int32 minimum: 1 maximum: 200 default: 50 description: Maximum number of executions 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 (newest) page. responses: "200": description: Paginated list of executions, newest first content: application/json: schema: $ref: "#/components/schemas/ListSandboxExecutionsResponse" "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/sandcastles/{id}/execute: post: tags: - Sandcastles summary: Execute Code description: | Execute Python code in a sandbox. Returns stdout/stderr output, generated files, and dataframe info. Executions are recorded — see [GET /v2/sandcastles/{id}/executions](/api-reference/v2/sandbox/list-executions). operationId: v2.executeCode parameters: - $ref: "#/components/parameters/SandboxId" requestBody: required: true content: application/json: schema: type: object required: - code properties: code: type: string description: Python code to execute responses: "200": description: Execution result content: application/json: schema: $ref: "#/components/schemas/ExecuteCodeResponse" "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/sandcastles/{id}/query: post: tags: - Sandcastles summary: Load Connector Data description: | Load data from a connector into a sandbox dataframe. Provide **exactly one** of `query` (an inline SQL query) or `tql_path` (the Ontology path of a saved `.tql` file) — sending both or neither returns `400 invalid_request`. `tql_path` runs **saved Ontology `.tql` files** that the caller's roles can already see — it does not compile caller-authored TQL. A path that is not visible to the member's roles returns `404 not_found`, indistinguishable from a path that does not exist. A path that does not end in `.tql`, or a file that fails to render, returns `400` with the renderer's message included. The response shape is the same for both branches. Executions are recorded — see [GET /v2/sandcastles/{id}/executions](/api-reference/v2/sandbox/list-executions). operationId: v2.loadConnectorData parameters: - $ref: "#/components/parameters/SandboxId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LoadConnectorDataRequest" responses: "200": description: Query result loaded into dataframe content: application/json: schema: $ref: "#/components/schemas/LoadConnectorDataResponse" "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/sandcastles/{id}/files: get: tags: - Sandcastles summary: List Files description: | List files in the sandbox working directory (non-recursive). Pass `?path=` to list a subdirectory. Hidden dotfiles (including internal sandbox state) are omitted. The `library/` subtree is the mounted Ontology: it lists the sandbox's own copy when that has content, otherwise the org library pruned to the caller's role permissions. operationId: v2.listFiles parameters: - $ref: "#/components/parameters/SandboxId" - name: path in: query required: false schema: type: string description: Relative subdirectory to list; defaults to the working directory root. responses: "200": description: Directory listing content: application/json: schema: type: object properties: files: type: array items: type: object properties: name: type: string size_bytes: type: integer is_dir: type: boolean modified_at: type: string format: date-time "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" post: tags: - Sandcastles summary: Upload File description: Upload a file to a sandbox environment. operationId: v2.uploadFile parameters: - $ref: "#/components/parameters/SandboxId" requestBody: required: true content: multipart/form-data: schema: type: object required: - file properties: file: type: string format: binary description: File to upload responses: "200": description: Upload confirmation content: application/json: schema: type: object properties: filename: type: string description: Basename of the uploaded file size_bytes: type: integer description: Size of the uploaded file in bytes "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/sandcastles/{id}/files/{path}: get: tags: - Sandcastles summary: Download File description: | Stream the bytes of a single file from the sandbox working directory. `path` is the file's path within the sandbox (subdirectories allowed, slashes permitted). Traversal outside the working directory and hidden dotfiles are rejected. `library/…` paths serve the sandbox's own copy when present, otherwise the org library pruned to the caller's role permissions (OWNERS files are never served). operationId: v2.downloadFile parameters: - $ref: "#/components/parameters/SandboxId" - $ref: "#/components/parameters/SandboxFilePath" responses: "200": description: File contents content: application/octet-stream: schema: type: string format: binary "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: - Sandcastles summary: Delete File description: | Remove a single file from the sandbox working directory. `path` is the file's path within the sandbox. Directories are rejected; the S3 copy (if any) is not affected. operationId: v2.deleteFile parameters: - $ref: "#/components/parameters/SandboxId" - $ref: "#/components/parameters/SandboxFilePath" responses: "200": description: Delete confirmation content: application/json: schema: type: object properties: 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/sandcastles/{id}/exec: post: tags: - Sandcastles summary: Exec Command description: | Run a one-shot command in the sandbox as a fresh process (via the worker shell). Unlike [Execute Code](/api-reference/v2/sandbox/execute-code), which feeds Python into a persistent kernel, `/exec` shares no interpreter state between calls — state carries only via the filesystem — and returns raw stdout/stderr plus the exit code. `kind` selects the interpreter: `bash` (default) runs the command in a shell; `python` runs it as a one-shot Python program (no kernel state, no dataframe/plot capture). A non-zero exit code is returned in the body, not as an HTTP error. Executions are recorded — see [GET /v2/sandcastles/{id}/executions](/api-reference/v2/sandbox/list-executions). operationId: v2.exec parameters: - $ref: "#/components/parameters/SandboxId" requestBody: required: true content: application/json: schema: type: object required: - command properties: command: type: string description: The command (bash) or program source (python) to run. kind: type: string enum: - bash - python default: bash description: Interpreter to run the command with. env: type: object additionalProperties: type: string description: Extra environment variables for the process. responses: "200": description: Command result content: application/json: schema: type: object properties: stdout: type: string stderr: type: string exit_code: type: integer error: type: string description: Worker-level error (e.g. timeout); empty on normal completion. "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/sandcastles/{id}/ontology/diff: get: tags: - Sandcastles summary: Diff Ontology (dry run) description: | Report the pending changes in the sandbox's Ontology mount (`/sandbox/files/library`) relative to its baseline snapshot — **without** authoring a change. Use this to decide whether there is anything to write back before calling [Create Ontology Change](/api-reference/v2/sandbox/create-ontology-change). The diff is scoped to the caller's `OWNERS` permissions: the mount the session sees was pruned at materialization, so only permitted paths can appear here. operationId: v2.ontologyDiff parameters: - $ref: "#/components/parameters/SandboxId" responses: "200": description: Pending library changes content: application/json: schema: type: object properties: has_changes: type: boolean description: True when the library differs from its baseline snapshot. diffs: type: array items: $ref: "#/components/schemas/LibraryChangeDiff" raw_diff: type: string description: The unified git diff, normalized to library-relative paths. "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/sandcastles/{id}/ontology/changes: post: tags: - Sandcastles summary: Create Ontology Change (writeback) description: | Persist the sandbox's edits to `/sandbox/files/library` back to the organization's **Ontology** by authoring a reviewable change. This is the API equivalent of the in-product writeback tool: it diffs the session library against its baseline snapshot, commits the delta to a change ref, detects merge conflicts, and submits the change for review. **Stage first.** A change can only be created from real file changes — edit files under `/sandbox/files/library` (via [Exec](/api-reference/v2/sandbox/execute-code) or [Upload](/api-reference/v2/sandbox/upload-file)) **before** calling this. Creating a new change with no changes returns `400`. **Review & permissions.** The change is submitted `OPEN` for admin review (or `DRAFT` when `draft` is true). Every changed path is revalidated against the caller's `OWNERS` permissions at merge — a change cannot widen access. If an auto-approve rule matches, the response status is `APPROVED` and the change is already live. **Updates & conflicts.** Pass `change_number` to file a new revision of an existing open/draft change (title/description optional — inherited if omitted). If the library has drifted, the response has `has_conflicts: true`, the session's `library/` is re-materialized with `.rej` markers, and the change stays `RESERVED` until you resolve the conflicts and re-submit with the same `change_number`. operationId: v2.createOntologyChange parameters: - $ref: "#/components/parameters/SandboxId" requestBody: required: false content: application/json: schema: type: object properties: title: type: string maxLength: 50 description: Short summary (≤50 chars). Required for a new change. description: type: string description: Markdown explanation of the changes. Required for a new change. draft: type: boolean default: false description: File as DRAFT instead of OPEN (not yet ready for review). change_number: type: integer format: int32 description: Set to revise an existing change (creates a new changeset). responses: "201": description: Change created or updated content: application/json: schema: type: object properties: change_id: type: string change_number: type: integer format: int32 status: type: string enum: [open, draft, approved, reserved] description: Change lifecycle state after submission. git_ref: type: string has_conflicts: type: boolean conflicts: type: string description: Human-readable conflict view (present when has_conflicts). auto_approved: type: boolean diffs: type: array items: $ref: "#/components/schemas/LibraryChangeDiff" raw_diff: type: string "400": description: No changes to write back, or invalid request content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "409": description: Referenced change is approved/denied and cannot be updated content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "429": $ref: "#/components/responses/RateLimited" "500": $ref: "#/components/responses/InternalError" /v2/changes: get: tags: - Changes summary: List Changes description: | List the organization's Ontology changes. Results are scoped to the caller's `OWNERS` read access — changes touching only paths you cannot read are omitted. Each `OPEN`/`DRAFT`/`DENIED` change carries a `capabilities` object indicating which actions you may take, identical to the review UI. operationId: v2.listChanges parameters: - name: status in: query required: false description: Filter by change status. Repeatable. schema: type: array items: type: string enum: - reserved - draft - open - approved - denied - name: page_size in: query required: false schema: type: integer format: int32 - name: page_token in: query required: false schema: type: string - name: include_auto_approved in: query required: false schema: type: boolean responses: "200": description: A page of changes content: application/json: schema: type: object properties: changes: type: array items: $ref: "#/components/schemas/Change" next_page_token: type: string counts: type: object additionalProperties: type: integer format: int32 "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/RateLimited" "500": $ref: "#/components/responses/InternalError" /v2/changes/{id}: get: tags: - Changes summary: Get Change description: | Fetch a single change with its file diffs and the caller's `capabilities`. Read access is scoped to the caller's `OWNERS` permissions. The `git_ref` is required as `expected_git_ref` when approving, denying, or restoring. operationId: v2.getChange parameters: - $ref: "#/components/parameters/ChangeId" - name: revision in: query required: false description: Fetch a historical revision (1-based); defaults to latest. schema: type: integer format: int32 responses: "200": description: The change content: application/json: schema: $ref: "#/components/schemas/Change" "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/changes/{id}/approve: post: tags: - Changes summary: Approve Change description: | Record the caller's approval and merge the change to the Ontology once the folder's approval rule is satisfied. The caller must have `OWNERS` write authority on every path the change touches. Self-approval is rejected when a folder rule requires distinct approvers. operationId: v2.approveChange parameters: - $ref: "#/components/parameters/ChangeId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ExpectedGitRefBody" responses: "200": description: Approval recorded (and merged when the rule is satisfied) content: application/json: schema: type: object properties: merged: type: boolean approval_count: type: integer format: int32 required_approvals: type: integer format: int32 already_approved: type: boolean "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "409": description: Change is not OPEN/DRAFT, or expected_git_ref is stale content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "429": $ref: "#/components/responses/RateLimited" "500": $ref: "#/components/responses/InternalError" /v2/changes/{id}/deny: post: tags: - Changes summary: Deny Change description: | Mark a change `DENIED`. The change author may deny their own change; otherwise the caller needs `OWNERS` write authority on every changed path. The git ref is preserved so the change can be restored later. operationId: v2.denyChange parameters: - $ref: "#/components/parameters/ChangeId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ExpectedGitRefBody" responses: "200": description: Change denied content: application/json: schema: $ref: "#/components/schemas/SuccessResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "409": description: Change is not OPEN/DRAFT, or expected_git_ref is stale content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "429": $ref: "#/components/responses/RateLimited" "500": $ref: "#/components/responses/InternalError" /v2/changes/{id}/restore: post: tags: - Changes summary: Restore Change description: | Reopen a `DENIED` change back to `OPEN`. Same authority as deny: the author, or a caller with `OWNERS` write authority on every changed path. operationId: v2.restoreChange parameters: - $ref: "#/components/parameters/ChangeId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ExpectedGitRefBody" responses: "200": description: Change restored content: application/json: schema: $ref: "#/components/schemas/SuccessResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "409": description: Change is not DENIED, or expected_git_ref is stale content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "429": $ref: "#/components/responses/RateLimited" "500": $ref: "#/components/responses/InternalError" /v2/members: get: tags: - Members summary: List Members description: | List the human members of the caller's organization, including each member's assigned role names. Requires the `member:read` permission. operationId: v2.listMembers responses: "200": description: The organization's members content: application/json: schema: $ref: "#/components/schemas/ListMembersResponse" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/RateLimited" "500": $ref: "#/components/responses/InternalError" /v2/members/invite: post: tags: - Members summary: Invite Member description: | Invite a new member to the caller's organization by email. The member is created immediately and receives a branded invitation email. Requires the `member:write` permission. operationId: v2.inviteMember requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/InviteMemberRequest" responses: "200": description: Invitation result content: application/json: schema: type: object properties: success: type: boolean "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/members/{id}: delete: tags: - Members summary: Remove Member description: | Remove a member from the caller's organization. Soft delete by default; pass `hard_delete=true` to purge. Requires the `member:write` permission. operationId: v2.deleteMember parameters: - $ref: "#/components/parameters/MemberId" - name: hard_delete in: query required: false schema: type: boolean description: Set to true to permanently delete instead of soft delete. responses: "200": description: Removal result content: application/json: schema: type: object properties: id: type: string success: type: boolean "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/members/{id}/roles: get: tags: - Members summary: Get Member Roles description: | List the RBAC roles assigned to a member. Requires the `role:read` permission. operationId: v2.getMemberRoles parameters: - $ref: "#/components/parameters/MemberId" responses: "200": description: The member's roles content: application/json: schema: $ref: "#/components/schemas/MemberRolesResponse" "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" post: tags: - Members summary: Assign Role to Member description: | Assign an RBAC role to a member. Requires the `role:write` permission. Service-account roles are immutable and cannot be changed this way. operationId: v2.assignMemberRole parameters: - $ref: "#/components/parameters/MemberId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AssignMemberRoleRequest" responses: "200": description: Assignment result content: application/json: schema: type: object properties: member_id: type: string role_id: type: string success: type: boolean "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/members/{id}/roles/{roleId}: delete: tags: - Members summary: Remove Role from Member description: | Remove an RBAC role from a member. Requires the `role:write` permission. operationId: v2.removeMemberRole parameters: - $ref: "#/components/parameters/MemberId" - $ref: "#/components/parameters/RoleId" responses: "200": description: Removal result content: application/json: schema: type: object properties: member_id: type: string role_id: type: string success: type: boolean "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/roles: get: tags: - Roles summary: List Roles description: | List all RBAC roles defined in the caller's organization, including the built-in system roles. Requires the `role:read` permission. operationId: v2.listRoles responses: "200": description: The organization's roles content: application/json: schema: $ref: "#/components/schemas/ListRolesResponse" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/RateLimited" "500": $ref: "#/components/responses/InternalError" post: tags: - Roles summary: Create Role description: | Create a custom RBAC role in the caller's organization. The new role starts with no permissions. Requires the `role:write` permission. operationId: v2.createRole requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateRoleRequest" responses: "201": description: The created role content: application/json: schema: $ref: "#/components/schemas/Role" "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/roles/{id}: patch: tags: - Roles summary: Update Role description: | Update a role's name, description, or model configuration. Fields omitted from the request keep their stored values. Requires the `role:write` permission. operationId: v2.updateRole parameters: - name: id in: path required: true schema: type: string format: uuid description: Role UUID requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateRoleRequest" responses: "200": description: The updated role content: application/json: schema: $ref: "#/components/schemas/Role" "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/api-keys: post: tags: - API Keys summary: Create API Key description: | Mint a platform API key. Every key must be role-scoped: provide either `assumedRoles` (role UUIDs the key is limited to) or set `inheritAllRoles` to `true`; a request with neither is rejected so a key cannot be over-privileged by accident. Non-admin callers may only scope keys to roles they already hold, and a key minted by another API key may only narrow — never widen — the parent key's role scope. `clientId` attaches client metadata to the key. Prefer a JSON object string: TQL row-level security exposes it to saved queries as `_tql.client_attributes_json.`, which is how one key per tenant/end-user enforces scoped data access. The bearer secret is returned once, in `key`, and cannot be retrieved again. operationId: v2.createApiKey requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateApiKeyRequest" responses: "201": description: The created key. `key` is the bearer secret, shown only once. content: application/json: schema: $ref: "#/components/schemas/CreateApiKeyResponse" "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" get: tags: - API Keys summary: List API Keys description: | List platform API keys. The bearer secret is never returned. Scope defaults to the widest the caller may see — all org keys with the `organization:read` permission, otherwise the caller's own keys. operationId: v2.listApiKeys parameters: - name: scope in: query required: false schema: type: string enum: [personal, all, service_accounts] description: "Narrow the listing. `all` and `service_accounts` require `organization:read`." - name: include_revoked in: query required: false schema: type: boolean description: Set to true to include revoked keys. - name: page_size in: query required: false schema: type: integer description: Page size for pagination. - name: page_token in: query required: false schema: type: string description: Opaque token from a previous page's `next_page_token`. responses: "200": description: The API keys visible to the caller content: application/json: schema: $ref: "#/components/schemas/ListApiKeysResponse" "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/api-keys/{id}/rotate: post: tags: - API Keys summary: Rotate API Key description: | Revoke the given key and mint a replacement with the same name, role scope, and client metadata. The new bearer secret is returned once, in `key`. Callers may always rotate their own keys; rotating another member's key requires the `organization:write` permission. operationId: v2.rotateApiKey parameters: - name: id in: path required: true description: The API key id (not the bearer secret) schema: type: string responses: "200": description: The replacement key. `key` is the bearer secret, shown only once. content: application/json: schema: $ref: "#/components/schemas/RotateApiKeyResponse" "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/api-keys/{id}: delete: tags: - API Keys summary: Revoke API Key description: | Revoke a platform API key by id. Callers may always revoke their own keys; revoking another member's key requires the `organization:write` permission. Revoking an already-revoked key returns `400`. operationId: v2.revokeApiKey parameters: - name: id in: path required: true description: The API key id (not the bearer secret) schema: type: string responses: "200": description: Revocation result content: application/json: schema: type: object properties: id: type: string 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" components: securitySchemes: bearerAuth: type: http scheme: bearer description: API key or JWT token parameters: ChatId: name: id in: path required: true schema: type: string format: uuid description: Chat ID CellId: name: cellId in: path required: true schema: type: string format: uuid description: Cell ID PlaybookId: name: id in: path required: true schema: type: string format: uuid description: Playbook ID MemberId: name: id in: path required: true schema: type: string format: uuid description: Member ID RoleId: name: roleId in: path required: true schema: type: string format: uuid description: Role ID SandboxId: name: id in: path required: true schema: type: string description: Sandbox ID SandboxFilePath: name: path in: path required: true schema: type: string description: File path within the sandbox working directory (slashes allowed). ConnectorId: name: id in: path required: true schema: type: integer format: int32 description: Connector ID ChangeId: name: id in: path required: true schema: type: string description: Change ID responses: BadRequest: description: Invalid request content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: code: invalid_request message: Invalid request body Unauthorized: description: Missing or invalid authentication content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: code: unauthenticated message: Authentication required Forbidden: description: Insufficient permissions content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: code: permission_denied message: Insufficient permissions NotFound: description: Resource not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: code: not_found message: Resource not found Conflict: description: Resource already exists content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: code: conflict message: A connector already exists for this account RateLimited: description: Rate limit exceeded content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: code: rate_limit_exceeded message: Rate limit exceeded InternalError: description: Internal server error content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: code: internal message: Internal server error schemas: LibraryChangeDiff: type: object description: One changed file in an Ontology change. properties: name: type: string description: Display name for the change. old_path: type: string new_path: type: string additions: type: integer format: int64 deletions: type: integer format: int64 is_new: type: boolean is_delete: type: boolean is_rename: type: boolean is_binary: type: boolean ChangeCapabilities: type: object description: Actions the calling member may take on a change. properties: can_approve: type: boolean can_deny: type: boolean can_restore: type: boolean caller_approved: type: boolean Change: type: object description: A reviewable Ontology change. properties: id: type: string number: type: integer format: int32 author_id: type: string author_email: type: string author_name: type: string title: type: string description: type: string status: type: string enum: - reserved - draft - open - approved - denied git_ref: type: string description: Echo this as expected_git_ref when approving/denying/restoring. revision: type: integer format: int32 ai_generated: type: boolean chat_id: type: string approval_count: type: integer format: int32 required_approvals: type: integer format: int32 created_at: type: string format: date-time updated_at: type: string format: date-time diffs: type: array items: $ref: "#/components/schemas/LibraryChangeDiff" capabilities: $ref: "#/components/schemas/ChangeCapabilities" ExpectedGitRefBody: type: object required: - expected_git_ref properties: expected_git_ref: type: string description: The change git_ref the caller reviewed; a stale value returns 409. SuccessResponse: type: object properties: success: type: boolean ErrorResponse: type: object properties: error: type: object properties: code: type: string description: Machine-readable error code enum: - invalid_request - unauthenticated - permission_denied - not_found - conflict - rate_limit_exceeded - internal - timeout - cancelled - execution_failed - no_report message: type: string description: Human-readable error message Model: type: object properties: id: type: string description: Model identifier — pass this as the `model` field on POST /v2/chats. example: gemini_3_5_flash provider: type: string description: Inference provider for the model. example: google supports_fast_mode: type: boolean description: Whether the model can run in fast mode. default: type: boolean description: Whether this is the model the org default currently resolves to. required: - id - provider - supports_fast_mode - default ListModelsResponse: type: object properties: models: type: array items: $ref: "#/components/schemas/Model" required: - models ChatRequest: 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 to run this chat on, as an `id` from `GET /v2/models` (e.g. `gemini_3_5_flash`). Omit to use the organization's default model. Only valid on new chats — supplying it together with `chat_id` returns 400, and a model the caller's org/role does not permit returns 403. example: gemini_3_5_flash tools: type: object description: Tool configuration. Enable specific tools for this chat session. properties: connector_ids: type: array items: type: integer format: int32 description: Connector IDs to query sql_enabled: type: boolean description: Enable SQL query generation python_enabled: type: boolean description: Enable Python code execution web_search_enabled: type: boolean description: Enable web search ontology_enabled: type: boolean description: Enable ontology-based queries tableau_enabled: type: boolean description: Enable Tableau integration powerbi_enabled: type: boolean description: Enable Power BI integration google_drive_enabled: type: boolean description: Enable Google Drive integration connector_ids: type: array items: type: integer format: int32 description: Connector IDs to query (shorthand for tools.connector_ids) ChatResponse: type: object properties: id: type: string format: uuid description: Message ID created_at: type: string format: date-time model: type: string description: "LLM model name (e.g. default, sonnet_4, opus_4)" response: type: string description: Markdown-formatted answer chat_id: type: string format: uuid description: Chat session ID assets: type: array items: $ref: "#/components/schemas/Asset" description: Assets produced by this response. Earlier turns' assets are available via Get Chat. GetChatResponse: type: object properties: chat: type: object properties: id: type: string format: uuid model: type: string description: "LLM model name (e.g. default, sonnet_4, opus_4)" messages: type: array items: $ref: "#/components/schemas/ChatMessage" assets: type: array items: $ref: "#/components/schemas/Asset" ChatMessage: type: object properties: role: type: string enum: - user - assistant content: type: string description: Markdown content created_at: type: string format: date-time cell_id: type: string format: uuid description: ID of the cell this message came from. Assets reference it via their message_cell_id. assets: type: array items: $ref: "#/components/schemas/Asset" description: Assets produced while generating this message. Only present on assistant messages. GetChatCellsResponse: type: object properties: chat_id: type: string format: uuid cells: type: array items: $ref: "#/components/schemas/ChatCell" has_more: type: boolean description: Whether older cells exist beyond this page ChatCell: type: object properties: id: type: string format: uuid description: Cell ID type: type: string description: "Cell type (e.g. markdown, python, sql, tableau_sql, metrics, ontology_query, javascript, bash, mcp_tool, preview, web_search)" status: type: string enum: - completed - running - pending - halted - failed - unknown created_at: type: string format: date-time error: type: string description: Execution error, when the step failed role: type: string enum: - user - assistant description: Message author (markdown cells only) content: type: string description: Markdown content (markdown cells only) code: type: string description: Executed code (python, javascript, and bash cells) query: type: string description: Executed query (sql, tableau_sql, metrics, and ontology_query cells) connector_id: type: integer format: int32 description: Connector the query ran against (sql and ontology_query cells) output: type: array items: type: string description: Captured stdout/stderr (python, javascript, and bash cells) dataframe_preview: type: string description: Markdown preview of the result set (query and python cells) execution_time_ms: type: integer format: int64 tool_name: type: string description: "MCP tool invoked, as server/tool (mcp_tool cells only)" assets: type: array items: $ref: "#/components/schemas/Asset" description: Assets this cell produced Asset: type: object properties: name: type: string description: Asset name type: type: string description: "Asset type (e.g. chart, image, table, pdf, html, text, download)" url: type: string description: Asset URL, freshly signed on every fetch content: type: string description: Inline text content, for text assets served without a URL cell_id: type: string format: uuid description: ID of the cell that produced this asset message_cell_id: type: string format: uuid description: cell_id of the assistant message this asset belongs to (Get Chat only) created_at: type: string format: date-time Connector: type: object properties: id: type: integer format: int32 description: Connector ID name: type: string description: Connector name type: type: string description: Connector type ListConnectorTypesResponse: type: object required: - types properties: types: type: array items: $ref: "#/components/schemas/ConnectorTypeInfo" ConnectorTypeInfo: type: object description: A supported connector type and the shape of its config. properties: connector_type: type: string description: "Value to set as config.connector_type (e.g. `KDB`)." config_key: type: string description: "Metadata object key to nest under config (e.g. `kdb`)." fields: type: array items: $ref: "#/components/schemas/ConnectorTypeField" ConnectorTypeField: type: object description: One configurable field of a connector type. properties: name: type: string type: type: string description: "JSON-friendly type: string, number, boolean, array, or object." confidential: type: boolean description: Write-only field (password/key/token); never returned by read endpoints. optional_on_update: type: boolean description: May be omitted on PATCH to preserve the stored value. ConnectorConfig: type: object required: - connector_type - name description: | A connector's type, display name, and type-specific connection metadata. Set exactly one metadata field matching `connector_type` (e.g. `connector_type: POSTGRES` ⇒ set `postgres`). The common database types are documented below; every supported type follows the same shape — see the proto `ConnectorConfig` for the full list (Snowflake, BigQuery, Databricks, Tableau, PowerBI, SQL Server, Trino, etc.). Confidential fields (passwords, keys, tokens) are write-only: they are never returned by read endpoints, and on update they are preserved from the stored connector when sent empty. properties: connector_type: type: string description: | Connector type enum name, e.g. `POSTGRES`, `REDSHIFT`, `MYSQL`, `SNOWFLAKE`, `BIGQUERY`, `DATABRICKS`, `TABLEAU`, `POWERBI`. example: POSTGRES name: type: string description: Human-readable connector name auth_strategy: type: string description: | Authentication strategy. Defaults to `service_role` when omitted; other values (`member_oauth`, `per_member_oauth`) are inferred from the metadata for OAuth-capable connectors. postgres: $ref: "#/components/schemas/PostgresMetadata" redshift: $ref: "#/components/schemas/RedshiftMetadata" mysql: $ref: "#/components/schemas/MySQLMetadata" snowflake: $ref: "#/components/schemas/SnowflakeMetadata" PostgresMetadata: type: object description: "Connection metadata for `connector_type: POSTGRES`." required: - host - user - password - database properties: host: type: string port: type: integer format: int32 default: 5432 user: type: string password: type: string description: Write-only. Omit on update to keep the stored value. database: type: string schemas: type: array items: type: string ssl_mode: type: boolean SnowflakeMetadata: type: object description: | Connection metadata for `connector_type: SNOWFLAKE`. Authenticate with either `username` + `password`, or `username` + `private_key` (key-pair auth, PEM-encoded PKCS#8; add `private_key_passphrase` if the key is encrypted). The OAuth and SSO fields are for org-level or per-member OAuth setups; see the Snowflake datasource docs for those flows. required: - locator - database - warehouse properties: locator: type: string description: "Account locator/identifier, e.g. `myorg-account123`." username: type: string password: type: string description: Write-only. Omit on update to keep the stored value. private_key: type: string description: Write-only. PEM-encoded private key for key-pair auth. private_key_passphrase: type: string description: Write-only. Passphrase when `private_key` is encrypted. role: type: string description: Snowflake role to assume for queries. database: type: string schema: type: string warehouse: type: string oauth_access_token: type: string description: Write-only. oauth_refresh_token: type: string description: Write-only. oauth_client_id: type: string oauth_client_secret: type: string description: Write-only. enable_sso_auth: type: boolean description: Pass the caller's IdP token directly to Snowflake External OAuth. token_exchange_endpoint: type: string description: IdP token exchange URL (RFC 8693) for per-member SSO auth. token_exchange_audience: type: string token_exchange_scope: type: string RedshiftMetadata: type: object description: "Connection metadata for `connector_type: REDSHIFT`." required: - host - database properties: host: type: string port: type: integer format: int32 default: 5439 user: type: string password: type: string description: Write-only. Omit on update to keep the stored value. database: type: string schemas: type: array items: type: string auth_type: type: string description: "`PASSWORD` (default) or `IAM_ROLE`." MySQLMetadata: type: object description: "Connection metadata for `connector_type: MYSQL`." required: - host - user - password - database properties: host: type: string port: type: integer format: int32 default: 3306 user: type: string password: type: string description: Write-only. Omit on update to keep the stored value. database: type: string CreateApiKeyRequest: type: object properties: expirySeconds: type: integer description: Optional TTL in seconds. Omit for a non-expiring key; short-lived keys are recommended for per-session embedding flows. example: 3600 assumedRoles: type: array items: type: string description: Role UUIDs to scope the key to. Callers may only specify roles they hold; API-key callers may only specify a subset of their own assumed roles. example: ["80de0196-496f-44fe-9d4c-8013b3b44082"] inheritAllRoles: type: boolean description: Set to true to inherit all of the creating member's roles. Required when assumedRoles is empty. name: type: string description: Optional display name for the key. example: acme-session-key targetMemberId: type: string description: "Mint the key for this member instead of the caller. Requires organization:write and a service-account target." clientId: type: string description: Optional client metadata stored on the key. Prefer a JSON object string (a JSON-encoded string, not a nested object); TQL row-level security reads its fields as `_tql.client_attributes_json.`. example: '{"tenant_id": "acme", "user_email": "jane@acme.example"}' fullMemberAccess: type: boolean description: When true, the key retains the target member's direct object grants alongside its assumed-role grants. API-key callers may enable this only when their own key already has full member access. PlatformApiKey: type: object properties: id: type: string description: The key id — use this for revocation, not the bearer secret example: 4f0c39a1-7c9e-4a34-9d20-6a1f6f6f2b71 member_id: type: string example: 9b2f7a64-11d0-4c1b-8f3e-2f9c5b7f6e10 name: type: string example: acme-session-key client_id: type: string example: '{"tenant_id": "acme", "user_email": "jane@acme.example"}' assumed_roles: type: array items: type: string example: ["80de0196-496f-44fe-9d4c-8013b3b44082"] status: type: string enum: [active, expired, revoked, unspecified] example: active created_at: type: string description: RFC 3339 timestamp example: "2026-07-06T19:30:00Z" expires_at: type: string description: RFC 3339 timestamp; absent for non-expiring keys example: "2026-07-06T20:30:00Z" CreateApiKeyResponse: type: object properties: key: type: string description: "The full bearer credential. Shown exactly once; store it securely. Use as `Authorization: Bearer `." example: BEARER_SECRET_SHOWN_ONCE api_key: $ref: "#/components/schemas/PlatformApiKey" ListApiKeysResponse: type: object properties: api_keys: type: array items: $ref: "#/components/schemas/PlatformApiKey" next_page_token: type: string description: Present when more pages exist; pass back as `page_token`. RotateApiKeyResponse: type: object properties: key: type: string description: "The replacement key's full bearer credential. Shown exactly once; store it securely." example: BEARER_SECRET_SHOWN_ONCE api_key: $ref: "#/components/schemas/PlatformApiKey" revoked_api_key_id: type: string description: The id of the key that was revoked by this rotation. Member: type: object properties: id: type: string example: 9b2f7a64-11d0-4c1b-8f3e-2f9c5b7f6e10 email: type: string example: jane@acme.example name: type: string example: Jane Doe roles: type: array items: type: string description: Names of the roles assigned to the member. Role ids are not included; resolve them via `GET /v2/members/{id}/roles` or `GET /v2/roles` before assigning or removing roles. example: ["admin"] is_admin: type: boolean is_service_account: type: boolean is_scim_managed: type: boolean created_at: type: string description: RFC 3339 timestamp example: "2026-07-06T19:30:00Z" ListMembersResponse: type: object properties: members: type: array items: $ref: "#/components/schemas/Member" InviteMemberRequest: type: object required: - email properties: email: type: string description: Email address to invite. example: jane@acme.example role: type: string description: >- System role name for the new member (`member` or `admin`). Defaults to `member`. Distinct from the RBAC Role entities managed via `/v2/roles` and `/v2/members/{id}/roles`. example: member Role: type: object properties: id: type: string example: 80de0196-496f-44fe-9d4c-8013b3b44082 name: type: string example: analyst description: type: string is_system: type: boolean description: True for the built-in roles (for example `admin` and `member`). is_scim_managed: type: boolean created_at: type: string description: RFC 3339 timestamp example: "2026-07-06T19:30:00Z" updated_at: type: string description: RFC 3339 timestamp example: "2026-07-06T19:30:00Z" ListRolesResponse: type: object properties: roles: type: array items: $ref: "#/components/schemas/Role" MemberRolesResponse: type: object properties: member_id: type: string roles: type: array items: $ref: "#/components/schemas/Role" AssignMemberRoleRequest: type: object required: - roleId properties: roleId: type: string description: Role UUID to assign. example: 80de0196-496f-44fe-9d4c-8013b3b44082 CreateRoleRequest: type: object required: - name properties: name: type: string description: Role name, unique within the organization. example: analyst description: type: string UpdateRoleRequest: type: object properties: name: type: string description: New role name; omit to keep the current name. description: type: string description: New description; omit to keep the current description. default_model_id: type: integer description: Default LLM model id for members of this role; 0 clears it. allowed_model_ids: type: array items: type: integer description: Restrict members of this role to these model ids. allow_model_choice: type: boolean description: Whether members of this role may pick a model per chat. clear_allowed_model_ids: type: boolean description: Set true to reset allowed_model_ids back to all models. CreateConnectorRequest: type: object required: - config properties: config: $ref: "#/components/schemas/ConnectorConfig" access: allOf: - $ref: "#/components/schemas/UpdateConnectorAccessRequest" description: | Access configuration applied atomically with creation. Grants are validated before the connector is created, so an invalid grant never leaves a partially configured connector. When omitted, the connector is created org-visible (`is_public: true`). The creating member always receives an owner grant. ConnectorAccessGrant: type: object required: - access_type description: | One access grant. Set exactly one of `member_id`, `role_id`, or `group_id`. Resolve ids via `GET /v2/members` and `GET /v2/roles`. properties: member_id: type: string example: 9b2f7a64-11d0-4c1b-8f3e-2f9c5b7f6e10 role_id: type: string example: 80de0196-496f-44fe-9d4c-8013b3b44082 group_id: type: string access_type: type: string enum: [owner, editor, viewer] UpdateConnectorAccessRequest: type: object required: - is_public properties: is_public: type: boolean description: When true, every member of the organization can use the connector. grants: type: array items: $ref: "#/components/schemas/ConnectorAccessGrant" ConnectorAccess: type: object properties: is_public: type: boolean grants: type: array items: allOf: - $ref: "#/components/schemas/ConnectorAccessGrant" - type: object properties: granted_by: type: string description: Member id that created the grant. expires_at: type: string description: RFC 3339 timestamp; absent for non-expiring grants. UpdateConnectorRequest: type: object required: - config properties: config: $ref: "#/components/schemas/ConnectorConfig" allow_sql_write_operations: type: boolean description: Allow the connector to execute write/DDL SQL. include_db_session_metadata: type: boolean description: Attach database session metadata to queries. TestConnectorRequest: type: object required: - config properties: config: $ref: "#/components/schemas/ConnectorConfig" connector_id: type: string description: | Optional. ID of an existing connector whose confidential fields should fill in any empty confidential fields in `config` before the connection is attempted. TestConnectorResponse: type: object required: - success properties: success: type: boolean description: Whether the connection succeeded. error: type: string description: Failure detail when `success` is false; empty otherwise. ListChatsResponse: type: object required: - chats - total_count properties: chats: type: array items: $ref: "#/components/schemas/ChatSummary" total_count: type: integer format: int32 ChatSummary: type: object required: - id properties: id: type: string format: uuid summary: type: string description: Chat title or summary timestamp: type: string format: date-time description: When the chat was created updated_at: type: string format: date-time description: When the chat was last updated creator_email: type: string description: Email of the user who created the chat preview: type: string description: Snippet of the first user message (up to 200 characters) source: type: string description: "Chat source (e.g. thread, playbook, slack, feed)" model: type: string description: "LLM model name (e.g. default, sonnet_4, opus_4)" is_running: type: boolean description: Whether the chat is currently processing a request ListPlaybooksResponse: type: object properties: playbooks: type: array items: $ref: "#/components/schemas/Playbook" total_count: type: integer format: int64 Playbook: type: object properties: id: type: string format: uuid name: type: string prompt: type: string status: type: string enum: - draft - deployed created_at: type: string format: date-time updated_at: type: string format: date-time connector_ids: type: array items: type: integer format: int32 description: Connector IDs attached to this playbook cron_string: type: string description: Cron schedule expression. Omitted when not set. email_addresses: type: array items: type: string description: Email addresses for report delivery. Omitted when empty. slack_channel_id: type: string description: Slack channel for report delivery. Omitted when not set. GetPlaybookResponse: type: object properties: playbook: $ref: "#/components/schemas/Playbook" reports: type: array items: $ref: "#/components/schemas/Report" total_reports_count: type: integer format: int64 UpdatePlaybookRequest: type: object properties: name: type: string description: Playbook name prompt: type: string description: Playbook prompt cron_string: type: string description: Cron schedule expression connector_ids: type: array items: type: integer format: int32 description: Connector IDs to use dataset_ids: type: array items: type: string description: Dataset IDs email_addresses: type: array items: type: string description: Email addresses for report delivery slack_channel_id: type: string description: Slack channel for report delivery tagged_slack_user_ids: type: array items: type: string description: Slack user IDs to tag in notifications selected_template_data_ids: type: array items: type: string description: Template data IDs Report: type: object properties: id: type: string format: uuid subject: type: string description: Report subject line summary: type: string description: Brief summary of the report blocks: type: array description: Structured report content blocks items: $ref: "#/components/schemas/ReportBlock" html_preview: type: string description: Pre-rendered HTML preview of the report chat_id: type: string format: uuid description: Chat session that generated this report created_at: type: string format: date-time ReportBlock: type: object required: - type properties: type: type: string enum: - hero - text - image - list - image_text - card - divider - spacer description: Block type heading: type: string description: Block heading (text, list, image_text) content: type: string description: Block body content (text, image_text) image_url: type: string description: Image URL (hero, image, image_text) image_alt: type: string description: Image alt text (hero, image, image_text) variant: type: string enum: - circular - rounded - pill - none description: Image shape variant (image, image_text) items: type: array items: type: string description: List items (list) blocks: type: array items: $ref: "#/components/schemas/ReportBlock" description: Nested blocks (card) height: type: integer description: Spacer height in pixels (spacer) RunPlaybookResponse: type: object properties: chat_id: type: string format: uuid report: $ref: "#/components/schemas/Report" assets: type: array items: $ref: "#/components/schemas/Asset" ListSandboxesResponse: type: object required: - sandboxes properties: sandboxes: type: array items: $ref: "#/components/schemas/SandboxSummary" next_cursor: type: string nullable: true description: | Opaque cursor to pass as `cursor` on the next request. Omitted when there are no more results. SandboxSummary: type: object properties: sandbox_id: type: string description: Unique sandbox identifier status: type: string enum: - running - stale - unknown - stopped description: | Sandbox state: - `running` — a live worker record was seen recently. Liveness is eventually consistent; a sandbox that died abruptly may 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}` to clear it. - `unknown` — liveness could not be determined (cache unavailable); the lease is open. - `stopped` — the sandbox has been released. Use `GET /v2/sandcastles/{id}` for the authoritative live check of a single sandbox. member_id: type: string nullable: true description: Member that started the sandbox. Null for legacy/open-lease rows. chat_id: type: string nullable: true description: Chat the sandbox is attached to. Null when not attached to a chat. started_at: type: string format: date-time description: When the sandbox was started released_at: type: string format: date-time nullable: true description: When the sandbox was released. Null while the lease is open. ListSandboxExecutionsResponse: type: object required: - executions properties: executions: type: array items: $ref: "#/components/schemas/SandboxExecution" next_cursor: type: string description: | Opaque cursor to pass as `cursor` on the next request. Omitted when there are no more results. SandboxExecution: type: object properties: id: type: string description: Unique execution identifier kind: type: string enum: - python - bash - sql - tql description: Execution type source: type: string enum: - platform_api - chat - dashboard - playbook - internal description: Product surface that triggered the execution input: type: string description: | The executed input — code (python/bash), SQL text (sql), or the library path (tql). output_preview: type: string description: | Truncated output preview (up to 16 KB; longer output ends with a `…[truncated]` marker). Does not include generated files or dataframes. error: type: string description: Error message when the execution failed; empty otherwise. duration_ms: type: integer format: int64 description: Execution duration in milliseconds created_at: type: string format: date-time description: When the execution ran SandboxStatus: type: object properties: status: type: string description: >- Point-in-time liveness for this sandbox. `stale` and `unknown` are list-only states (see SandboxSummary); this endpoint reports only `running` or `stopped`. enum: - running - stopped memory_usage: type: string description: Human-readable memory usage (only when running) dataframes: type: array description: Loaded dataframes (only when running) items: $ref: "#/components/schemas/DataframeInfo" DataframeInfo: type: object properties: name: type: string num_rows: type: integer format: int32 num_cols: type: integer format: int32 memory_usage_bytes: type: integer format: int64 ExecuteCodeResponse: type: object properties: output: type: array nullable: true items: type: string description: | Stdout/stderr output as an array of strings, one element per print call. Null when execution fails with an error. error: type: string description: | Execution error message, including the Python exception type and message (e.g. "executing Python code: ZeroDivisionError('division by zero')"). Only present when execution fails. When set, output is null. execution_time_ms: type: integer format: int64 description: Execution duration in milliseconds files: type: array items: type: object properties: name: type: string url: type: string mime_type: type: string dataframes: type: array description: | Dataframes created or modified during execution. May be empty even if code creates dataframes — use GET /v2/sandcastles/{id} for the authoritative list of loaded dataframes. items: type: object properties: name: type: string num_rows: type: integer format: int64 num_cols: type: integer format: int64 LoadConnectorDataRequest: type: object required: - connector_id properties: connector_id: type: integer format: int32 description: Connector to query query: type: string description: | Inline SQL query to execute. Mutually exclusive with `tql_path` — provide exactly one of the two (`400 invalid_request` otherwise). tql_path: type: string description: | Ontology path of a saved `.tql` file to run (must end in `.tql`). Only files already visible to the member's roles can be run — a path that is not visible returns `404 not_found`, indistinguishable from a path that does not exist. Mutually exclusive with `query` — provide exactly one of the two (`400 invalid_request` otherwise). params: type: object description: | Parameter values passed to the saved `.tql` file when it is rendered. Only used with `tql_path`. If rendering fails, the request returns `400` with the renderer's message included. max_rows: type: integer format: int64 description: | Maximum number of rows to load. Only applies to the `tql_path` branch; clamped to the range 1–2,000,000 (default 2,000,000). dataframe_name: type: string description: | Name for the resulting dataframe. Defaults to `connector_{id}` for `query`, or to the `.tql` filename stem (e.g. `revenue` for `reports/revenue.tql`) for `tql_path`. LoadConnectorDataResponse: type: object properties: preview: type: string description: | Text summary of the loaded data. Format varies by size: for smaller results, a dataframe preview string from the sandbox; for larger results (2048+ rows), a markdown table of the first 100 rows. dataframe_name: type: string num_rows: type: integer format: int64 num_cols: type: integer format: int64