{
  "openapi": "3.0.3",
  "info": {
    "title": "Webenta Index API",
    "description": "REST API for Webenta Index — structured data storage for AI agents.",
    "version": "1.0.0",
    "contact": {
      "url": "https://webenta.sk"
    }
  },
  "servers": [
    {
      "url": "https://index.webenta.sk",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Health"
    },
    {
      "name": "Authentication"
    },
    {
      "name": "Account"
    },
    {
      "name": "Plans"
    },
    {
      "name": "Projects"
    },
    {
      "name": "Tables"
    },
    {
      "name": "Columns"
    },
    {
      "name": "Rows"
    },
    {
      "name": "Querying"
    },
    {
      "name": "Events"
    },
    {
      "name": "Dashboard"
    },
    {
      "name": "API Keys"
    },
    {
      "name": "Instructions"
    },
    {
      "name": "Data Transfer"
    },
    {
      "name": "Billing"
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key (Authorization: Bearer …)"
      }
    }
  },
  "paths": {
    "/api/v1/health": {
      "get": {
        "summary": "Health check",
        "description": "Returns a simple ok response. Use this to verify the API is reachable before making authenticated calls.",
        "tags": [
          "Health"
        ],
        "operationId": "health",
        "security": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/auth/otp/request": {
      "post": {
        "summary": "Request OTP",
        "description": "Sends a one-time password to the given email. Rate-limited to 10 requests per hour per IP and 3 per 15 minutes per email.",
        "tags": [
          "Authentication"
        ],
        "operationId": "otp-request",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Email address to send the OTP to."
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/auth/otp/verify": {
      "post": {
        "summary": "Verify OTP",
        "description": "Verifies an OTP code. On success, sets a session cookie and returns ok.",
        "tags": [
          "Authentication"
        ],
        "operationId": "otp-verify",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Email the OTP was sent to."
                  },
                  "code": {
                    "type": "string",
                    "description": "The 6-digit OTP code."
                  }
                },
                "required": [
                  "email",
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/auth/logout": {
      "post": {
        "summary": "Logout",
        "description": "Invalidates the current session and clears the session cookie.",
        "tags": [
          "Authentication"
        ],
        "operationId": "logout",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/auth/google/start": {
      "get": {
        "summary": "Start Google OAuth",
        "description": "Initiates Google OAuth flow. Redirects the browser to Google's authorization page. Use from a browser, not from server-side code.",
        "tags": [
          "Authentication"
        ],
        "operationId": "google-start",
        "security": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/auth/google/callback": {
      "get": {
        "summary": "Google OAuth callback",
        "description": "Completes Google OAuth. Called automatically by Google after the user authorizes. On success, creates a session and redirects to /dashboard.",
        "tags": [
          "Authentication"
        ],
        "operationId": "google-callback",
        "security": [],
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Authorization code from Google."
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "CSRF state token set during the start step."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "summary": "Get current user",
        "description": "Returns the authenticated user's profile, active plan details, and current storage usage.",
        "tags": [
          "Account"
        ],
        "operationId": "me",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/whoami-key": {
      "get": {
        "summary": "Introspect API key",
        "description": "Returns the scope and project binding of the API key used in the request. Works only with an API key — session cookies are rejected.",
        "tags": [
          "Account"
        ],
        "operationId": "whoami-key",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/usage": {
      "get": {
        "summary": "API usage stats",
        "description": "Returns API call counts broken down by success/failure over a time window.",
        "tags": [
          "Account"
        ],
        "operationId": "usage",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "60m",
                "24h",
                "7d",
                "30d"
              ],
              "default": "24h"
            },
            "description": "Time window."
          },
          {
            "name": "projectId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter to a specific project."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/me/redeem-code": {
      "post": {
        "summary": "Redeem promo code",
        "description": "Redeems a promotional code that upgrades the account plan. Requires a web session.",
        "tags": [
          "Account"
        ],
        "operationId": "redeem-code",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "The promotion code to redeem."
                  }
                },
                "required": [
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/plans": {
      "get": {
        "summary": "List plans",
        "description": "Returns all available subscription plans with their resource limits.",
        "tags": [
          "Plans"
        ],
        "operationId": "list-plans",
        "security": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "summary": "List projects",
        "description": "Returns all non-archived projects owned by the authenticated user.",
        "tags": [
          "Projects"
        ],
        "operationId": "list-projects",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "Create project",
        "description": "Creates a new project with an isolated Postgres database. Requires a web session — project-scoped API keys cannot create projects.",
        "tags": [
          "Projects"
        ],
        "operationId": "create-project",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Display name for the project (1–64 characters)."
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}": {
      "get": {
        "summary": "Get project",
        "description": "Returns project metadata including the internal database name and current storage usage in bytes.",
        "tags": [
          "Projects"
        ],
        "operationId": "get-project",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "Delete project",
        "description": "Permanently deletes a project and its Postgres database. This action cannot be undone. Requires a web session — API keys cannot delete projects.",
        "tags": [
          "Projects"
        ],
        "operationId": "delete-project",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/info": {
      "get": {
        "summary": "Get project info",
        "description": "Returns extended project info including custom instructions, plan limits, and storage usage. The MCP server calls this endpoint on connect.",
        "tags": [
          "Projects"
        ],
        "operationId": "get-project-info",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/tables": {
      "get": {
        "summary": "List tables",
        "description": "Returns all tables in the project with their full column schemas.",
        "tags": [
          "Tables"
        ],
        "operationId": "list-tables",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "Create table",
        "description": "Creates a new table with the given schema. An auto-increment `id` column is always added automatically — do not include it in columns.",
        "tags": [
          "Tables"
        ],
        "operationId": "create-table",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Table name. snake_case recommended."
                  },
                  "columns": {
                    "type": "string",
                    "description": "Array of column definitions. Each has: `name` (string), `type` (text|number|boolean|date|enum|json), `nullable` (boolean, default true), `enumValues` (string[], for enum type only)."
                  }
                },
                "required": [
                  "name",
                  "columns"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/tables/{table}": {
      "delete": {
        "summary": "Drop table",
        "description": "Permanently drops a table and all its rows. This action cannot be undone.",
        "tags": [
          "Tables"
        ],
        "operationId": "delete-table",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/tables/{table}/columns": {
      "post": {
        "summary": "Add column",
        "description": "Adds a new column to an existing table. Existing rows will have NULL for the new column regardless of the nullable setting.",
        "tags": [
          "Columns"
        ],
        "operationId": "add-column",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Column name."
                  },
                  "type": {
                    "type": "string",
                    "description": "Column type.",
                    "enum": [
                      "text",
                      "number",
                      "boolean",
                      "date",
                      "enum",
                      "json"
                    ]
                  },
                  "nullable": {
                    "type": "boolean",
                    "description": "Whether the column allows NULL."
                  },
                  "enumValues": {
                    "type": "string",
                    "description": "Allowed values — required when type is \"enum\"."
                  }
                },
                "required": [
                  "name",
                  "type"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/tables/{table}/columns/{col}": {
      "patch": {
        "summary": "Rename column",
        "description": "Renames a column. The rename is propagated to dashboard widget configs that reference this column.",
        "tags": [
          "Columns"
        ],
        "operationId": "rename-column",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          },
          {
            "name": "col",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current column name."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rename": {
                    "type": "string",
                    "description": "New column name."
                  }
                },
                "required": [
                  "rename"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "Drop column",
        "description": "Drops a column from a table. All data in that column is permanently deleted.",
        "tags": [
          "Columns"
        ],
        "operationId": "delete-column",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          },
          {
            "name": "col",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Column name to drop."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/tables/{table}/rows": {
      "get": {
        "summary": "List rows",
        "description": "Returns rows from a table with optional filtering, ordering, and pagination.",
        "tags": [
          "Rows"
        ],
        "operationId": "list-rows",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          },
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "URL-encoded JSON filter. Supported operators per column: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`, `in`. Example: `{\"exercise\":{\"eq\":\"squat\"}}`."
          },
          {
            "name": "orderBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "URL-encoded JSON sort. Example: `{\"logged_at\":\"desc\"}`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": "100"
            },
            "description": "Max rows to return."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": "0"
            },
            "description": "Rows to skip (for pagination)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "Insert rows",
        "description": "Inserts one or more rows into a table. Pass either a `rows` array for batch insert, or a single `values` object for one row.",
        "tags": [
          "Rows"
        ],
        "operationId": "insert-rows",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rows": {
                    "type": "string",
                    "description": "Array of row objects (batch insert). Use either `rows` or `values`, not both."
                  },
                  "values": {
                    "type": "string",
                    "description": "Single row object. Use either `rows` or `values`, not both."
                  }
                },
                "required": []
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/tables/{table}/rows/{rowId}": {
      "patch": {
        "summary": "Update row",
        "description": "Partially updates a row by ID. Only the fields present in `values` are changed; all other fields remain as-is.",
        "tags": [
          "Rows"
        ],
        "operationId": "update-row",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          },
          {
            "name": "rowId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Row ID (the auto-increment `id` field)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "values": {
                    "type": "string",
                    "description": "Key-value pairs of fields to update."
                  }
                },
                "required": [
                  "values"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "Delete row",
        "description": "Permanently deletes a single row by its ID.",
        "tags": [
          "Rows"
        ],
        "operationId": "delete-row",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          },
          {
            "name": "rowId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Row ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/query/aggregate": {
      "post": {
        "summary": "Aggregate query",
        "description": "Runs a GROUP BY aggregate query — count, sum, average, min, or max — with optional grouping and filtering. This is what dashboard widgets use under the hood.",
        "tags": [
          "Querying"
        ],
        "operationId": "aggregate",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "table": {
                    "type": "string",
                    "description": "Table to query."
                  },
                  "metrics": {
                    "type": "string",
                    "description": "Aggregate metrics. Each has `type` (count|sum|avg|min|max) and `column` (the column to aggregate)."
                  },
                  "groupBy": {
                    "type": "string",
                    "description": "Columns to group results by."
                  },
                  "filter": {
                    "type": "string",
                    "description": "Filter object (same format as the row list filter query param)."
                  }
                },
                "required": [
                  "table",
                  "metrics"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/events": {
      "get": {
        "summary": "Event stream (SSE)",
        "description": "Opens a Server-Sent Events stream that delivers real-time notifications whenever data in the project changes — rows inserted, tables created, etc. The dashboard uses this to refresh without polling.",
        "tags": [
          "Events"
        ],
        "operationId": "events-stream",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/dashboard": {
      "get": {
        "summary": "Get dashboard",
        "description": "Returns the saved dashboard layout and variables for a project. Auto-creates an empty dashboard if none has been saved yet.",
        "tags": [
          "Dashboard"
        ],
        "operationId": "get-dashboard",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "put": {
        "summary": "Save dashboard",
        "description": "Saves the dashboard layout and optional variables. The entire layout is replaced on each save.",
        "tags": [
          "Dashboard"
        ],
        "operationId": "save-dashboard",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "layout": {
                    "type": "string",
                    "description": "Array of widget definitions. Each widget has type, title, config, and grid position (x, y, w, h)."
                  },
                  "variables": {
                    "type": "string",
                    "description": "Dashboard-level variable definitions for filtering widgets."
                  }
                },
                "required": [
                  "layout"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/instructions": {
      "get": {
        "summary": "Get instructions",
        "description": "Returns the custom agent instructions for a project. These are injected into the MCP system prompt on every connection.",
        "tags": [
          "Instructions"
        ],
        "operationId": "get-instructions",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "put": {
        "summary": "Update instructions",
        "description": "Replaces the custom agent instructions for a project (max 64 KB). Pass an empty string to clear. Requires a web session.",
        "tags": [
          "Instructions"
        ],
        "operationId": "update-instructions",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "instructions": {
                    "type": "string",
                    "description": "New instructions text. Empty string clears existing instructions."
                  }
                },
                "required": []
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/apikeys": {
      "get": {
        "summary": "List global API keys",
        "description": "Returns all API keys owned by the authenticated user (both global and project-scoped). Requires a web session — API keys cannot list other keys.",
        "tags": [
          "API Keys"
        ],
        "operationId": "list-global-apikeys",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "Create global API key",
        "description": "Creates a new global API key that works across all projects. The key value is only returned once — store it immediately.",
        "tags": [
          "API Keys"
        ],
        "operationId": "create-global-apikey",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Human-readable label for this key."
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/apikeys/{keyId}": {
      "delete": {
        "summary": "Delete API key",
        "description": "Permanently revokes an API key. Any agent using this key will immediately lose access.",
        "tags": [
          "API Keys"
        ],
        "operationId": "delete-global-apikey",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Key ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/apikeys": {
      "get": {
        "summary": "List project API keys",
        "description": "Returns all API keys scoped to a specific project.",
        "tags": [
          "API Keys"
        ],
        "operationId": "list-project-apikeys",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "Create project API key",
        "description": "Creates a new API key scoped to a single project. The key value is only returned once.",
        "tags": [
          "API Keys"
        ],
        "operationId": "create-project-apikey",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Human-readable label for this key."
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/apikeys/{keyId}": {
      "delete": {
        "summary": "Delete project API key",
        "description": "Permanently revokes a project-scoped API key.",
        "tags": [
          "API Keys"
        ],
        "operationId": "delete-project-apikey",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Key ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/transfer": {
      "get": {
        "summary": "Export project (ZIP)",
        "description": "Exports all tables as a ZIP archive containing one JSON file per table (up to 50 000 rows each).",
        "tags": [
          "Data Transfer"
        ],
        "operationId": "export-project",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "Import project data",
        "description": "Bulk-imports data into one or more tables with wipe or append mode and configurable conflict handling.",
        "tags": [
          "Data Transfer"
        ],
        "operationId": "import-project",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "description": "Import mode: `wipe` truncates the table first; `append` keeps existing rows.",
                    "enum": [
                      "wipe",
                      "append"
                    ]
                  },
                  "onConflict": {
                    "type": "string",
                    "description": "ID conflict strategy: `replace` overwrites, `skip` ignores, `newid` assigns a new ID.",
                    "enum": [
                      "replace",
                      "skip",
                      "newid"
                    ]
                  },
                  "tables": {
                    "type": "string",
                    "description": "Array of table import specs. Each has `name`, `rows`, `columns`, and optionally `createTable` / `createTableColumns` to create the table if it does not exist."
                  }
                },
                "required": [
                  "mode",
                  "onConflict",
                  "tables"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/projects/{id}/tables/{table}/transfer": {
      "get": {
        "summary": "Export table",
        "description": "Exports a single table as a JSON or CSV file (up to 50 000 rows).",
        "tags": [
          "Data Transfer"
        ],
        "operationId": "export-table",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            },
            "description": "Export format."
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "Import into table",
        "description": "Imports rows into a specific table with wipe or append mode and conflict handling.",
        "tags": [
          "Data Transfer"
        ],
        "operationId": "import-table",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID."
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rows": {
                    "type": "string",
                    "description": "Rows to import."
                  },
                  "columns": {
                    "type": "string",
                    "description": "Column names present in each row object."
                  },
                  "mode": {
                    "type": "string",
                    "description": "Import mode.",
                    "enum": [
                      "wipe",
                      "append"
                    ]
                  },
                  "onConflict": {
                    "type": "string",
                    "description": "Conflict strategy.",
                    "enum": [
                      "replace",
                      "skip",
                      "newid"
                    ]
                  }
                },
                "required": [
                  "rows",
                  "columns",
                  "mode",
                  "onConflict"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/billing/checkout": {
      "post": {
        "summary": "Create checkout session",
        "description": "Creates a Stripe checkout session for a paid plan upgrade. Returns a URL to redirect the user to.",
        "tags": [
          "Billing"
        ],
        "operationId": "billing-checkout",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "planSlug": {
                    "type": "string",
                    "description": "The plan ID to subscribe to (e.g. \"pro\")."
                  }
                },
                "required": [
                  "planSlug"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/billing/portal": {
      "post": {
        "summary": "Open billing portal",
        "description": "Creates a Stripe billing portal session. Returns a URL for the user to manage their subscription, invoices, and payment method.",
        "tags": [
          "Billing"
        ],
        "operationId": "billing-portal",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    }
  }
}