{
  "openapi": "3.1.0",
  "info": {
    "title": "Speak To Your Database API",
    "version": "1.1.2",
    "description": "Ask questions about your company's data (Odoo, PostgreSQL, MySQL, and the documents and spreadsheets uploaded to your workspace) from your own code, Make or n8n, and get the answer as text, tables and figures. A documents answer lists its sources (file, page or section). Read-only: the API never changes your data.\n\n## Authentication\n\nCreate a workspace API key in **Docs → API → API key generator** (owners and admins). Send it on every request, as `x-api-key: styd_sk_…` or `Authorization: Bearer styd_sk_…`. A key belongs to one workspace and acts as its own member (role `member`): it can ask questions and read the conversations it created. The workspace's owners and admins can read those conversations too, in the STYD web app. A workspace can have 5 active keys; a key expires never, or after 30 days, 90 days or 1 year (chosen when it is created). A key on any path outside this API gets 403 `api_key_route_not_allowed` (its `message` lists the routes a key may call).\n\n## Prompts\n\nOnly **`POST /chat` costs a prompt: 1 per answer delivered** (a clarification question or a refusal is an answer too). Setup stops such as `user_not_mapped` are free, and a failed question is refunded. Every other endpoint is free. Each answer carries `promptsUsed` and `promptsLeft`; `GET /context` shows what is left.\n\n## Quick start\n\n1. `GET /connections` — pick a connection `id`. Every new workspace also has STYD's sample connections (`sample: true`, fictional data), so you can try the API before connecting your own data.\n2. `POST /chat` with `{ \"connectionId\": <id>, \"prompt\": \"How many customers do we have?\" }` — the answer comes back within `wait` seconds (default 90).\n3. If the answer is still being prepared you get **202** with a `runId`: call `GET /runs/{runId}?wait=30` until `status` is `complete` or `failed`.\n\n## Errors\n\nEvery error is JSON: `{ \"error\": \"<stable code>\", \"message\": \"<one English sentence>\", \"status\": <HTTP status> }`, sometimes with more fields (`links` on 402, `retryAfterMs` on 429). Branch on `error`; show `message`.\n\n## Limits\n\n- Up to 30 questions per minute per key, 8 waiting per key and 20 waiting per workspace (shared with MCP apps). Every 429 carries a `Retry-After` header in seconds; nothing is asked and no prompt is used.\n- 2 questions run at the same time per key, 3 per workspace and 10 across STYD; the others wait their turn, and that wait counts in `wait`.\n- A request waits at most 110 seconds.\n- A question is at most 1,000 characters.\n- Tables: 50 rows × 12 columns, 200 characters per cell; a whole answer: 32 KB (`truncated` says when something was cut).\n- 20 questions in an hour that used no prompt (failed, or stopped at a setup step) pause the key's new questions for up to an hour (429 `too_many_failed_questions`).\n- `GET /threads` returns the key's 100 newest conversations, `GET /connections` every connection: one reply each, no pagination.\n\n## Questions and answers\n\n- Each question is answered on its own: a conversation (`threadId`) groups questions, but earlier questions are not used as context. Ask complete questions.\n- Languages: STYD answers in the language of the question, or in the one the question asks for; when the language is unclear, in English. Names, codes and figures from your data are not translated. The `email` format's greeting and closing are French for a French answer, English otherwise.\n- Not available: streaming (ask, then poll), webhooks, writes (read-only), file upload (upload in the STYD web app), CSV export (formats: `json`, `text`, `email`).",
    "contact": {
      "name": "Speak To Your Database",
      "url": "https://speaktoyourdatabase.com/docs/api"
    }
  },
  "servers": [
    {
      "url": "https://speaktoyourdatabase.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKeyHeader": []
    },
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Check the key and see the workspace it belongs to. Free."
    },
    {
      "name": "Connections",
      "description": "The data sources the key can ask about. Free."
    },
    {
      "name": "Ask",
      "description": "Ask a question and read its answer. `POST /chat` costs 1 prompt per answer; reading an answer is free."
    },
    {
      "name": "Conversations",
      "description": "Conversations (threads) the key created. Free."
    }
  ],
  "paths": {
    "/context": {
      "get": {
        "operationId": "getContext",
        "tags": [
          "Account"
        ],
        "summary": "Check the API key",
        "description": "The key (id, label, dates), its workspace, its role (always `member`) and the prompts the workspace has left. Use it as a connection test. **Free:** no prompt is used.",
        "responses": {
          "200": {
            "description": "The key is valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Context"
                },
                "example": {
                  "apiKey": {
                    "id": "k_7Hq2Lm",
                    "label": "Make — weekly report",
                    "createdAt": "2026-09-02T08:15:00.000Z",
                    "expiresAt": null,
                    "lastUsedAt": "2026-10-18T07:00:12.000Z"
                  },
                  "workspace": {
                    "id": 42,
                    "name": "Acme Retail"
                  },
                  "role": "member",
                  "promptsLeft": 248
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated. No key, an unknown / revoked / expired key, or a key from before workspace keys (create a new one).",
            "headers": {
              "WWW-Authenticate": {
                "description": "Sent on an API-key refusal: `Bearer realm=\"styd\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "missing_api_key",
                            "invalid_api_key",
                            "api_key_revoked",
                            "api_key_expired",
                            "api_key_legacy_unsupported",
                            "api_key_required"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "missing_api_key": {
                    "summary": "missing_api_key",
                    "value": {
                      "error": "missing_api_key",
                      "message": "An API key is required (x-api-key or Authorization: Bearer).",
                      "status": 401
                    }
                  },
                  "invalid_api_key": {
                    "summary": "invalid_api_key",
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is invalid.",
                      "status": 401
                    }
                  },
                  "api_key_revoked": {
                    "summary": "api_key_revoked",
                    "value": {
                      "error": "api_key_revoked",
                      "message": "This API key has been revoked.",
                      "status": 401
                    }
                  },
                  "api_key_expired": {
                    "summary": "api_key_expired",
                    "value": {
                      "error": "api_key_expired",
                      "message": "This API key has expired.",
                      "status": 401
                    }
                  },
                  "api_key_legacy_unsupported": {
                    "summary": "api_key_legacy_unsupported",
                    "value": {
                      "error": "api_key_legacy_unsupported",
                      "message": "This key predates workspace keys and can no longer be used. Create a new API key in Docs → API.",
                      "status": 401
                    }
                  },
                  "api_key_required": {
                    "summary": "api_key_required",
                    "value": {
                      "error": "api_key_required",
                      "message": "This endpoint needs a workspace API key.",
                      "status": 401
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found. The key's workspace is no longer active.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "workspace_not_found"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "workspace_not_found": {
                    "summary": "workspace_not_found",
                    "value": {
                      "error": "workspace_not_found",
                      "message": "The workspace of this key is not active.",
                      "status": 404
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error. Never with internal details. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "internal_error"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "internal_error": {
                    "summary": "internal_error",
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong on our side. Retry later; if a question was being asked, check GET /api/v1/threads before asking again.",
                      "status": 500
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/workspaces": {
      "get": {
        "operationId": "listWorkspaces",
        "tags": [
          "Account"
        ],
        "summary": "List workspaces",
        "description": "The key's workspace (a key reaches exactly one), with its prompts left. **Free.**",
        "responses": {
          "200": {
            "description": "The key's workspace (an empty list when it is no longer active).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceList"
                },
                "example": {
                  "workspaces": [
                    {
                      "id": 42,
                      "name": "Acme Retail",
                      "role": "member",
                      "promptsLeft": 248
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated. No key, an unknown / revoked / expired key, or a key from before workspace keys (create a new one).",
            "headers": {
              "WWW-Authenticate": {
                "description": "Sent on an API-key refusal: `Bearer realm=\"styd\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "missing_api_key",
                            "invalid_api_key",
                            "api_key_revoked",
                            "api_key_expired",
                            "api_key_legacy_unsupported",
                            "api_key_required"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "missing_api_key": {
                    "summary": "missing_api_key",
                    "value": {
                      "error": "missing_api_key",
                      "message": "An API key is required (x-api-key or Authorization: Bearer).",
                      "status": 401
                    }
                  },
                  "invalid_api_key": {
                    "summary": "invalid_api_key",
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is invalid.",
                      "status": 401
                    }
                  },
                  "api_key_revoked": {
                    "summary": "api_key_revoked",
                    "value": {
                      "error": "api_key_revoked",
                      "message": "This API key has been revoked.",
                      "status": 401
                    }
                  },
                  "api_key_expired": {
                    "summary": "api_key_expired",
                    "value": {
                      "error": "api_key_expired",
                      "message": "This API key has expired.",
                      "status": 401
                    }
                  },
                  "api_key_legacy_unsupported": {
                    "summary": "api_key_legacy_unsupported",
                    "value": {
                      "error": "api_key_legacy_unsupported",
                      "message": "This key predates workspace keys and can no longer be used. Create a new API key in Docs → API.",
                      "status": 401
                    }
                  },
                  "api_key_required": {
                    "summary": "api_key_required",
                    "value": {
                      "error": "api_key_required",
                      "message": "This endpoint needs a workspace API key.",
                      "status": 401
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error. Never with internal details. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "internal_error"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "internal_error": {
                    "summary": "internal_error",
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong on our side. Retry later; if a question was being asked, check GET /api/v1/threads before asking again.",
                      "status": 500
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/connections": {
      "get": {
        "operationId": "listConnections",
        "tags": [
          "Connections"
        ],
        "summary": "List connections",
        "description": "The workspace's data sources: your own connections first (by id), then STYD's **sample connections** (`sample: true`, fictional data, available in every new workspace). Use an `id` as `connectionId` in `POST /chat`. Never returns a host, a user, a password or any configuration. **Free.**",
        "parameters": [
          {
            "name": "workspaceId",
            "in": "query",
            "required": false,
            "description": "Optional. The key's workspace when left out; any other workspace is refused (403).",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The connections.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectionList"
                },
                "example": {
                  "workspaceId": 42,
                  "connections": [
                    {
                      "id": 12,
                      "name": "Shop database",
                      "driver": "postgres",
                      "kind": "database",
                      "sample": false,
                      "createdAt": "2026-09-02T08:10:00.000Z"
                    },
                    {
                      "id": 15,
                      "name": "Acme Odoo",
                      "driver": "odoo_bridge",
                      "kind": "odoo",
                      "sample": false,
                      "createdAt": "2026-09-05T13:40:00.000Z"
                    },
                    {
                      "id": 3,
                      "name": "Sample: DVD rental (fictional)",
                      "driver": "postgres",
                      "kind": "database",
                      "sample": true,
                      "createdAt": "2026-06-01T00:00:00.000Z"
                    },
                    {
                      "id": 4,
                      "name": "Sample: Airline bookings (fictional)",
                      "driver": "postgres",
                      "kind": "database",
                      "sample": true,
                      "createdAt": "2026-06-01T00:00:00.000Z"
                    },
                    {
                      "id": 5,
                      "name": "Sample: Northstar Odoo (fictional)",
                      "driver": "odoo_bridge",
                      "kind": "odoo",
                      "sample": true,
                      "createdAt": "2026-06-01T00:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated. No key, an unknown / revoked / expired key, or a key from before workspace keys (create a new one).",
            "headers": {
              "WWW-Authenticate": {
                "description": "Sent on an API-key refusal: `Bearer realm=\"styd\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "missing_api_key",
                            "invalid_api_key",
                            "api_key_revoked",
                            "api_key_expired",
                            "api_key_legacy_unsupported",
                            "api_key_required"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "missing_api_key": {
                    "summary": "missing_api_key",
                    "value": {
                      "error": "missing_api_key",
                      "message": "An API key is required (x-api-key or Authorization: Bearer).",
                      "status": 401
                    }
                  },
                  "invalid_api_key": {
                    "summary": "invalid_api_key",
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is invalid.",
                      "status": 401
                    }
                  },
                  "api_key_revoked": {
                    "summary": "api_key_revoked",
                    "value": {
                      "error": "api_key_revoked",
                      "message": "This API key has been revoked.",
                      "status": 401
                    }
                  },
                  "api_key_expired": {
                    "summary": "api_key_expired",
                    "value": {
                      "error": "api_key_expired",
                      "message": "This API key has expired.",
                      "status": 401
                    }
                  },
                  "api_key_legacy_unsupported": {
                    "summary": "api_key_legacy_unsupported",
                    "value": {
                      "error": "api_key_legacy_unsupported",
                      "message": "This key predates workspace keys and can no longer be used. Create a new API key in Docs → API.",
                      "status": 401
                    }
                  },
                  "api_key_required": {
                    "summary": "api_key_required",
                    "value": {
                      "error": "api_key_required",
                      "message": "This endpoint needs a workspace API key.",
                      "status": 401
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not allowed. `workspaceId` names another workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "api_key_workspace_mismatch"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "api_key_workspace_mismatch": {
                    "summary": "api_key_workspace_mismatch",
                    "value": {
                      "error": "api_key_workspace_mismatch",
                      "message": "This API key is not authorized for the requested workspace.",
                      "status": 403
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error. Never with internal details. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "internal_error"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "internal_error": {
                    "summary": "internal_error",
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong on our side. Retry later; if a question was being asked, check GET /api/v1/threads before asking again.",
                      "status": 500
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/chat": {
      "post": {
        "operationId": "askQuestion",
        "tags": [
          "Ask"
        ],
        "summary": "Ask a question (costs 1 prompt)",
        "description": "> **Costs 1 prompt per answer.** Every \"Send\" from this page asks a real question on your workspace and uses 1 prompt, like any other client.\n\nAsk a question in plain language. Send `connectionId` (from `GET /connections`; the sample connections work in every new workspace) to start a new conversation, or `threadId` to add the question to a conversation the key created. Each question over the API is answered on its own: earlier questions of the conversation are not used as context, so ask complete questions.\n\n**Waiting.** The request waits up to `wait` seconds (default 90, at most 110) and answers **200** with the answer. If it is not ready by then — or with `wait=0` — it answers **202** with the `runId` (`status` `queued` or `running`): poll `GET /runs/{runId}?wait=30`.\n\n**Prompts.** 1 prompt per answer delivered, including a clarification question or a refusal. Free: setup stops (`refusal.code` `user_not_mapped`), a 4xx refusal (nothing was asked), and a failed run (refunded). `promptsUsed` / `promptsLeft` come with the answer (`promptsLeft` is `null` while it is pending).\n\n**Retries.** Send an `Idempotency-Key` so a retry after a timeout never asks (or charges) twice: the same key within 24 hours returns the same run.",
        "parameters": [
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "Seconds to wait for the answer before replying. Default 90; at most 110 (a larger value is capped); `0` = reply at once with 202 and the `runId`. Not a number or negative → 400 `invalid_wait`. `timeout` is accepted as an alias.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 110,
              "default": 90
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "How the answer comes back. Leave it out for the full answer object. `json`: text + structured tables, figures and sources (best for automations). `text`: plain text, markdown removed, tables as aligned lines. `email`: a ready-to-send `subject` + `body`. Formatting is free: it never costs an extra prompt.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "text",
                "email"
              ]
            },
            "example": ""
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional, recommended for automations: 1–255 printable ASCII characters (no spaces), e.g. the execution id of your scenario (`make-4f1c2a-0001`). The same key from the same API key within 24 hours returns the existing run (same `runId`, header `Idempotent-Replayed: true`): no new question, no new charge, no second conversation. The same key with another question → 409 `idempotency_key_reused`.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[!-~]+$"
            },
            "example": ""
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatRequest"
              },
              "examples": {
                "newConversation": {
                  "summary": "New conversation on a connection",
                  "value": {
                    "connectionId": 12,
                    "prompt": "Show our 3 most recent customers."
                  }
                },
                "sampleConnection": {
                  "summary": "Try it on a sample connection",
                  "value": {
                    "connectionId": 3,
                    "prompt": "How many customers are there?",
                    "format": "json"
                  }
                },
                "existingConversation": {
                  "summary": "Add a question to a conversation",
                  "value": {
                    "threadId": "cmgx2k8p40003l708h3q1z9vd",
                    "prompt": "What was our revenue in September 2026 compared with August?"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The answer (the run finished within `wait`). Its shape depends on `format`; `outcome` says whether it was answered, refused or failed.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnswerResponse"
                },
                "examples": {
                  "answer": {
                    "summary": "Default: a customers list",
                    "value": {
                      "version": 1,
                      "runId": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8",
                      "threadId": "cmgx2k8p40003l708h3q1z9vd",
                      "status": "complete",
                      "outcome": "answered",
                      "answer": {
                        "text": "Here are the 3 most recent customers.\n\n| Customer | Email | City | Signed up |\n|---|---|---|---|\n| Maria Lopez | maria.lopez@example.com | Lyon | 2026-10-14 |\n| Tom Becker | tom.becker@example.com | — | 2026-10-12 |\n| Aiko Tanaka | aiko.tanaka@example.com | Osaka | 2026-10-09 |",
                        "language": "en",
                        "blocks": [],
                        "truncated": false,
                        "totalRows": 1284
                      },
                      "evidence": {
                        "source": "database",
                        "summary": "The 3 most recently created customers.",
                        "filters": [],
                        "recordCount": 3,
                        "totalMatched": 1284,
                        "calculation": "Customers sorted by sign-up date, newest first; the first 3 are shown.",
                        "dataUsed": [
                          "customers"
                        ],
                        "assumptions": [
                          "“Signed up” is the customer's creation date."
                        ],
                        "verification": {
                          "status": "ok",
                          "reason": ""
                        }
                      },
                      "creditsUsed": 1,
                      "refusal": null,
                      "promptsUsed": 1,
                      "promptsLeft": 248
                    }
                  },
                  "jsonList": {
                    "summary": "format=json: a customers list (tables)",
                    "value": {
                      "format": "json",
                      "runId": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8",
                      "threadId": "cmgx2k8p40003l708h3q1z9vd",
                      "status": "complete",
                      "outcome": "answered",
                      "refusal": null,
                      "creditsUsed": 1,
                      "promptsUsed": 1,
                      "promptsLeft": 248,
                      "text": "Here are the 3 most recent customers.\n\n| Customer | Email | City | Signed up |\n|---|---|---|---|\n| Maria Lopez | maria.lopez@example.com | Lyon | 2026-10-14 |\n| Tom Becker | tom.becker@example.com | — | 2026-10-12 |\n| Aiko Tanaka | aiko.tanaka@example.com | Osaka | 2026-10-09 |",
                      "language": "en",
                      "tables": [
                        {
                          "columns": [
                            {
                              "key": "customer",
                              "label": "Customer"
                            },
                            {
                              "key": "email",
                              "label": "Email"
                            },
                            {
                              "key": "city",
                              "label": "City"
                            },
                            {
                              "key": "signed_up",
                              "label": "Signed up"
                            }
                          ],
                          "rows": [
                            {
                              "customer": "Maria Lopez",
                              "email": "maria.lopez@example.com",
                              "city": "Lyon",
                              "signed_up": "2026-10-14"
                            },
                            {
                              "customer": "Tom Becker",
                              "email": "tom.becker@example.com",
                              "city": null,
                              "signed_up": "2026-10-12"
                            },
                            {
                              "customer": "Aiko Tanaka",
                              "email": "aiko.tanaka@example.com",
                              "city": "Osaka",
                              "signed_up": "2026-10-09"
                            }
                          ],
                          "totalRows": 1284,
                          "truncated": true
                        }
                      ],
                      "charts": [],
                      "figures": [],
                      "sources": {
                        "source": "database",
                        "summary": "The 3 most recently created customers.",
                        "filters": [],
                        "calculation": "Customers sorted by sign-up date, newest first; the first 3 are shown.",
                        "dataUsed": [
                          "customers"
                        ],
                        "assumptions": [
                          "“Signed up” is the customer's creation date."
                        ],
                        "recordCount": 3,
                        "totalMatched": 1284,
                        "verification": {
                          "status": "ok",
                          "reason": ""
                        },
                        "items": [
                          {
                            "kind": "database",
                            "n": null,
                            "connectionId": "12",
                            "name": "Shop database",
                            "documentId": null,
                            "versionId": null,
                            "passageId": null,
                            "page": null,
                            "section": null,
                            "uploadedAt": null,
                            "url": null
                          }
                        ]
                      },
                      "truncated": false,
                      "totalRows": 1284,
                      "sample": false
                    }
                  },
                  "jsonTotal": {
                    "summary": "format=json: a computed total (figures)",
                    "value": {
                      "format": "json",
                      "runId": "0d8c51f2-7b3e-4a6c-9f10-2e4b6a8c0d12",
                      "threadId": "cmgx2m1qz0007l708k2v5r3ta",
                      "status": "complete",
                      "outcome": "answered",
                      "refusal": null,
                      "creditsUsed": 1,
                      "promptsUsed": 1,
                      "promptsLeft": 247,
                      "text": "Revenue in September 2026 was **48,210.50 EUR**, up 3,940.25 EUR (+8.9 %) on August 2026 (44,270.25 EUR).",
                      "language": "en",
                      "tables": [],
                      "charts": [],
                      "figures": [
                        {
                          "label": "Revenue (September 2026)",
                          "value": 48210.5,
                          "text": "48210.5"
                        },
                        {
                          "label": "Revenue (August 2026)",
                          "value": 44270.25,
                          "text": "44270.25"
                        },
                        {
                          "label": "Change",
                          "value": 3940.25,
                          "text": "3940.25"
                        },
                        {
                          "label": "Change (%)",
                          "value": 8.9,
                          "text": "8.9"
                        }
                      ],
                      "sources": {
                        "source": "database",
                        "summary": "Paid orders, September 2026 compared with August 2026.",
                        "filters": [],
                        "calculation": "Sum of order totals for paid orders, by order date, in the business time zone.",
                        "dataUsed": [
                          "orders"
                        ],
                        "assumptions": [
                          "Revenue = order total including tax, paid orders only."
                        ],
                        "recordCount": 1,
                        "totalMatched": null,
                        "verification": {
                          "status": "ok",
                          "reason": ""
                        },
                        "items": [
                          {
                            "kind": "database",
                            "n": null,
                            "connectionId": "12",
                            "name": "Shop database",
                            "documentId": null,
                            "versionId": null,
                            "passageId": null,
                            "page": null,
                            "section": null,
                            "uploadedAt": null,
                            "url": null
                          }
                        ]
                      },
                      "truncated": false,
                      "totalRows": null,
                      "sample": false
                    }
                  },
                  "jsonSample": {
                    "summary": "format=json: a sample connection",
                    "value": {
                      "format": "json",
                      "runId": "9a7e3c11-5d2b-4f8e-a1c6-7b0d4e2f9a33",
                      "threadId": "cmgx2p6vt000bl708x1d2f7hc",
                      "status": "complete",
                      "outcome": "answered",
                      "refusal": null,
                      "creditsUsed": 1,
                      "promptsUsed": 1,
                      "promptsLeft": 19,
                      "text": "There are **599** customers.\n\nThis answer uses fictional sample data. Connect your own Odoo or database to ask about your business: https://speaktoyourdatabase.com/connections",
                      "language": "en",
                      "tables": [],
                      "charts": [],
                      "figures": [
                        {
                          "label": "Count",
                          "value": 599,
                          "text": "599"
                        }
                      ],
                      "sources": {
                        "source": "database",
                        "summary": "All customers.",
                        "filters": [],
                        "calculation": "Count of customers.",
                        "dataUsed": [
                          "customer"
                        ],
                        "assumptions": [],
                        "recordCount": 1,
                        "totalMatched": null,
                        "verification": {
                          "status": "ok",
                          "reason": ""
                        },
                        "items": [
                          {
                            "kind": "database",
                            "n": null,
                            "connectionId": "3",
                            "name": "Sample: DVD rental (fictional)",
                            "documentId": null,
                            "versionId": null,
                            "passageId": null,
                            "page": null,
                            "section": null,
                            "uploadedAt": null,
                            "url": null
                          }
                        ]
                      },
                      "truncated": false,
                      "totalRows": null,
                      "sample": true
                    }
                  },
                  "text": {
                    "summary": "format=text",
                    "value": {
                      "format": "text",
                      "runId": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8",
                      "threadId": "cmgx2k8p40003l708h3q1z9vd",
                      "status": "complete",
                      "outcome": "answered",
                      "refusal": null,
                      "creditsUsed": 1,
                      "promptsUsed": 1,
                      "promptsLeft": 248,
                      "text": "Here are the 3 most recent customers.\n\nCustomer     Email                    City   Signed up\nMaria Lopez  maria.lopez@example.com  Lyon   2026-10-14\nTom Becker   tom.becker@example.com   —      2026-10-12\nAiko Tanaka  aiko.tanaka@example.com  Osaka  2026-10-09",
                      "language": "en"
                    }
                  },
                  "email": {
                    "summary": "format=email",
                    "value": {
                      "format": "email",
                      "runId": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8",
                      "threadId": "cmgx2k8p40003l708h3q1z9vd",
                      "status": "complete",
                      "outcome": "answered",
                      "refusal": null,
                      "creditsUsed": 1,
                      "promptsUsed": 1,
                      "promptsLeft": 248,
                      "text": "Here are the 3 most recent customers.\n\nCustomer     Email                    City   Signed up\nMaria Lopez  maria.lopez@example.com  Lyon   2026-10-14\nTom Becker   tom.becker@example.com   —      2026-10-12\nAiko Tanaka  aiko.tanaka@example.com  Osaka  2026-10-09",
                      "language": "en",
                      "subject": "Here are the 3 most recent customers.",
                      "body": "Hello,\n\nHere are the 3 most recent customers.\n\nCustomer     Email                    City   Signed up\nMaria Lopez  maria.lopez@example.com  Lyon   2026-10-14\nTom Becker   tom.becker@example.com   —      2026-10-12\nAiko Tanaka  aiko.tanaka@example.com  Osaka  2026-10-09\n\nSource: the connected database (read-only) — The 3 most recently created customers.\n\nBest regards"
                    }
                  },
                  "refused": {
                    "summary": "Refused: a setup step (free)",
                    "value": {
                      "version": 1,
                      "runId": "3c9e7a50-1f42-4d8b-b6e0-58a2c4d1f7e9",
                      "threadId": "cmgx2r0aa000fl708q7w8e1kb",
                      "status": "complete",
                      "outcome": "refused",
                      "answer": {
                        "text": "This API key is not linked to an Odoo user yet. An owner or admin can link it in Settings → Members → Odoo access.",
                        "language": "en",
                        "blocks": [],
                        "truncated": false,
                        "totalRows": null
                      },
                      "evidence": null,
                      "creditsUsed": 0,
                      "refusal": {
                        "code": "user_not_mapped"
                      },
                      "promptsUsed": 0,
                      "promptsLeft": 248
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Accepted, still being answered (`wait` ran out, or `wait=0`). Poll `GET /runs/{runId}`. Nothing is charged until the answer is delivered.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnswerResponse"
                },
                "example": {
                  "version": 1,
                  "runId": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8",
                  "threadId": "cmgx2k8p40003l708h3q1z9vd",
                  "status": "running",
                  "outcome": "pending",
                  "answer": {
                    "text": "",
                    "language": null,
                    "blocks": [],
                    "truncated": false,
                    "totalRows": null
                  },
                  "evidence": null,
                  "creditsUsed": 0,
                  "refusal": null,
                  "promptsUsed": 0,
                  "promptsLeft": null
                }
              }
            }
          },
          "400": {
            "description": "Bad request. Nothing was asked.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "missing_prompt",
                            "question_too_long",
                            "missing_threadId",
                            "invalid_workspaceId",
                            "invalid_connectionId",
                            "connection_mismatch",
                            "invalid_format",
                            "invalid_wait",
                            "invalid_idempotency_key",
                            "invalid_json",
                            "invalid_request"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "missing_prompt": {
                    "summary": "missing_prompt",
                    "value": {
                      "error": "missing_prompt",
                      "message": "A question is required (body.prompt).",
                      "status": 400
                    }
                  },
                  "question_too_long": {
                    "summary": "question_too_long",
                    "value": {
                      "error": "question_too_long",
                      "message": "The question is too long: at most 1,000 characters (maxLength).",
                      "status": 400
                    }
                  },
                  "missing_threadId": {
                    "summary": "missing_threadId",
                    "value": {
                      "error": "missing_threadId",
                      "message": "Pass threadId (an existing conversation) or connectionId (a new conversation is created).",
                      "status": 400
                    }
                  },
                  "invalid_workspaceId": {
                    "summary": "invalid_workspaceId",
                    "value": {
                      "error": "invalid_workspaceId",
                      "message": "workspaceId must be a positive integer.",
                      "status": 400
                    }
                  },
                  "invalid_connectionId": {
                    "summary": "invalid_connectionId",
                    "value": {
                      "error": "invalid_connectionId",
                      "message": "connectionId must be a positive integer.",
                      "status": 400
                    }
                  },
                  "connection_mismatch": {
                    "summary": "connection_mismatch",
                    "value": {
                      "error": "connection_mismatch",
                      "message": "body.connectionId does not match the connection of the thread.",
                      "status": 400
                    }
                  },
                  "invalid_format": {
                    "summary": "invalid_format",
                    "value": {
                      "error": "invalid_format",
                      "message": "format must be one of: json, text, email.",
                      "status": 400
                    }
                  },
                  "invalid_wait": {
                    "summary": "invalid_wait",
                    "value": {
                      "error": "invalid_wait",
                      "message": "wait must be a number of seconds (0 = reply at once; at most 110).",
                      "status": 400
                    }
                  },
                  "invalid_idempotency_key": {
                    "summary": "invalid_idempotency_key",
                    "value": {
                      "error": "invalid_idempotency_key",
                      "message": "Idempotency-Key must be 1–255 printable ASCII characters, sent once.",
                      "status": 400
                    }
                  },
                  "invalid_json": {
                    "summary": "invalid_json",
                    "value": {
                      "error": "invalid_json",
                      "message": "The request body is not valid JSON.",
                      "status": 400
                    }
                  },
                  "invalid_request": {
                    "summary": "invalid_request",
                    "value": {
                      "error": "invalid_request",
                      "message": "The request is not valid.",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated. No key, an unknown / revoked / expired key, or a key from before workspace keys (create a new one).",
            "headers": {
              "WWW-Authenticate": {
                "description": "Sent on an API-key refusal: `Bearer realm=\"styd\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "missing_api_key",
                            "invalid_api_key",
                            "api_key_revoked",
                            "api_key_expired",
                            "api_key_legacy_unsupported",
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "missing_api_key": {
                    "summary": "missing_api_key",
                    "value": {
                      "error": "missing_api_key",
                      "message": "An API key is required (x-api-key or Authorization: Bearer).",
                      "status": 401
                    }
                  },
                  "invalid_api_key": {
                    "summary": "invalid_api_key",
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is invalid.",
                      "status": 401
                    }
                  },
                  "api_key_revoked": {
                    "summary": "api_key_revoked",
                    "value": {
                      "error": "api_key_revoked",
                      "message": "This API key has been revoked.",
                      "status": 401
                    }
                  },
                  "api_key_expired": {
                    "summary": "api_key_expired",
                    "value": {
                      "error": "api_key_expired",
                      "message": "This API key has expired.",
                      "status": 401
                    }
                  },
                  "api_key_legacy_unsupported": {
                    "summary": "api_key_legacy_unsupported",
                    "value": {
                      "error": "api_key_legacy_unsupported",
                      "message": "This key predates workspace keys and can no longer be used. Create a new API key in Docs → API.",
                      "status": 401
                    }
                  },
                  "unauthorized": {
                    "summary": "unauthorized",
                    "value": {
                      "error": "unauthorized",
                      "message": "Authentication is required.",
                      "status": 401
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "No prompts left. The workspace has no prompts left; nothing was asked. `links.topUps` (Settings → Usage) and `links.pricing` say where to act.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "no_credits",
                            "free_workspace_limit",
                            "trial_ended",
                            "trial_paused",
                            "trial_limit_reached",
                            "trial_questions_limit",
                            "trial_analysis_budget"
                          ]
                        }
                      },
                      "required": [
                        "links"
                      ]
                    }
                  ]
                },
                "examples": {
                  "no_credits": {
                    "summary": "no_credits",
                    "value": {
                      "error": "no_credits",
                      "message": "The workspace has no prompts left. The workspace owner can buy top-up prompts or choose a plan to continue.",
                      "status": 402,
                      "links": {
                        "pricing": "https://speaktoyourdatabase.com/pricing",
                        "topUps": "https://speaktoyourdatabase.com/settings?tab=usage"
                      },
                      "hasPlan": true,
                      "canBuyTopups": true
                    }
                  },
                  "free_workspace_limit": {
                    "summary": "free_workspace_limit",
                    "value": {
                      "error": "free_workspace_limit",
                      "message": "The workspace has used its free AI allowance. The workspace owner can buy top-up prompts or choose a plan to continue.",
                      "status": 402,
                      "links": {
                        "pricing": "https://speaktoyourdatabase.com/pricing",
                        "topUps": "https://speaktoyourdatabase.com/settings?tab=usage"
                      }
                    }
                  },
                  "trial_ended": {
                    "summary": "trial_ended",
                    "value": {
                      "error": "trial_ended",
                      "message": "The free trial prompts are used. The workspace owner can choose a plan or buy top-up prompts to continue.",
                      "status": 402,
                      "links": {
                        "pricing": "https://speaktoyourdatabase.com/pricing",
                        "topUps": "https://speaktoyourdatabase.com/settings?tab=usage"
                      }
                    }
                  },
                  "trial_paused": {
                    "summary": "trial_paused",
                    "value": {
                      "error": "trial_paused",
                      "message": "The free trial is paused. The workspace owner can choose a plan to continue.",
                      "status": 402,
                      "links": {
                        "pricing": "https://speaktoyourdatabase.com/pricing",
                        "topUps": "https://speaktoyourdatabase.com/settings?tab=usage"
                      }
                    }
                  },
                  "trial_limit_reached": {
                    "summary": "trial_limit_reached",
                    "value": {
                      "error": "trial_limit_reached",
                      "message": "The free trial has reached its limit. The workspace owner can choose a plan to continue.",
                      "status": 402,
                      "links": {
                        "pricing": "https://speaktoyourdatabase.com/pricing",
                        "topUps": "https://speaktoyourdatabase.com/settings?tab=usage"
                      }
                    }
                  },
                  "trial_questions_limit": {
                    "summary": "trial_questions_limit",
                    "value": {
                      "error": "trial_questions_limit",
                      "message": "The free trial has reached its question limit. The workspace owner can choose a plan to continue.",
                      "status": 402,
                      "links": {
                        "pricing": "https://speaktoyourdatabase.com/pricing",
                        "topUps": "https://speaktoyourdatabase.com/settings?tab=usage"
                      }
                    }
                  },
                  "trial_analysis_budget": {
                    "summary": "trial_analysis_budget",
                    "value": {
                      "error": "trial_analysis_budget",
                      "message": "The free trial has used its analysis allowance. The workspace owner can choose a plan to continue.",
                      "status": 402,
                      "links": {
                        "pricing": "https://speaktoyourdatabase.com/pricing",
                        "topUps": "https://speaktoyourdatabase.com/settings?tab=usage"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not allowed. Nothing was asked.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "api_key_workspace_mismatch",
                            "forbidden",
                            "viewer_cannot_prompt",
                            "odoo_access_denied",
                            "thread_workspace_mismatch"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "api_key_workspace_mismatch": {
                    "summary": "api_key_workspace_mismatch",
                    "value": {
                      "error": "api_key_workspace_mismatch",
                      "message": "This API key is not authorized for the requested workspace.",
                      "status": 403
                    }
                  },
                  "forbidden": {
                    "summary": "forbidden",
                    "value": {
                      "error": "forbidden",
                      "message": "This API key cannot act on this thread.",
                      "status": 403
                    }
                  },
                  "viewer_cannot_prompt": {
                    "summary": "viewer_cannot_prompt",
                    "value": {
                      "error": "viewer_cannot_prompt",
                      "message": "A viewer can read but never ask.",
                      "status": 403
                    }
                  },
                  "odoo_access_denied": {
                    "summary": "odoo_access_denied",
                    "value": {
                      "error": "odoo_access_denied",
                      "message": "This API key has no Odoo access on this connection. Link it to an Odoo user in Settings → Members → Odoo access.",
                      "status": 403
                    }
                  },
                  "thread_workspace_mismatch": {
                    "summary": "thread_workspace_mismatch",
                    "value": {
                      "error": "thread_workspace_mismatch",
                      "message": "The thread belongs to another workspace.",
                      "status": 403
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found. Another workspace's conversation or connection is never confirmed.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "thread_not_found",
                            "connection_not_found"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "thread_not_found": {
                    "summary": "thread_not_found",
                    "value": {
                      "error": "thread_not_found",
                      "message": "No such thread.",
                      "status": 404
                    }
                  },
                  "connection_not_found": {
                    "summary": "connection_not_found",
                    "value": {
                      "error": "connection_not_found",
                      "message": "No such connection in this workspace.",
                      "status": 404
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Conflict. `idempotency_request_in_progress`: retry with the same key after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "idempotency_key_reused",
                            "idempotency_request_in_progress",
                            "connection_archived"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "idempotency_key_reused": {
                    "summary": "idempotency_key_reused",
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "This Idempotency-Key was already used with a different question or thread. Use a new key for a new question.",
                      "status": 409
                    }
                  },
                  "idempotency_request_in_progress": {
                    "summary": "idempotency_request_in_progress",
                    "value": {
                      "error": "idempotency_request_in_progress",
                      "message": "A request with this Idempotency-Key is still being processed. Retry in a moment.",
                      "status": 409
                    }
                  },
                  "connection_archived": {
                    "summary": "connection_archived",
                    "value": {
                      "error": "connection_archived",
                      "message": "This connection is archived.",
                      "status": 409
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Body too large. The body is over the size limit.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "payload_too_large"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "payload_too_large": {
                    "summary": "payload_too_large",
                    "value": {
                      "error": "payload_too_large",
                      "message": "The request body is too large.",
                      "status": 413
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "Not JSON. Send `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "unsupported_media_type"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "unsupported_media_type": {
                    "summary": "unsupported_media_type",
                    "value": {
                      "error": "unsupported_media_type",
                      "message": "Send the body as JSON (Content-Type: application/json).",
                      "status": 415
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. Nothing was asked and no prompt is used. Wait `Retry-After` seconds. `rate_limited` adds `reason`: `too_many_requests`, `user_queue_full`, `workspace_queue_full` or `busy` (STYD is busy right now: try again later).",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "rate_limited",
                            "too_many_failed_questions",
                            "sample_busy"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "rate_limited": {
                    "summary": "rate_limited",
                    "value": {
                      "error": "rate_limited",
                      "message": "Too many requests. Wait and retry (see Retry-After).",
                      "status": 429,
                      "reason": "too_many_requests",
                      "retryAfterMs": 4000
                    }
                  },
                  "too_many_failed_questions": {
                    "summary": "too_many_failed_questions",
                    "value": {
                      "error": "too_many_failed_questions",
                      "message": "Too many questions failed or were refused in the last hour. Wait (see Retry-After), then ask again.",
                      "status": 429,
                      "reason": "too_many_failed_questions",
                      "retryAfterSec": 1800
                    }
                  },
                  "sample_busy": {
                    "summary": "sample_busy",
                    "value": {
                      "error": "sample_busy",
                      "message": "The sample connection is busy. Retry in a moment (see Retry-After).",
                      "status": 429,
                      "retryAfterMs": 3000
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error. Never with internal details. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "internal_error"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "internal_error": {
                    "summary": "internal_error",
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong on our side. Retry later; if a question was being asked, check GET /api/v1/threads before asking again.",
                      "status": 500
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Unavailable. Sample connections are switched off for now.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "sample_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "sample_unavailable": {
                    "summary": "sample_unavailable",
                    "value": {
                      "error": "sample_unavailable",
                      "message": "Sample connections are not available right now.",
                      "status": 503
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/runs/{runId}": {
      "get": {
        "operationId": "getAnswer",
        "tags": [
          "Ask"
        ],
        "summary": "Get an answer",
        "description": "The answer of a question asked with `POST /chat` (by this key). Always **200** while the run exists: branch on `status` (`queued` / `running` while pending, then `complete` or `failed`) and `outcome`. With `wait`, a pending run is waited for up to that many seconds before replying; without it, the current state comes back at once. **Free:** reading an answer never uses a prompt.",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "description": "The `runId` returned by `POST /chat`. A value that is not a run id → 404 `run_not_found`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8"
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "Seconds to wait for a pending answer: default 0 (reply at once), at most 110 (a larger value is capped). Not a number or negative → 400 `invalid_wait`.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 110,
              "default": 0
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "How the answer comes back. Leave it out for the full answer object. `json`: text + structured tables, figures and sources (best for automations). `text`: plain text, markdown removed, tables as aligned lines. `email`: a ready-to-send `subject` + `body`. Formatting is free: it never costs an extra prompt.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "text",
                "email"
              ]
            },
            "example": ""
          }
        ],
        "responses": {
          "200": {
            "description": "The run: pending (`outcome: pending`), answered, refused or failed. The shape depends on `format`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnswerResponse"
                },
                "examples": {
                  "answer": {
                    "summary": "Default: answered",
                    "value": {
                      "version": 1,
                      "runId": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8",
                      "threadId": "cmgx2k8p40003l708h3q1z9vd",
                      "status": "complete",
                      "outcome": "answered",
                      "answer": {
                        "text": "Here are the 3 most recent customers.\n\n| Customer | Email | City | Signed up |\n|---|---|---|---|\n| Maria Lopez | maria.lopez@example.com | Lyon | 2026-10-14 |\n| Tom Becker | tom.becker@example.com | — | 2026-10-12 |\n| Aiko Tanaka | aiko.tanaka@example.com | Osaka | 2026-10-09 |",
                        "language": "en",
                        "blocks": [],
                        "truncated": false,
                        "totalRows": 1284
                      },
                      "evidence": {
                        "source": "database",
                        "summary": "The 3 most recently created customers.",
                        "filters": [],
                        "recordCount": 3,
                        "totalMatched": 1284,
                        "calculation": "Customers sorted by sign-up date, newest first; the first 3 are shown.",
                        "dataUsed": [
                          "customers"
                        ],
                        "assumptions": [
                          "“Signed up” is the customer's creation date."
                        ],
                        "verification": {
                          "status": "ok",
                          "reason": ""
                        }
                      },
                      "creditsUsed": 1,
                      "refusal": null,
                      "promptsUsed": 1,
                      "promptsLeft": 248
                    }
                  },
                  "pending": {
                    "summary": "Still running",
                    "value": {
                      "version": 1,
                      "runId": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8",
                      "threadId": "cmgx2k8p40003l708h3q1z9vd",
                      "status": "running",
                      "outcome": "pending",
                      "answer": {
                        "text": "",
                        "language": null,
                        "blocks": [],
                        "truncated": false,
                        "totalRows": null
                      },
                      "evidence": null,
                      "creditsUsed": 0,
                      "refusal": null,
                      "promptsUsed": 0,
                      "promptsLeft": null
                    }
                  },
                  "jsonTotal": {
                    "summary": "format=json: a computed total",
                    "value": {
                      "format": "json",
                      "runId": "0d8c51f2-7b3e-4a6c-9f10-2e4b6a8c0d12",
                      "threadId": "cmgx2m1qz0007l708k2v5r3ta",
                      "status": "complete",
                      "outcome": "answered",
                      "refusal": null,
                      "creditsUsed": 1,
                      "promptsUsed": 1,
                      "promptsLeft": 247,
                      "text": "Revenue in September 2026 was **48,210.50 EUR**, up 3,940.25 EUR (+8.9 %) on August 2026 (44,270.25 EUR).",
                      "language": "en",
                      "tables": [],
                      "charts": [],
                      "figures": [
                        {
                          "label": "Revenue (September 2026)",
                          "value": 48210.5,
                          "text": "48210.5"
                        },
                        {
                          "label": "Revenue (August 2026)",
                          "value": 44270.25,
                          "text": "44270.25"
                        },
                        {
                          "label": "Change",
                          "value": 3940.25,
                          "text": "3940.25"
                        },
                        {
                          "label": "Change (%)",
                          "value": 8.9,
                          "text": "8.9"
                        }
                      ],
                      "sources": {
                        "source": "database",
                        "summary": "Paid orders, September 2026 compared with August 2026.",
                        "filters": [],
                        "calculation": "Sum of order totals for paid orders, by order date, in the business time zone.",
                        "dataUsed": [
                          "orders"
                        ],
                        "assumptions": [
                          "Revenue = order total including tax, paid orders only."
                        ],
                        "recordCount": 1,
                        "totalMatched": null,
                        "verification": {
                          "status": "ok",
                          "reason": ""
                        },
                        "items": [
                          {
                            "kind": "database",
                            "n": null,
                            "connectionId": "12",
                            "name": "Shop database",
                            "documentId": null,
                            "versionId": null,
                            "passageId": null,
                            "page": null,
                            "section": null,
                            "uploadedAt": null,
                            "url": null
                          }
                        ]
                      },
                      "truncated": false,
                      "totalRows": null,
                      "sample": false
                    }
                  },
                  "text": {
                    "summary": "format=text",
                    "value": {
                      "format": "text",
                      "runId": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8",
                      "threadId": "cmgx2k8p40003l708h3q1z9vd",
                      "status": "complete",
                      "outcome": "answered",
                      "refusal": null,
                      "creditsUsed": 1,
                      "promptsUsed": 1,
                      "promptsLeft": 248,
                      "text": "Here are the 3 most recent customers.\n\nCustomer     Email                    City   Signed up\nMaria Lopez  maria.lopez@example.com  Lyon   2026-10-14\nTom Becker   tom.becker@example.com   —      2026-10-12\nAiko Tanaka  aiko.tanaka@example.com  Osaka  2026-10-09",
                      "language": "en"
                    }
                  },
                  "email": {
                    "summary": "format=email",
                    "value": {
                      "format": "email",
                      "runId": "6b1f3e0c-2a9d-4c57-8e14-93d0a7b5f2e8",
                      "threadId": "cmgx2k8p40003l708h3q1z9vd",
                      "status": "complete",
                      "outcome": "answered",
                      "refusal": null,
                      "creditsUsed": 1,
                      "promptsUsed": 1,
                      "promptsLeft": 248,
                      "text": "Here are the 3 most recent customers.\n\nCustomer     Email                    City   Signed up\nMaria Lopez  maria.lopez@example.com  Lyon   2026-10-14\nTom Becker   tom.becker@example.com   —      2026-10-12\nAiko Tanaka  aiko.tanaka@example.com  Osaka  2026-10-09",
                      "language": "en",
                      "subject": "Here are the 3 most recent customers.",
                      "body": "Hello,\n\nHere are the 3 most recent customers.\n\nCustomer     Email                    City   Signed up\nMaria Lopez  maria.lopez@example.com  Lyon   2026-10-14\nTom Becker   tom.becker@example.com   —      2026-10-12\nAiko Tanaka  aiko.tanaka@example.com  Osaka  2026-10-09\n\nSource: the connected database (read-only) — The 3 most recently created customers.\n\nBest regards"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request. ",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "missing_runId",
                            "invalid_wait",
                            "invalid_format"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "missing_runId": {
                    "summary": "missing_runId",
                    "value": {
                      "error": "missing_runId",
                      "message": "runId is required.",
                      "status": 400
                    }
                  },
                  "invalid_wait": {
                    "summary": "invalid_wait",
                    "value": {
                      "error": "invalid_wait",
                      "message": "wait must be a number of seconds (0 = reply at once; at most 110).",
                      "status": 400
                    }
                  },
                  "invalid_format": {
                    "summary": "invalid_format",
                    "value": {
                      "error": "invalid_format",
                      "message": "format must be one of: json, text, email.",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated. No key, an unknown / revoked / expired key, or a key from before workspace keys (create a new one).",
            "headers": {
              "WWW-Authenticate": {
                "description": "Sent on an API-key refusal: `Bearer realm=\"styd\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "missing_api_key",
                            "invalid_api_key",
                            "api_key_revoked",
                            "api_key_expired",
                            "api_key_legacy_unsupported",
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "missing_api_key": {
                    "summary": "missing_api_key",
                    "value": {
                      "error": "missing_api_key",
                      "message": "An API key is required (x-api-key or Authorization: Bearer).",
                      "status": 401
                    }
                  },
                  "invalid_api_key": {
                    "summary": "invalid_api_key",
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is invalid.",
                      "status": 401
                    }
                  },
                  "api_key_revoked": {
                    "summary": "api_key_revoked",
                    "value": {
                      "error": "api_key_revoked",
                      "message": "This API key has been revoked.",
                      "status": 401
                    }
                  },
                  "api_key_expired": {
                    "summary": "api_key_expired",
                    "value": {
                      "error": "api_key_expired",
                      "message": "This API key has expired.",
                      "status": 401
                    }
                  },
                  "api_key_legacy_unsupported": {
                    "summary": "api_key_legacy_unsupported",
                    "value": {
                      "error": "api_key_legacy_unsupported",
                      "message": "This key predates workspace keys and can no longer be used. Create a new API key in Docs → API.",
                      "status": 401
                    }
                  },
                  "unauthorized": {
                    "summary": "unauthorized",
                    "value": {
                      "error": "unauthorized",
                      "message": "Authentication is required.",
                      "status": 401
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not allowed. The run belongs to another key or workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "forbidden",
                            "api_key_workspace_mismatch"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "forbidden": {
                    "summary": "forbidden",
                    "value": {
                      "error": "forbidden",
                      "message": "This API key cannot act on this thread.",
                      "status": 403
                    }
                  },
                  "api_key_workspace_mismatch": {
                    "summary": "api_key_workspace_mismatch",
                    "value": {
                      "error": "api_key_workspace_mismatch",
                      "message": "This API key is not authorized for the requested workspace.",
                      "status": 403
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found. No run with this id (also for a value that is not a run id).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "run_not_found"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "run_not_found": {
                    "summary": "run_not_found",
                    "value": {
                      "error": "run_not_found",
                      "message": "No such run.",
                      "status": 404
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error. Never with internal details. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "internal_error"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "internal_error": {
                    "summary": "internal_error",
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong on our side. Retry later; if a question was being asked, check GET /api/v1/threads before asking again.",
                      "status": 500
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/threads": {
      "get": {
        "operationId": "listThreads",
        "tags": [
          "Conversations"
        ],
        "summary": "List conversations",
        "description": "The conversations this key created, newest first, at most 100 (no pagination). A conversation groups the questions asked on one connection. **Free.**",
        "parameters": [
          {
            "name": "workspaceId",
            "in": "query",
            "required": false,
            "description": "Optional. The key's workspace when left out; any other workspace is refused (403).",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The key's conversations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ThreadList"
                },
                "example": {
                  "threads": [
                    {
                      "id": "cmgx2k8p40003l708h3q1z9vd",
                      "title": "Show our 3 most recent customers.",
                      "connectionId": 12,
                      "createdAt": "2026-10-18T07:00:10.000Z",
                      "connectionName": "Shop database"
                    },
                    {
                      "id": "cmgx2p6vt000bl708x1d2f7hc",
                      "title": "How many customers are there?",
                      "connectionId": 3,
                      "createdAt": "2026-10-17T16:22:41.000Z",
                      "connectionName": "Sample: DVD rental (fictional)"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request. ",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "invalid_workspaceId"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "invalid_workspaceId": {
                    "summary": "invalid_workspaceId",
                    "value": {
                      "error": "invalid_workspaceId",
                      "message": "workspaceId must be a positive integer.",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated. No key, an unknown / revoked / expired key, or a key from before workspace keys (create a new one).",
            "headers": {
              "WWW-Authenticate": {
                "description": "Sent on an API-key refusal: `Bearer realm=\"styd\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "missing_api_key",
                            "invalid_api_key",
                            "api_key_revoked",
                            "api_key_expired",
                            "api_key_legacy_unsupported",
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "missing_api_key": {
                    "summary": "missing_api_key",
                    "value": {
                      "error": "missing_api_key",
                      "message": "An API key is required (x-api-key or Authorization: Bearer).",
                      "status": 401
                    }
                  },
                  "invalid_api_key": {
                    "summary": "invalid_api_key",
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is invalid.",
                      "status": 401
                    }
                  },
                  "api_key_revoked": {
                    "summary": "api_key_revoked",
                    "value": {
                      "error": "api_key_revoked",
                      "message": "This API key has been revoked.",
                      "status": 401
                    }
                  },
                  "api_key_expired": {
                    "summary": "api_key_expired",
                    "value": {
                      "error": "api_key_expired",
                      "message": "This API key has expired.",
                      "status": 401
                    }
                  },
                  "api_key_legacy_unsupported": {
                    "summary": "api_key_legacy_unsupported",
                    "value": {
                      "error": "api_key_legacy_unsupported",
                      "message": "This key predates workspace keys and can no longer be used. Create a new API key in Docs → API.",
                      "status": 401
                    }
                  },
                  "unauthorized": {
                    "summary": "unauthorized",
                    "value": {
                      "error": "unauthorized",
                      "message": "Authentication is required.",
                      "status": 401
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not allowed. Another workspace, or the key is no longer a member.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "api_key_workspace_mismatch",
                            "forbidden"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "api_key_workspace_mismatch": {
                    "summary": "api_key_workspace_mismatch",
                    "value": {
                      "error": "api_key_workspace_mismatch",
                      "message": "This API key is not authorized for the requested workspace.",
                      "status": 403
                    }
                  },
                  "forbidden": {
                    "summary": "forbidden",
                    "value": {
                      "error": "forbidden",
                      "message": "This API key cannot act on this thread.",
                      "status": 403
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error. Never with internal details. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "internal_error"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "internal_error": {
                    "summary": "internal_error",
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong on our side. Retry later; if a question was being asked, check GET /api/v1/threads before asking again.",
                      "status": 500
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createThread",
        "tags": [
          "Conversations"
        ],
        "summary": "Create a conversation",
        "description": "Optional: an empty conversation on a connection. `POST /chat` with `connectionId` creates one for you, so most clients never need this. **Free.**",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateThreadRequest"
              },
              "example": {
                "connectionId": 12,
                "title": "Weekly sales report"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new conversation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedThread"
                },
                "example": {
                  "id": "cmgx2t4hb000jl708m5n6p2wd",
                  "title": "Weekly sales report",
                  "connectionId": 12,
                  "connectionName": "Shop database",
                  "createdAt": "2026-10-19T09:30:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad request. ",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "invalid_workspaceId",
                            "invalid_connectionId",
                            "invalid_json",
                            "invalid_request"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "invalid_workspaceId": {
                    "summary": "invalid_workspaceId",
                    "value": {
                      "error": "invalid_workspaceId",
                      "message": "workspaceId must be a positive integer.",
                      "status": 400
                    }
                  },
                  "invalid_connectionId": {
                    "summary": "invalid_connectionId",
                    "value": {
                      "error": "invalid_connectionId",
                      "message": "connectionId must be a positive integer.",
                      "status": 400
                    }
                  },
                  "invalid_json": {
                    "summary": "invalid_json",
                    "value": {
                      "error": "invalid_json",
                      "message": "The request body is not valid JSON.",
                      "status": 400
                    }
                  },
                  "invalid_request": {
                    "summary": "invalid_request",
                    "value": {
                      "error": "invalid_request",
                      "message": "The request is not valid.",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated. No key, an unknown / revoked / expired key, or a key from before workspace keys (create a new one).",
            "headers": {
              "WWW-Authenticate": {
                "description": "Sent on an API-key refusal: `Bearer realm=\"styd\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "missing_api_key",
                            "invalid_api_key",
                            "api_key_revoked",
                            "api_key_expired",
                            "api_key_legacy_unsupported",
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "missing_api_key": {
                    "summary": "missing_api_key",
                    "value": {
                      "error": "missing_api_key",
                      "message": "An API key is required (x-api-key or Authorization: Bearer).",
                      "status": 401
                    }
                  },
                  "invalid_api_key": {
                    "summary": "invalid_api_key",
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is invalid.",
                      "status": 401
                    }
                  },
                  "api_key_revoked": {
                    "summary": "api_key_revoked",
                    "value": {
                      "error": "api_key_revoked",
                      "message": "This API key has been revoked.",
                      "status": 401
                    }
                  },
                  "api_key_expired": {
                    "summary": "api_key_expired",
                    "value": {
                      "error": "api_key_expired",
                      "message": "This API key has expired.",
                      "status": 401
                    }
                  },
                  "api_key_legacy_unsupported": {
                    "summary": "api_key_legacy_unsupported",
                    "value": {
                      "error": "api_key_legacy_unsupported",
                      "message": "This key predates workspace keys and can no longer be used. Create a new API key in Docs → API.",
                      "status": 401
                    }
                  },
                  "unauthorized": {
                    "summary": "unauthorized",
                    "value": {
                      "error": "unauthorized",
                      "message": "Authentication is required.",
                      "status": 401
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not allowed. ",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "api_key_workspace_mismatch",
                            "forbidden"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "api_key_workspace_mismatch": {
                    "summary": "api_key_workspace_mismatch",
                    "value": {
                      "error": "api_key_workspace_mismatch",
                      "message": "This API key is not authorized for the requested workspace.",
                      "status": 403
                    }
                  },
                  "forbidden": {
                    "summary": "forbidden",
                    "value": {
                      "error": "forbidden",
                      "message": "This API key cannot act on this thread.",
                      "status": 403
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found. ",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "connection_not_found"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "connection_not_found": {
                    "summary": "connection_not_found",
                    "value": {
                      "error": "connection_not_found",
                      "message": "No such connection in this workspace.",
                      "status": 404
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Conflict. ",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "connection_archived"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "connection_archived": {
                    "summary": "connection_archived",
                    "value": {
                      "error": "connection_archived",
                      "message": "This connection is archived.",
                      "status": 409
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Body too large. The body is over the size limit.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "payload_too_large"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "payload_too_large": {
                    "summary": "payload_too_large",
                    "value": {
                      "error": "payload_too_large",
                      "message": "The request body is too large.",
                      "status": 413
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "Not JSON. Send `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "unsupported_media_type"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "unsupported_media_type": {
                    "summary": "unsupported_media_type",
                    "value": {
                      "error": "unsupported_media_type",
                      "message": "Send the body as JSON (Content-Type: application/json).",
                      "status": 415
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error. Never with internal details. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "internal_error"
                          ]
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "internal_error": {
                    "summary": "internal_error",
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong on our side. Retry later; if a question was being asked, check GET /api/v1/threads before asking again.",
                      "status": 500
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "A workspace API key (`styd_sk_…`), created in Docs → API → API key generator."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "The same key as `Authorization: Bearer styd_sk_…`."
      }
    },
    "responses": {
      "ApiKeyRouteNotAllowed": {
        "description": "Not allowed. A key on a path or method outside this API: nothing is done. `message` lists the routes a key may call.",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "api_key_route_not_allowed"
                      ]
                    }
                  }
                }
              ]
            },
            "examples": {
              "api_key_route_not_allowed": {
                "summary": "api_key_route_not_allowed",
                "value": {
                  "error": "api_key_route_not_allowed",
                  "message": "API keys can only call these routes: POST /api/v1/chat; GET /api/v1/runs/{runId}; POST, GET /api/v1/threads; GET /api/v1/threads/{threadId}/events; GET /v1/api-keys/context; GET /api/v1/context; GET /api/v1/workspaces; GET /api/v1/connections; POST, GET, DELETE /api/v1/mcp; POST, GET, DELETE /mcp.",
                  "status": 403
                }
              }
            }
          }
        }
      }
    },
    "headers": {
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "IdempotentReplayed": {
        "description": "`true` when this reply replays an earlier request with the same `Idempotency-Key` (nothing new was asked or charged).",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every error of the public API. Branch on `error`, show `message`. Some errors add fields (listed); new fields may be added.",
        "required": [
          "error",
          "message",
          "status"
        ],
        "properties": {
          "error": {
            "type": "string",
            "pattern": "^[a-z][a-zA-Z0-9_]*$",
            "description": "A stable code."
          },
          "message": {
            "type": "string",
            "description": "One English sentence to show."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status."
          },
          "links": {
            "type": "object",
            "description": "On every 402: where the workspace owner can act.",
            "required": [
              "pricing",
              "topUps"
            ],
            "properties": {
              "pricing": {
                "type": "string",
                "format": "uri",
                "description": "Choose a plan."
              },
              "topUps": {
                "type": "string",
                "format": "uri",
                "description": "Settings → Usage: buy top-up prompts (no plan needed)."
              }
            }
          },
          "reason": {
            "type": "string",
            "description": "429 `rate_limited`: which limit (`too_many_requests`, `user_queue_full`, `workspace_queue_full`, or `busy` when STYD is very busy: try again later)."
          },
          "retryAfterMs": {
            "type": "integer",
            "description": "429: the wait in milliseconds (the `Retry-After` header has it in seconds)."
          },
          "retryAfterSec": {
            "type": "integer",
            "description": "429 `too_many_failed_questions`: the wait in seconds."
          },
          "hasPlan": {
            "type": "boolean",
            "description": "402 `no_credits`: the workspace has a plan."
          },
          "canBuyTopups": {
            "type": "boolean",
            "description": "402 `no_credits`: top-up prompts can be bought."
          },
          "messages": {
            "type": "object",
            "description": "Some refusals: the message in English and French (`{ en, fr }`).",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      },
      "ApiKeyInfo": {
        "type": "object",
        "required": [
          "id",
          "label",
          "createdAt",
          "expiresAt",
          "lastUsedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The key's id (never the key itself)."
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "description": "The name given to the key."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "`null`: never expires."
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "WorkspaceRef": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "PromptsLeft": {
        "type": [
          "integer",
          "null"
        ],
        "description": "Prompts the workspace has left (plan + top-ups, or the free trial). `null` when it cannot be read."
      },
      "Context": {
        "type": "object",
        "required": [
          "apiKey",
          "workspace",
          "role",
          "promptsLeft"
        ],
        "properties": {
          "apiKey": {
            "$ref": "#/components/schemas/ApiKeyInfo"
          },
          "workspace": {
            "$ref": "#/components/schemas/WorkspaceRef"
          },
          "role": {
            "type": "string",
            "const": "member",
            "description": "A key always acts as a workspace member."
          },
          "promptsLeft": {
            "$ref": "#/components/schemas/PromptsLeft"
          }
        },
        "additionalProperties": false
      },
      "Workspace": {
        "type": "object",
        "required": [
          "id",
          "name",
          "role",
          "promptsLeft"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "const": "member"
          },
          "promptsLeft": {
            "$ref": "#/components/schemas/PromptsLeft"
          }
        },
        "additionalProperties": false
      },
      "WorkspaceList": {
        "type": "object",
        "required": [
          "workspaces"
        ],
        "properties": {
          "workspaces": {
            "type": "array",
            "maxItems": 1,
            "items": {
              "$ref": "#/components/schemas/Workspace"
            }
          }
        },
        "additionalProperties": false
      },
      "Connection": {
        "type": "object",
        "required": [
          "id",
          "name",
          "driver",
          "kind",
          "sample",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Use it as `connectionId` in `POST /chat`."
          },
          "name": {
            "type": "string",
            "description": "The connection's name; a sample connection's display name."
          },
          "driver": {
            "type": "string",
            "description": "How STYD reads it: `postgres`, `mysql` or `odoo_bridge` (every Odoo connection, with an API key or the STYD Bridge); `uploaded_files` for the workspace's \"Uploaded files\" connection.",
            "examples": [
              "postgres",
              "mysql",
              "odoo_bridge",
              "uploaded_files"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "odoo",
              "database",
              "uploaded_files"
            ],
            "description": "Odoo, a database (SQL), or `uploaded_files` (the workspace's \"Uploaded files\" connection: its documents and spreadsheets)."
          },
          "sample": {
            "type": "boolean",
            "description": "`true` only for STYD's sample connections (fictional data)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "ConnectionList": {
        "type": "object",
        "required": [
          "workspaceId",
          "connections"
        ],
        "properties": {
          "workspaceId": {
            "type": "integer"
          },
          "connections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Connection"
            },
            "description": "Own connections first (by id), then the sample connections."
          }
        },
        "additionalProperties": false
      },
      "Thread": {
        "type": "object",
        "required": [
          "id",
          "title",
          "connectionId",
          "createdAt",
          "connectionName"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Use it as `threadId` in `POST /chat`."
          },
          "title": {
            "type": "string"
          },
          "connectionId": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "connectionName": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "ThreadList": {
        "type": "object",
        "required": [
          "threads"
        ],
        "properties": {
          "threads": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Thread"
            },
            "description": "Newest first."
          }
        },
        "additionalProperties": false
      },
      "CreatedThread": {
        "type": "object",
        "required": [
          "id",
          "title",
          "connectionId",
          "connectionName",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "connectionId": {
            "type": "integer"
          },
          "connectionName": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "CreateThreadRequest": {
        "type": "object",
        "required": [
          "connectionId"
        ],
        "properties": {
          "connectionId": {
            "type": [
              "integer",
              "string"
            ],
            "description": "A connection id from `GET /connections`."
          },
          "title": {
            "type": "string",
            "description": "Default: `API · <connection name>`."
          },
          "workspaceId": {
            "type": "integer",
            "description": "Optional: the key's workspace."
          }
        }
      },
      "ChatRequest": {
        "type": "object",
        "description": "Send `prompt` and either `connectionId` (a new conversation) or `threadId` (an existing one). Empty strings count as not given, so an automation can send every field.",
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 1000,
            "description": "The question, in plain language, at most 1,000 characters (a longer one → 400 `question_too_long`, nothing asked). STYD answers in the language of the question (or the one it asks for); when that is unclear, in English. `question` and `q` are accepted as aliases.",
            "example": "Show our 3 most recent customers."
          },
          "question": {
            "type": "string",
            "maxLength": 1000,
            "description": "Alias of `prompt`."
          },
          "connectionId": {
            "type": [
              "integer",
              "string"
            ],
            "description": "A connection id from `GET /connections` (sample connections work in every new workspace). Starts a new conversation. With `threadId`, it must be that conversation's connection."
          },
          "threadId": {
            "type": "string",
            "description": "A conversation this key created (from an earlier answer or `GET /threads`)."
          },
          "title": {
            "type": "string",
            "description": "The new conversation's title. Default: the question."
          },
          "workspaceId": {
            "type": "integer",
            "description": "Optional: the key's workspace."
          },
          "format": {
            "type": "string",
            "enum": [
              "json",
              "text",
              "email"
            ],
            "description": "Same as the `format` query parameter."
          }
        }
      },
      "RunStatus": {
        "type": "string",
        "enum": [
          "queued",
          "running",
          "complete",
          "failed"
        ],
        "description": "`queued` / `running`: poll again. `complete` / `failed`: final."
      },
      "Outcome": {
        "type": "string",
        "enum": [
          "answered",
          "refused",
          "failed",
          "pending"
        ],
        "description": "`answered`: read the text. `refused`: STYD answered but could not read the data (`refusal.code` says why; the text explains it). `failed`: no answer this time (refunded). `pending`: not ready yet."
      },
      "Refusal": {
        "type": [
          "object",
          "null"
        ],
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "e.g. `user_not_mapped` (link the key to an Odoo user in Settings → Members → Odoo access; free)."
          }
        },
        "additionalProperties": false
      },
      "Cell": {
        "type": [
          "string",
          "number",
          "boolean",
          "null"
        ],
        "description": "A cell: an empty value is `null`."
      },
      "TableBlock": {
        "type": "object",
        "required": [
          "type",
          "columns",
          "rows",
          "truncated",
          "totalRows",
          "totalColumns"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "table"
          },
          "columns": {
            "type": "array",
            "maxItems": 12,
            "items": {
              "$ref": "#/components/schemas/Column"
            }
          },
          "rows": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": {
                "$ref": "#/components/schemas/Cell"
              }
            }
          },
          "truncated": {
            "type": "boolean"
          },
          "totalRows": {
            "type": "integer"
          },
          "totalColumns": {
            "type": "integer"
          }
        },
        "additionalProperties": false
      },
      "ChartBlock": {
        "type": "object",
        "required": [
          "type",
          "labels",
          "values",
          "title"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "chart"
          },
          "labels": {
            "type": "array",
            "items": {
              "type": [
                "string",
                "number"
              ]
            }
          },
          "values": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "Column": {
        "type": "object",
        "required": [
          "key",
          "label"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "The key of the cell in each row."
          },
          "label": {
            "type": "string",
            "description": "The header to show."
          }
        },
        "additionalProperties": false
      },
      "Citation": {
        "type": "object",
        "description": "A documents answer: one source behind an inline `[n]` marker of the text.",
        "required": [
          "n",
          "fileName",
          "pageNo",
          "sectionHeading",
          "uploadedAt"
        ],
        "properties": {
          "n": {
            "type": "integer"
          },
          "fileName": {
            "type": "string"
          },
          "pageNo": {
            "type": [
              "integer",
              "null"
            ]
          },
          "sectionHeading": {
            "type": [
              "string",
              "null"
            ]
          },
          "uploadedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "recognised": {
            "type": "boolean",
            "const": true,
            "description": "Present only when the cited text was recognised from a scanned page (OCR)."
          },
          "lowConfidence": {
            "type": "boolean",
            "const": true,
            "description": "Present only with `recognised`, when that scanned page was hard to read in places."
          }
        },
        "additionalProperties": false
      },
      "GuideHint": {
        "type": "object",
        "required": [
          "kind",
          "ref",
          "url"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "const": "column"
          },
          "ref": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        },
        "additionalProperties": false
      },
      "Verification": {
        "type": [
          "object",
          "null"
        ],
        "required": [
          "status",
          "reason"
        ],
        "properties": {
          "status": {
            "type": "string",
            "description": "`ok`, or `partial` when a cut list's total is unknown."
          },
          "reason": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "SourceKind": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "odoo",
          "database",
          "documents",
          "uploaded_files",
          null
        ]
      },
      "Evidence": {
        "type": [
          "object",
          "null"
        ],
        "description": "How the answer was calculated (never the SQL or the Odoo query). `null` while pending, on a refusal, on a reply that read no data (`noDataSource: true`), or when there is nothing to show.",
        "required": [
          "source",
          "summary",
          "filters",
          "recordCount",
          "totalMatched",
          "calculation",
          "dataUsed",
          "assumptions",
          "verification"
        ],
        "properties": {
          "source": {
            "$ref": "#/components/schemas/SourceKind"
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "filters": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "recordCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "totalMatched": {
            "type": [
              "integer",
              "null"
            ]
          },
          "calculation": {
            "type": [
              "string",
              "null"
            ]
          },
          "dataUsed": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "verification": {
            "$ref": "#/components/schemas/Verification"
          },
          "guideHints": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/GuideHint"
            },
            "description": "SQL answers that rested on an unconfirmed column: where to confirm it in the AI Guide (`{ kind, ref, url }`). `null` when there is none; `[]` on a sample connection (no link)."
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            },
            "description": "Documents answers only."
          },
          "citationsNotShown": {
            "type": "integer",
            "description": "Documents answers: sources cut by the size limit."
          }
        },
        "additionalProperties": false
      },
      "Answer": {
        "type": "object",
        "title": "Answer (default)",
        "description": "The full answer object (no `format`).",
        "required": [
          "version",
          "runId",
          "threadId",
          "status",
          "outcome",
          "answer",
          "evidence",
          "creditsUsed",
          "refusal",
          "promptsUsed",
          "promptsLeft"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "const": 1
          },
          "runId": {
            "type": "string",
            "format": "uuid"
          },
          "threadId": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "outcome": {
            "$ref": "#/components/schemas/Outcome"
          },
          "answer": {
            "type": "object",
            "required": [
              "text",
              "language",
              "blocks",
              "truncated",
              "totalRows"
            ],
            "properties": {
              "text": {
                "type": "string",
                "description": "The answer in markdown (tables as markdown tables). Empty while pending."
              },
              "language": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "e.g. `en`, `fr`."
              },
              "blocks": {
                "type": "array",
                "items": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/TableBlock"
                    },
                    {
                      "$ref": "#/components/schemas/ChartBlock"
                    }
                  ]
                },
                "description": "Tables and charts, when the answer has them as separate blocks."
              },
              "truncated": {
                "type": "boolean",
                "description": "Something was cut to fit the limits (50 rows × 12 columns, 200 characters per cell, 32 KB)."
              },
              "totalRows": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The rows the answer is about (all matched rows when known)."
              }
            },
            "additionalProperties": false
          },
          "evidence": {
            "$ref": "#/components/schemas/Evidence"
          },
          "creditsUsed": {
            "type": "integer",
            "description": "Prompts charged for this question (same as `promptsUsed`)."
          },
          "refusal": {
            "$ref": "#/components/schemas/Refusal"
          },
          "sample": {
            "type": "boolean",
            "const": true,
            "description": "Present only on an answer about a sample connection (fictional data)."
          },
          "noDataSource": {
            "type": "boolean",
            "const": true,
            "description": "Present only on a reply that read no data (a question not about your data, small talk, suggested questions, \"no information\"): it has no data source, so `evidence` is `null`."
          },
          "promptsUsed": {
            "type": "integer",
            "description": "Prompts this question used: 1 when answered, 0 while pending or when free."
          },
          "promptsLeft": {
            "$ref": "#/components/schemas/PromptsLeft"
          },
          "parts": {
            "$ref": "#/components/schemas/AnswerParts"
          },
          "generalKnowledge": {
            "$ref": "#/components/schemas/GeneralKnowledge"
          },
          "generalOmitted": {
            "$ref": "#/components/schemas/GeneralOmitted"
          },
          "draft": {
            "$ref": "#/components/schemas/Draft",
            "description": "Present only when the answer holds a draft STYD wrote (an email, a message or a note): its kind and subject. Its text is the `general` part."
          }
        },
        "additionalProperties": false
      },
      "AnswerPart": {
        "type": "object",
        "description": "One part of an answer that combines sources, in reading order.",
        "required": [
          "kind",
          "source",
          "label",
          "text"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "data",
              "documents",
              "data_missing",
              "documents_missing",
              "general"
            ],
            "description": "`data` / `documents`: read from your data or documents. `data_missing` / `documents_missing`: a short line saying they do not hold this. `general`: general knowledge, not from your data."
          },
          "source": {
            "type": "string",
            "enum": [
              "database",
              "odoo",
              "uploaded_files",
              "documents",
              "general"
            ]
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "description": "The part's heading in the answer language (\"From your database\", \"General knowledge\"…); `null` for a `*_missing` line."
          },
          "text": {
            "type": "string",
            "description": "The part's text in markdown (the same text as in `answer.text` / `text`, under its label)."
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            },
            "description": "The `documents` part only: its sources."
          },
          "draft": {
            "$ref": "#/components/schemas/Draft",
            "description": "The `general` part only, when it is a draft."
          }
        },
        "additionalProperties": false
      },
      "AnswerParts": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/AnswerPart"
        },
        "minItems": 1,
        "description": "Present only when the answer combines sources (data and documents, or general knowledge after or instead of your data), a feature being turned on workspace by workspace: each part with its kind, source, label and text, the lead part first. A general-knowledge-only answer is one `general` part. Absent when the answer was cut to fit the size limit (the text stays whole)."
      },
      "GeneralKnowledge": {
        "type": "boolean",
        "const": true,
        "description": "Present only when the answer holds general knowledge: that text is not from your data."
      },
      "GeneralOmitted": {
        "type": "boolean",
        "const": true,
        "description": "Present only on an answer to a question asked through MCP (an AI app), whose general-knowledge part was left out."
      },
      "Draft": {
        "type": "object",
        "required": [
          "kind",
          "subject"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "email",
              "message",
              "note"
            ]
          },
          "subject": {
            "type": [
              "string",
              "null"
            ],
            "description": "The draft's subject, when it has one."
          }
        },
        "additionalProperties": false
      },
      "JsonTable": {
        "type": "object",
        "required": [
          "columns",
          "rows",
          "totalRows",
          "truncated"
        ],
        "properties": {
          "columns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Column"
            }
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": {
                "$ref": "#/components/schemas/Cell"
              }
            },
            "description": "One object per row, keyed by column `key`. Cells are raw values from the user's data (a spreadsheet, a database, Odoo), never escaped: render them as plain text, never as Markdown or HTML."
          },
          "totalRows": {
            "type": [
              "integer",
              "null"
            ],
            "description": "All the rows the table is about (more than `rows` when cut); `null` when unknown."
          },
          "truncated": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "Figure": {
        "type": "object",
        "description": "A value the engine computed (a total, an average, a count, a period and its change). Never a number read from the text.",
        "required": [
          "label",
          "value",
          "text"
        ],
        "properties": {
          "label": {
            "type": "string"
          },
          "value": {
            "type": "number"
          },
          "text": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "JsonChart": {
        "type": "object",
        "required": [
          "title",
          "labels",
          "values"
        ],
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "labels": {
            "type": "array",
            "items": {
              "type": [
                "string",
                "number"
              ]
            }
          },
          "values": {
            "type": "array",
            "items": {
              "type": "number"
            }
          }
        },
        "additionalProperties": false
      },
      "SourceItem": {
        "type": "object",
        "description": "One source of the answer. A database or Odoo answer: its connection (a sample by its sample name). An answer from uploaded spreadsheets: `kind` `uploaded_files` and the \"Uploaded files\" connection, with table names only. A documents answer: one item per citation. An answer that combines data and documents: its connection first, then one item per citation. Fields that do not apply are `null`.",
        "required": [
          "kind",
          "n",
          "connectionId",
          "name",
          "documentId",
          "versionId",
          "passageId",
          "page",
          "section",
          "uploadedAt",
          "url"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "database",
              "odoo",
              "document",
              "uploaded_files"
            ]
          },
          "n": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Documents: the `[n]` marker in the text."
          },
          "connectionId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The connection's id, as a string."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The connection's name (as `GET /connections` shows it) or the document's file name."
          },
          "documentId": {
            "type": [
              "string",
              "null"
            ]
          },
          "versionId": {
            "type": [
              "string",
              "null"
            ]
          },
          "passageId": {
            "type": [
              "string",
              "null"
            ]
          },
          "page": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Documents: the page."
          },
          "section": {
            "type": [
              "string",
              "null"
            ],
            "description": "Documents: the section heading."
          },
          "uploadedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "Documents: when the file was uploaded."
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "recognised": {
            "type": "boolean",
            "const": true,
            "description": "Present only when this document's cited text was recognised from a scanned page (OCR)."
          },
          "lowConfidence": {
            "type": "boolean",
            "const": true,
            "description": "Present only with `recognised`, when that scanned page was hard to read in places."
          }
        },
        "additionalProperties": false
      },
      "JsonSources": {
        "type": [
          "object",
          "null"
        ],
        "required": [
          "source",
          "summary",
          "filters",
          "calculation",
          "dataUsed",
          "assumptions",
          "recordCount",
          "totalMatched",
          "verification",
          "items"
        ],
        "properties": {
          "source": {
            "$ref": "#/components/schemas/SourceKind"
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "filters": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "calculation": {
            "type": [
              "string",
              "null"
            ]
          },
          "dataUsed": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "recordCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "totalMatched": {
            "type": [
              "integer",
              "null"
            ]
          },
          "verification": {
            "$ref": "#/components/schemas/Verification"
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            }
          },
          "citationsNotShown": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SourceItem"
            }
          }
        },
        "additionalProperties": false
      },
      "JsonAnswer": {
        "type": "object",
        "title": "Answer (format=json)",
        "description": "A structured view for automations: tables as rows keyed by column, figures the engine computed, the sources. Tables and figures come from the query result, never from the text.",
        "required": [
          "format",
          "runId",
          "threadId",
          "status",
          "outcome",
          "refusal",
          "creditsUsed",
          "promptsUsed",
          "promptsLeft",
          "text",
          "language",
          "tables",
          "charts",
          "figures",
          "sources",
          "truncated",
          "totalRows",
          "sample"
        ],
        "properties": {
          "format": {
            "type": "string",
            "const": "json"
          },
          "runId": {
            "type": "string",
            "format": "uuid"
          },
          "threadId": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "outcome": {
            "$ref": "#/components/schemas/Outcome"
          },
          "refusal": {
            "$ref": "#/components/schemas/Refusal"
          },
          "creditsUsed": {
            "type": "integer"
          },
          "promptsUsed": {
            "type": "integer"
          },
          "promptsLeft": {
            "$ref": "#/components/schemas/PromptsLeft"
          },
          "text": {
            "type": "string",
            "description": "The answer in markdown."
          },
          "language": {
            "type": [
              "string",
              "null"
            ]
          },
          "tables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JsonTable"
            }
          },
          "charts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JsonChart"
            }
          },
          "figures": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Figure"
            }
          },
          "sources": {
            "$ref": "#/components/schemas/JsonSources"
          },
          "truncated": {
            "type": "boolean"
          },
          "totalRows": {
            "type": [
              "integer",
              "null"
            ]
          },
          "sample": {
            "type": "boolean",
            "description": "`true` when the answer is about a sample connection (fictional data)."
          },
          "noDataSource": {
            "type": "boolean",
            "const": true,
            "description": "Present only on a reply that read no data (a question not about your data, small talk, suggested questions, \"no information\"): it has no data source, so `sources` is `null`."
          },
          "parts": {
            "$ref": "#/components/schemas/AnswerParts"
          },
          "generalKnowledge": {
            "$ref": "#/components/schemas/GeneralKnowledge"
          },
          "generalOmitted": {
            "$ref": "#/components/schemas/GeneralOmitted"
          }
        },
        "additionalProperties": false
      },
      "TextAnswer": {
        "type": "object",
        "title": "Answer (format=text)",
        "required": [
          "format",
          "runId",
          "threadId",
          "status",
          "outcome",
          "refusal",
          "creditsUsed",
          "promptsUsed",
          "promptsLeft",
          "text",
          "language"
        ],
        "properties": {
          "format": {
            "type": "string",
            "const": "text"
          },
          "runId": {
            "type": "string",
            "format": "uuid"
          },
          "threadId": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "outcome": {
            "$ref": "#/components/schemas/Outcome"
          },
          "refusal": {
            "$ref": "#/components/schemas/Refusal"
          },
          "creditsUsed": {
            "type": "integer"
          },
          "promptsUsed": {
            "type": "integer"
          },
          "promptsLeft": {
            "$ref": "#/components/schemas/PromptsLeft"
          },
          "text": {
            "type": "string",
            "description": "Plain text: markdown removed, tables as aligned lines, sources listed for documents. An answer that combines sources has a heading per part (\"From your database:\", \"General knowledge:\")."
          },
          "language": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "EmailAnswer": {
        "type": "object",
        "title": "Answer (format=email)",
        "required": [
          "format",
          "runId",
          "threadId",
          "status",
          "outcome",
          "refusal",
          "creditsUsed",
          "promptsUsed",
          "promptsLeft",
          "subject",
          "body",
          "text",
          "language"
        ],
        "properties": {
          "format": {
            "type": "string",
            "const": "email"
          },
          "runId": {
            "type": "string",
            "format": "uuid"
          },
          "threadId": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "outcome": {
            "$ref": "#/components/schemas/Outcome"
          },
          "refusal": {
            "$ref": "#/components/schemas/Refusal"
          },
          "creditsUsed": {
            "type": "integer"
          },
          "promptsUsed": {
            "type": "integer"
          },
          "promptsLeft": {
            "$ref": "#/components/schemas/PromptsLeft"
          },
          "subject": {
            "type": "string",
            "description": "The answer's first sentence (or a draft's own subject), at most 80 characters."
          },
          "body": {
            "type": "string",
            "description": "A short email: a neutral opening, the plain answer, its source, a neutral closing (French for a French answer, English otherwise). Nothing that is not in the answer. When the answer is a draft STYD wrote, the body is the draft alone."
          },
          "text": {
            "type": "string"
          },
          "language": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "AnswerResponse": {
        "description": "The answer, in the shape `format` asked for.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/Answer"
          },
          {
            "$ref": "#/components/schemas/JsonAnswer"
          },
          {
            "$ref": "#/components/schemas/TextAnswer"
          },
          {
            "$ref": "#/components/schemas/EmailAnswer"
          }
        ]
      }
    }
  }
}