{
  "openapi": "3.1.0",
  "info": {
    "title": "ONE API",
    "version": "0.10.0",
    "description": "HTTP API for approved ONE agent and integration access."
  },
  "servers": [
    {
      "url": "https://one.geotech.one/api",
      "description": "ONE production API"
    },
    {
      "url": "https://geotech-crm.web.app/api",
      "description": "Permanent Firebase Hosting fallback"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "geoCrmKey": []
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Health check",
        "security": [],
        "responses": {
          "200": {
            "description": "API is reachable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/info": {
      "get": {
        "summary": "Get app info",
        "description": "Return canonical ONE app URLs, API/docs links, and short answer hints for agents.",
        "security": [],
        "responses": {
          "200": {
            "description": "Canonical ONE app metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppInfoResponse"
                }
              }
            }
          }
        }
      }
    },
    "/machines/client/latest": {
      "get": {
        "summary": "Get latest OneClient release",
        "description": "Return the signed release manifest for the requested OneClient channel and platform.",
        "security": [],
        "parameters": [
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "windows"
            }
          },
          {
            "name": "arch",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "amd64"
            }
          },
          {
            "name": "channel",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "stable"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Signed OneClient release manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "channel": {
                      "type": "string"
                    },
                    "platform": {
                      "type": "string"
                    },
                    "arch": {
                      "type": "string"
                    },
                    "release": {
                      "type": "object",
                      "properties": {
                        "version": {
                          "type": "string"
                        },
                        "channel": {
                          "type": "string"
                        },
                        "platform": {
                          "type": "string"
                        },
                        "arch": {
                          "type": "string"
                        },
                        "releasedAt": {
                          "type": "string"
                        },
                        "agent": {
                          "type": "object"
                        },
                        "updater": {
                          "type": "object"
                        },
                        "signature": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/machines/client/releases/{version}/{platform}/{arch}/{fileName}": {
      "get": {
        "summary": "Download OneClient release artifact",
        "description": "Stream a signed-release artifact from Firebase Storage through the ONE API.",
        "security": [],
        "parameters": [
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "platform",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "arch",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fileName",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "one-client-agent.exe",
                "one-client-updater.exe",
                "one-client-tray.exe",
                "bootstrap-windows-service.ps1",
                "one-client-agent",
                "one-client-updater",
                "bootstrap-macos-service.sh"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Windows executable, macOS executable, or bootstrap script artifact.",
            "content": {
              "application/vnd.microsoft.portable-executable": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/machines/{machineId}/color": {
      "patch": {
        "summary": "Set a shared remote machine color",
        "description": "Save the machine and calendar color as a global Remote Machines setting. Requires Firebase admin or owner access.",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "name": "machineId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "color"
                ],
                "properties": {
                  "color": {
                    "type": "string",
                    "pattern": "^#[0-9a-fA-F]{6}$",
                    "example": "#18b67c"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Shared machine color was updated."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/machines/{machineId}": {
      "delete": {
        "summary": "Remove a remote machine",
        "description": "Archive a machine from the Remote Machines scheduler and clear its live status, sessions, CPU metrics, events, and bookings. The installed client is not uninstalled.",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "name": "machineId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "aliases": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Machine was archived from the scheduler."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "Show API key metadata",
        "responses": {
          "200": {
            "description": "Authenticated API key metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiKey": {
                      "$ref": "#/components/schemas/ApiKey"
                    }
                  },
                  "required": [
                    "apiKey"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/logs": {
      "get": {
        "summary": "List recent diagnostic logs",
        "description": "Read the last seven days of ONE logs, filtered by time range, level, category, connector, source, or text query.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional ISO timestamp or epoch milliseconds. Values older than seven days are clamped."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional ISO timestamp or epoch milliseconds. Defaults to now."
          },
          {
            "name": "level",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated levels: debug, info, warn, error."
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated categories: app, llm, connector, system."
          },
          {
            "name": "connector",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated connector filters, such as slack, gmail, openrouter, codex, api, or web."
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated source filters, such as web, bridge, mcp, or geoCrmApi."
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive text search across messages and details."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent diagnostic logs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LogListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "summary": "Write a diagnostic log",
        "description": "Write a short-lived app, LLM, connector, or system diagnostic log entry. Requires write scope.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WriteLogRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stored diagnostic log.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WriteLogResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/complex-decisions": {
      "post": {
        "summary": "Upload an owner-only Complex Decision",
        "description": "Create or replace a rich HTML decision brief. This endpoint requires a personal owner API key with write scope. The server always stores the report as owner-only, removes active HTML content, and clears legacy report grants.",
        "operationId": "uploadComplexDecision",
        "security": [
          {
            "geoCrmKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["slug", "navigationLabel", "title", "category", "summary", "html"],
                "properties": {
                  "slug": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", "maxLength": 120 },
                  "navigationLabel": { "type": "string", "maxLength": 160 },
                  "title": { "type": "string", "maxLength": 300 },
                  "category": { "type": "string", "maxLength": 120 },
                  "status": { "type": "string", "enum": ["draft", "recommendation_ready", "decided", "archived"], "default": "draft" },
                  "recommendation": { "type": "string", "enum": ["proceed", "proceed_conditionally", "defer", "do_not_proceed"], "default": "defer" },
                  "summary": { "type": "string", "maxLength": 800 },
                  "publishedAt": { "type": "string", "format": "date-time" },
                  "sourceFileName": { "type": "string", "maxLength": 240 },
                  "codexThreadUrl": { "type": "string", "pattern": "^codex://threads/[A-Za-z0-9-]+$", "maxLength": 240 },
                  "html": { "type": "string", "maxLength": 900000 },
                  "position": { "type": "number", "default": 850 }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Existing owner-only decision replaced." },
          "201": { "description": "Owner-only decision created." },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/branding": {
      "get": {
        "summary": "Get GeoTech brand standards",
        "description": "Return the authoritative approved logos, exact colour values, typography, patterns, hosted assets, the Cover, Getting Started, Foundations, and Tokens pages, and all 89 design variables for presentations, Complex Decisions, websites, product design, and other visual work.",
        "security": [],
        "responses": {
          "200": {
            "description": "Current GeoTech brand standards.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandStandardsResponse"
                }
              }
            }
          }
        }
      }
    },
    "/boards": {
      "get": {
        "summary": "List boards",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional board-name filter."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Board summaries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "boards": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BoardSummary"
                      }
                    }
                  },
                  "required": [
                    "boards"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/boards/{boardId}": {
      "get": {
        "summary": "Get board metadata",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          }
        ],
        "responses": {
          "200": {
            "description": "One board summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "board": {
                      "$ref": "#/components/schemas/BoardSummary"
                    }
                  },
                  "required": [
                    "board"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "summary": "Remove a board from the active workspace",
        "description": "Requires an API key with the write scope. The board name must be supplied as confirmation. Protected core boards cannot be removed. Removed boards are archived under an internal deleted-boards path for recovery.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteBoardRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Board removed from active workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteBoardResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/boards/{boardId}/columns": {
      "post": {
        "summary": "Create or return a board column",
        "description": "Requires an API key with the write scope. If a matching column title or ID already exists, the existing column is returned.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnsureColumnRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing column returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnsureColumnResponse"
                }
              }
            }
          },
          "201": {
            "description": "Column created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnsureColumnResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/boards/{boardId}/columns/{columnId}": {
      "delete": {
        "summary": "Archive a board column",
        "description": "Requires an API key with the write scope. Archives a non-name, non-subitems column so it no longer appears in the active board table. Existing item cell data is preserved for recovery.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ColumnId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ArchiveColumnRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Column archived.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArchiveColumnResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/boards/{boardId}/items": {
      "get": {
        "summary": "List board items",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "name": "query",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional text search across item names and visible fields."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Board metadata and matching item summaries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "board": {
                      "$ref": "#/components/schemas/BoardSummary"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ItemSummary"
                      }
                    }
                  },
                  "required": [
                    "board",
                    "items"
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a board item",
        "description": "Requires an API key with the write scope. Creates a new item on one board using existing column IDs or titles for field values.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateItemRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created item detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/ItemDetail"
                    }
                  },
                  "required": [
                    "item"
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/boards/{boardId}/items/move": {
      "post": {
        "summary": "Move filtered board items",
        "description": "Requires an API key with the write scope. Moves matching items on one board to another group, such as moving Production rows with Status=Done to Archived. Use dryRun to preview matched items without writing.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoveItemsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Move result or dry-run preview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoveItemsResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/boards/{boardId}/items/duplicates": {
      "get": {
        "summary": "Find duplicate board records",
        "description": "Find duplicate candidate groups on one board by exact or similar normalized item names.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "name_threshold",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0.5,
              "maximum": 1,
              "default": 0.86
            },
            "description": "Similarity threshold for fuzzy name matches."
          }
        ],
        "responses": {
          "200": {
            "description": "Duplicate board record candidate groups.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicateCandidateResponse"
                }
              }
            }
          }
        }
      }
    },
    "/boards/{boardId}/items/duplicates/merge": {
      "post": {
        "summary": "Merge duplicate board records",
        "description": "Requires an API key with the write scope. Merges duplicate items on one board into a primary item, preserving candidate field values.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BoardItemDuplicateMergeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Merge result or dry-run preview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BoardItemDuplicateMergeResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/boards/{boardId}/items/{itemId}": {
      "get": {
        "summary": "Get one item",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ItemId"
          }
        ],
        "responses": {
          "200": {
            "description": "One item with updates, assets, and subitems.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/ItemDetail"
                    }
                  },
                  "required": [
                    "item"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "summary": "Update item name or fields",
        "description": "Requires an API key with the write scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ItemId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateItemRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated item summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/ItemSummary"
                    }
                  },
                  "required": [
                    "item"
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "delete": {
        "summary": "Soft-delete an item",
        "description": "Requires an API key with the write scope. Marks the item state as deleted and writes an audit update.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ItemId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteItemRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Soft-delete result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    },
                    "item": {
                      "$ref": "#/components/schemas/ItemDetail"
                    },
                    "update": {
                      "$ref": "#/components/schemas/Update"
                    }
                  },
                  "required": [
                    "deleted",
                    "item",
                    "update"
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/boards/{boardId}/items/{itemId}/files": {
      "post": {
        "summary": "Set a file or image field",
        "description": "Requires an API key with the write scope. Creates the file column when it does not exist.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ItemId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetFileFieldRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "File field updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetFileFieldResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/boards/{boardId}/items/{itemId}/updates": {
      "post": {
        "summary": "Add an item update note",
        "description": "Requires an API key with the write scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ItemId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created update note.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "update": {
                      "$ref": "#/components/schemas/Update"
                    }
                  },
                  "required": [
                    "update"
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/contacts": {
      "post": {
        "summary": "Create or update a contact and link a company",
        "description": "Requires an API key with the write scope. Finds an existing Contacts record by email/name or creates one, then links it to an existing or newly created Companies record when company details are provided.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing contact returned after verification or update.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpsertContactResponse"
                }
              }
            }
          },
          "201": {
            "description": "Contact created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpsertContactResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/contacts/duplicates": {
      "get": {
        "summary": "Find duplicate contact candidates",
        "description": "Find duplicate candidate groups on the Contacts board by shared email, shared phone, exact name, or similar name.",
        "parameters": [
          {
            "name": "board_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional Contacts board ID. Defaults to the board named Contacts."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "name_threshold",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0.5,
              "maximum": 1,
              "default": 0.86
            },
            "description": "Similarity threshold for fuzzy name matches."
          }
        ],
        "responses": {
          "200": {
            "description": "Duplicate contact candidate groups.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "board": {
                      "$ref": "#/components/schemas/BoardSummary"
                    },
                    "candidateGroups": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ContactDuplicateGroup"
                      }
                    },
                    "totalCandidateGroups": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "board",
                    "candidateGroups",
                    "totalCandidateGroups"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/contacts/duplicates/merge": {
      "post": {
        "summary": "Merge duplicate contacts",
        "description": "Requires an API key with the write scope. Merges duplicate Contacts items into a primary item, preserving secondary emails and candidate field values.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactDuplicateMergeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Merge result or dry-run preview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactDuplicateMergeResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/task-automation/assign": {
      "post": {
        "summary": "Assign or unassign local Codex",
        "description": "Firebase-authenticated browser endpoint. Assigning Codex or explicitly mentioning @Codex assigns the task to the Codex agent for manual MCP pickup; unassigning removes the Codex assignee without removing human assignees. No worker job is created.",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskAutomationAssignmentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The task was already assigned to Codex (idempotent repeat)."
          },
          "201": {
            "description": "Codex assignment was applied or cancelled."
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/task-projects/boards": {
      "get": {
        "summary": "List project-task boards",
        "description": "Requires tasks:read, read, or write scope. Returns only least-privilege board summaries allowed for project-task automation; task-only keys cannot use generic board-read routes.",
        "parameters": [
          { "name": "query", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200 } }
        ],
        "responses": {
          "200": { "description": "Allowed project-task board summaries." },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/task-projects/boards/{boardId}/projects": {
      "get": {
        "summary": "List task project summaries",
        "description": "Requires tasks:read, read, or write scope. Returns only safe project identity and lifecycle fields; updates, assets, fields, and nested tasks are omitted.",
        "parameters": [
          { "$ref": "#/components/parameters/BoardId" },
          { "name": "query", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200 } }
        ],
        "responses": {
          "200": { "description": "Safe project summaries." },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/task-projects/boards/{boardId}/projects/{projectId}": {
      "get": {
        "summary": "Get a task project summary",
        "description": "Requires tasks:read, read, or write scope. Returns one safe project summary without generic item updates, assets, fields, or nested tasks.",
        "parameters": [
          { "$ref": "#/components/parameters/BoardId" },
          { "$ref": "#/components/parameters/ProjectId" }
        ],
        "responses": {
          "200": { "description": "Safe project summary." },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks": {
      "get": {
        "summary": "List project tasks",
        "description": "Requires tasks:read, read, or write scope. Supports query text and exact slackUrl matching.",
        "parameters": [
          { "$ref": "#/components/parameters/BoardId" },
          { "$ref": "#/components/parameters/ProjectId" },
          { "name": "query", "in": "query", "schema": { "type": "string" } },
          { "name": "slackUrl", "in": "query", "schema": { "type": "string", "format": "uri" } }
        ],
        "responses": {
          "200": { "description": "Project task list." },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "post": {
        "summary": "Create project tasks",
        "description": "Requires tasks:write or write scope. Creates one or more Backlog tasks with fields, Slack sources, and one-level subtasks.",
        "parameters": [
          { "$ref": "#/components/parameters/BoardId" },
          { "$ref": "#/components/parameters/ProjectId" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["tasks"], "properties": { "tasks": { "type": "array", "minItems": 1, "maxItems": 12, "items": { "type": "object" } } } } } }
        },
        "responses": {
          "201": { "description": "Project tasks created." },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks/{taskId}": {
      "get": {
        "summary": "Get developer task context",
        "description": "Requires tasks:read, read, or write scope. Returns nested task fields, the complete comment timeline, attachment metadata, image-only metadata for visual inspection, and a revision for concurrency-safe writes.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ProjectId"
          },
          {
            "$ref": "#/components/parameters/TaskId"
          }
        ],
        "responses": {
          "200": {
            "description": "Complete developer task context.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskContext"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "summary": "Update project task",
        "description": "Requires tasks:write or write scope. Uses expectedRevision or expected_revision and bot-managed field provenance to preserve human edits; details and comments are append-only. Status changes are rejected here and must use the review-state endpoint.",
        "parameters": [
          { "$ref": "#/components/parameters/BoardId" },
          { "$ref": "#/components/parameters/ProjectId" },
          { "$ref": "#/components/parameters/TaskId" }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } },
        "responses": {
          "200": { "description": "Task updated with any preserved-field conflicts." },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "description": "Task revision conflict." }
        }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks/{taskId}/subtasks/{subtaskId}": {
      "parameters": [
        { "$ref": "#/components/parameters/BoardId" },
        { "$ref": "#/components/parameters/ProjectId" },
        { "$ref": "#/components/parameters/TaskId" },
        { "$ref": "#/components/parameters/SubtaskId" }
      ],
      "get": {
        "summary": "Get project subtask context",
        "description": "Requires tasks:read, read, or write scope. Returns full one-level subtask fields, comments, images, Slack sources, and revision.",
        "responses": { "200": { "description": "Subtask context." }, "404": { "$ref": "#/components/responses/NotFound" } }
      },
      "patch": {
        "summary": "Update project subtask",
        "description": "Requires tasks:write or write scope. Applies the same field, comment, source, and revision semantics as a top-level task. Status changes and nested subtasks are rejected.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } },
        "responses": { "200": { "description": "Subtask updated." }, "409": { "description": "Subtask revision conflict." } }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks/{taskId}/subtasks/{subtaskId}/comments": {
      "parameters": [
        { "$ref": "#/components/parameters/BoardId" },
        { "$ref": "#/components/parameters/ProjectId" },
        { "$ref": "#/components/parameters/TaskId" },
        { "$ref": "#/components/parameters/SubtaskId" }
      ],
      "post": {
        "summary": "Comment on a project subtask",
        "description": "Requires tasks:comment or broader write scope. Returns only the appended comment, duplicate flag, and new revision; it does not expose subtask fields or prior comments.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"] } } } },
        "responses": { "200": { "description": "Idempotent duplicate comment." }, "201": { "description": "Comment appended." }, "409": { "description": "Subtask revision conflict." } }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks/{taskId}/subtasks/{subtaskId}/images/{assetId}": {
      "get": {
        "summary": "Fetch developer subtask image",
        "description": "Requires tasks:read, read, or write scope. Proxies a Firebase subtask-description or comment image without exposing its stored download URL.",
        "parameters": [
          { "$ref": "#/components/parameters/BoardId" },
          { "$ref": "#/components/parameters/ProjectId" },
          { "$ref": "#/components/parameters/TaskId" },
          { "$ref": "#/components/parameters/SubtaskId" },
          { "$ref": "#/components/parameters/AssetId" }
        ],
        "responses": {
          "200": {
            "description": "Image bytes.",
            "content": {
              "image/*": {
                "schema": { "type": "string", "format": "binary" }
              }
            }
          },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/slack/task-automation/channels/{teamId}/{channelId}": {
      "parameters": [
        { "$ref": "#/components/parameters/TeamId" },
        { "$ref": "#/components/parameters/ChannelId" }
      ],
      "get": {
        "summary": "Get Slack channel project mapping",
        "description": "Requires tasks:read, read, or write scope.",
        "responses": { "200": { "description": "Mapping or null." } }
      },
      "post": {
        "summary": "Save Slack channel project mapping",
        "description": "Requires tasks:write or write scope. Validates that the board is a project task board and that the project exists before saving the lookup.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["boardId", "projectId"] } } } },
        "responses": { "200": { "description": "Mapping saved." } }
      }
    },
    "/slack/task-automation/proposals": {
      "get": {
        "summary": "List Slack task proposals",
        "description": "Requires tasks:read, read, or write scope. Filters by teamId, channelId, threadTs, proposalMessageTs, or status.",
        "responses": { "200": { "description": "Versioned proposal list." } }
      },
      "post": {
        "summary": "Create Slack task proposal",
        "description": "Requires tasks:write or write scope and a project task board target. Stores only the summary, task operations, participant IDs, hashes, and Slack source metadata; raw transcripts and attachment bytes are not accepted or persisted.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } },
        "responses": { "201": { "description": "Pending 24-hour proposal created." } }
      }
    },
    "/slack/task-automation/proposals/{proposalId}": {
      "parameters": [
        { "$ref": "#/components/parameters/ProposalId" }
      ],
      "get": {
        "summary": "Get Slack task proposal",
        "description": "Requires tasks:read, read, or write scope.",
        "responses": { "200": { "description": "Proposal." }, "404": { "$ref": "#/components/responses/NotFound" } }
      },
      "patch": {
        "summary": "Refresh Slack task proposal",
        "description": "Requires tasks:write or write scope. Replaces a pending preview with a new version and 24-hour expiry using an exact positive-integer expectedVersion. The project target cannot change. The Slack bot binds a posted message without incrementing the proposal version before advertising approval controls; approval remains gated until that exact visible version is acknowledged. Set supersede to true when a deleted target makes the pending preview permanently obsolete.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["expectedVersion"], "properties": { "expectedVersion": { "type": "integer", "minimum": 1 }, "supersede": { "type": "boolean" }, "supersededReason": { "type": "string", "maxLength": 500 } } } } } },
        "responses": { "200": { "description": "Refreshed proposal." }, "400": { "$ref": "#/components/responses/BadRequest" }, "409": { "description": "Proposal version conflict or already approved." } }
      }
    },
    "/slack/task-automation/proposals/{proposalId}/approve": {
      "parameters": [
        { "$ref": "#/components/parameters/ProposalId" }
      ],
      "post": {
        "summary": "Approve Slack task proposal",
        "description": "Requires tasks:write or write scope. Validates the exact version, live thread snapshot hash, 24-hour expiry, and participant approver before idempotently applying task operations.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["approverSlackUserId", "expectedVersion", "sourceSnapshotHash"], "properties": { "approverSlackUserId": { "type": "string" }, "expectedVersion": { "type": "integer", "minimum": 1 }, "sourceSnapshotHash": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Proposal applied or prior idempotent result returned." }, "400": { "$ref": "#/components/responses/BadRequest" }, "403": { "$ref": "#/components/responses/Forbidden" }, "409": { "description": "Stale proposal or changed thread." }, "410": { "description": "Proposal expired." } }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks/{taskId}/images/{assetId}": {
      "get": {
        "summary": "Fetch developer task image",
        "description": "Requires tasks:read, read, or write scope. Proxies a Firebase task-description or comment image without exposing its stored download URL.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ProjectId"
          },
          {
            "$ref": "#/components/parameters/TaskId"
          },
          {
            "$ref": "#/components/parameters/AssetId"
          }
        ],
        "responses": {
          "200": {
            "description": "Image bytes.",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks/{taskId}/comments": {
      "post": {
        "summary": "Add developer task comment",
        "description": "Requires tasks:comment or write scope. Adds an idempotent Codex-attributed nested task comment and rejects stale revisions.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ProjectId"
          },
          {
            "$ref": "#/components/parameters/TaskId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskCommentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Task comment created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskWriteResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Task revision conflict."
          }
        }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks/{taskId}/review-state": {
      "patch": {
        "summary": "Set developer task review state",
        "description": "Requires tasks:review or write scope. Codex may set only In Progress or Review; a human must mark Done.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ProjectId"
          },
          {
            "$ref": "#/components/parameters/TaskId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskReviewStateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task review state updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskWriteResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Task revision conflict."
          }
        }
      }
    },
    "/search": {
      "get": {
        "summary": "Search items across boards",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Text to search across item names, board names, groups, and visible fields. Search ignores common instruction words and supports token/prefix matches such as chris for Christopher."
          },
          {
            "name": "board_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional board ID or board name to limit search. Omit it to search all boards."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching item summaries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ItemSummary"
                      }
                    }
                  },
                  "required": [
                    "items"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/backups": {
      "post": {
        "summary": "Queue a protected ONE backup",
        "description": "Returns immediately with a background job. Requires backups:write or Firebase owner authentication; API keys may create manual backups only.",
        "security": [
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "source": {
                    "type": "string",
                    "enum": [
                      "manual",
                      "automatic"
                    ],
                    "default": "manual"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "compact",
                      "full"
                    ],
                    "default": "compact"
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 240
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Backup job queued."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/backups/jobs/{jobId}": {
      "get": {
        "summary": "Get a backup job",
        "description": "Returns queued, running, completed, or failed status and the saved backup summary when complete. Backup API keys may read only jobs they created.",
        "security": [
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current backup job state."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Backup job not found."
          }
        }
      }
    },
    "/documents/sign-requests": {
      "get": {
        "summary": "List e-signature agreements",
        "description": "Requires documents:read or a Firebase user with Documents access.",
        "responses": {
          "200": {
            "description": "Private agreement records without signing tokens.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureRequestListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "summary": "Create an agreement draft",
        "description": "Upload a raw PDF, PNG, JPEG, or WebP body (20 MB maximum). Headers X-File-Name, X-Document-Name, and X-Signers are required; X-Subject, X-Message, and JSON X-Settings are optional. Requires documents:write.",
        "parameters": [
          {
            "in": "header",
            "name": "X-File-Name",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "X-Document-Name",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "X-Signers",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "X-Subject",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "X-Message",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "X-Settings",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/pdf": {
              "schema": {
                "type": "string",
                "contentEncoding": "binary"
              }
            },
            "image/*": {
              "schema": {
                "type": "string",
                "contentEncoding": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Draft created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureRequestResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/documents/sign-requests/from-template": {
      "post": {
        "summary": "Create a draft from a template",
        "description": "Fill the template recipient roles in order. Requires documents:write.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "templateId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "signers": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/SignatureRecipient"
                    }
                  },
                  "subject": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "settings": {
                    "$ref": "#/components/schemas/SignatureSettings"
                  }
                },
                "required": [
                  "templateId",
                  "name",
                  "signers"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Draft created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureRequestResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/documents/sign-templates": {
      "get": {
        "summary": "List reusable agreement templates",
        "description": "Requires documents:read.",
        "responses": {
          "200": {
            "description": "Templates with documents, roles, fields, messages, and options."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/documents/sign-templates/{templateId}": {
      "delete": {
        "summary": "Delete a signature template",
        "description": "Permanently removes the selected reusable template and its private document copies. Requires documents:write.",
        "parameters": [
          {
            "in": "path",
            "name": "templateId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Template deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/documents/sign-requests/{requestId}": {
      "parameters": [
        {
          "in": "path",
          "name": "requestId",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "Get an agreement",
        "description": "Requires documents:read.",
        "responses": {
          "200": {
            "description": "Agreement details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureRequestResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "summary": "Update a draft",
        "description": "Update name, recipients/actions, routing, subject/message, reminders, and expiration. Requires documents:write.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "signers": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/SignatureRecipient"
                    }
                  },
                  "subject": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "settings": {
                    "$ref": "#/components/schemas/SignatureSettings"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureRequestResponse"
                }
              }
            }
          },
          "409": {
            "description": "Request is not a draft."
          }
        }
      },
      "delete": {
        "summary": "Delete an agreement",
        "description": "Delete a draft or terminal agreement and its private files. Requires documents:write.",
        "responses": {
          "200": {
            "description": "Agreement deleted."
          },
          "409": {
            "description": "Live agreements must be voided first."
          }
        }
      }
    },
    "/documents/sign-requests/{requestId}/documents": {
      "post": {
        "summary": "Add a document to a draft",
        "description": "Upload a raw supported document body; 10 documents maximum. Requires documents:write.",
        "parameters": [
          {
            "in": "path",
            "name": "requestId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "X-File-Name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/pdf": {
              "schema": {
                "type": "string",
                "contentEncoding": "binary"
              }
            },
            "image/*": {
              "schema": {
                "type": "string",
                "contentEncoding": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureRequestResponse"
                }
              }
            }
          },
          "409": {
            "description": "Request is not a draft."
          }
        }
      }
    },
    "/documents/sign-requests/{requestId}/documents/{documentId}": {
      "delete": {
        "summary": "Remove a document from a draft",
        "parameters": [
          {
            "in": "path",
            "name": "requestId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "documentId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Document and its fields removed."
          },
          "409": {
            "description": "The only document cannot be removed."
          }
        }
      }
    },
    "/documents/sign-requests/{requestId}/prepare": {
      "put": {
        "summary": "Place recipient fields",
        "description": "Supports signature, initials, text, name, email, title, date_signed, and checkbox fields. Requires documents:write.",
        "parameters": [
          {
            "in": "path",
            "name": "requestId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/SignatureField"
                    }
                  }
                },
                "required": [
                  "fields"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fields saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureRequestResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/documents/sign-requests/{requestId}/send": {
      "post": {
        "summary": "Send a prepared agreement",
        "description": "Creates private links for the active parallel/sequential routing group. Requires documents:send.",
        "parameters": [
          {
            "in": "path",
            "name": "requestId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Agreement sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureRequestResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "description": "Request is not a draft."
          }
        }
      }
    },
    "/documents/sign-requests/{requestId}/template": {
      "post": {
        "summary": "Save a prepared draft as a template",
        "description": "Requires documents:write.",
        "parameters": [
          {
            "in": "path",
            "name": "requestId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Template saved."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/signing/{token}": {
      "get": {
        "summary": "Open a private signing session",
        "security": [],
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Token-scoped recipient session."
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "Link is no longer active."
          }
        }
      }
    },
    "/signing/{token}/complete": {
      "post": {
        "summary": "Complete a signing session",
        "security": [],
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recipient response recorded."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "410": {
            "description": "Link is no longer active."
          }
        }
      }
    },
    "/signing/{token}/decline": {
      "post": {
        "summary": "Decline an agreement",
        "security": [],
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Decline recorded and agreement stopped."
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "410": {
            "description": "Link is no longer active."
          }
        }
      }
    },
    "/finance/bills": {
      "get": {
        "summary": "List Accounts Payable bills",
        "description": "Return a bounded AP register with status counts. Accepts status, q, dueBefore, dueAfter, cursor, and limit. Requires finance:read, read, write, or Firebase Finance access.",
        "responses": { "200": { "description": "Accounts Payable register." }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } }
      },
      "post": {
        "summary": "Create an Accounts Payable bill",
        "description": "Create a needs-review supplier bill using minor-unit string amounts. Requires finance:write, write, or Firebase Finance write access.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["vendor", "issueDate", "dueDate", "amountMinor", "currency"], "properties": { "vendor": { "type": "object" }, "invoiceNumber": { "type": "string" }, "issueDate": { "type": "string", "format": "date" }, "dueDate": { "type": "string", "format": "date" }, "amountMinor": { "type": "string", "pattern": "^[0-9]+$" }, "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }, "lineItems": { "type": "array", "items": { "type": "object" } } } } } } },
        "responses": { "201": { "description": "Bill created for review." }, "400": { "$ref": "#/components/responses/BadRequest" }, "409": { "description": "Duplicate invoice or idempotency conflict." } }
      }
    },
    "/finance/bills/aging": {
      "get": {
        "summary": "Get Accounts Payable aging",
        "description": "Return approved open bills in current, 1-30, 31-60, and 60+ day buckets by vendor and currency, plus bills awaiting approval.",
        "responses": { "200": { "description": "AP aging report." } }
      }
    },
    "/finance/bills/{billId}/approve": {
      "post": {
        "summary": "Approve an Accounts Payable bill",
        "description": "Approve a reviewed bill so it enters committed cash forecasting and outgoing-bank matching. Related endpoints use the same request contract: unapprove, reject, cancel, match, and unmatch.",
        "parameters": [{ "in": "path", "name": "billId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["expectedRevision"], "properties": { "expectedRevision": { "type": "integer" }, "reason": { "type": "string" }, "transactionId": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Bill transitioned." }, "409": { "description": "Stale revision or invalid lifecycle transition." } }
      }
    },
    "/finance/bills/{billId}": {
      "get": {
        "summary": "Read an Accounts Payable bill",
        "parameters": [{ "in": "path", "name": "billId", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Bill details, audit history, and match candidates." }, "404": { "description": "Bill not found." } }
      },
      "patch": {
        "summary": "Edit a reviewable Accounts Payable bill",
        "parameters": [{ "in": "path", "name": "billId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Bill updated." }, "409": { "description": "Stale revision or immutable state." } }
      },
      "delete": {
        "summary": "Delete an uncommitted Accounts Payable bill",
        "description": "Delete an unmatched bill in needs-review, rejected, or cancelled state. Approved bills must first be unapproved; paid bills are retained. A linked source receipt or scan is preserved for reprocessing.",
        "parameters": [{ "in": "path", "name": "billId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Uncommitted bill deleted; source evidence retained." }, "409": { "description": "Approved, paid, or matched bill cannot be deleted." } }
      }
    },
    "/finance/bills/bulk-approve": {
      "post": {
        "summary": "Approve a bounded batch of Accounts Payable bills",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Per-bill approval and failure results." } }
      }
    },
    "/finance/bills/{billId}/unapprove": {
      "post": {
        "summary": "Return an approved bill to review",
        "parameters": [{ "in": "path", "name": "billId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Bill returned to needs-review." } }
      }
    },
    "/finance/bills/{billId}/reject": {
      "post": {
        "summary": "Reject an Accounts Payable bill",
        "parameters": [{ "in": "path", "name": "billId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Bill rejected with an audit reason." } }
      }
    },
    "/finance/bills/{billId}/cancel": {
      "post": {
        "summary": "Cancel an open Accounts Payable bill",
        "parameters": [{ "in": "path", "name": "billId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Bill cancelled with an audit reason." } }
      }
    },
    "/finance/bills/{billId}/match": {
      "post": {
        "summary": "Match a bill to an outgoing bank transaction",
        "parameters": [{ "in": "path", "name": "billId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Approved bill marked paid and linked to the transaction." }, "409": { "description": "Direction, amount, currency, state, or revision mismatch." } }
      }
    },
    "/finance/bills/{billId}/unmatch": {
      "post": {
        "summary": "Remove a bill payment match",
        "parameters": [{ "in": "path", "name": "billId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Bill returned to approved and transaction backlink removed." } }
      }
    },
    "/finance/bills/rematch": {
      "post": {
        "summary": "Match approved payables to bank transactions",
        "description": "Conservatively match up to 100 approved unmatched bills to exact outgoing external bank transactions.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Matched bills and review candidates." } }
      }
    },
    "/finance/accounts": {
      "get": {
        "summary": "List Finance accounts with invoice payment details",
        "description": "Return configured Finance accounts and their invoice-ready bank, BIC/SWIFT, IBAN, or account-number fields.",
        "responses": { "200": { "description": "Finance accounts and payment details." } }
      }
    },
    "/finance/accounts/{accountId}/payment-details": {
      "patch": {
        "summary": "Update an operating account's invoice payment details",
        "description": "Owner-only update of country, bank, BIC/SWIFT, and either IBAN or account number for an operating account.",
        "parameters": [{ "in": "path", "name": "accountId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["country", "bank", "bic"], "properties": { "country": { "type": "string" }, "bank": { "type": "string" }, "bic": { "type": "string" }, "iban": { "type": "string" }, "accountNumber": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Payment details updated." }, "403": { "$ref": "#/components/responses/Forbidden" } }
      }
    },
    "/finance/invoice-customers": {
      "get": {
        "summary": "List Companies as invoice customers",
        "description": "Return Companies-board records available for Accounts Receivable customer selection.",
        "responses": { "200": { "description": "Invoice customer options." } }
      }
    },
    "/finance/invoices": {
      "get": {
        "summary": "List Accounts Receivable invoices",
        "description": "Return a bounded AR invoice register with draft, issued, paid, and void counts.",
        "responses": { "200": { "description": "Accounts Receivable register." } }
      },
      "post": {
        "summary": "Create an Accounts Receivable invoice draft",
        "description": "Create an editable customer invoice draft. All calculated amounts are server-derived minor-unit strings.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["client", "lineItems", "currency"], "properties": { "client": { "type": "object" }, "issueDate": { "type": "string", "format": "date" }, "dueDate": { "type": "string", "format": "date" }, "servicePeriod": { "type": "object" }, "taxTreatment": { "type": "string", "enum": ["domestic", "reverse_charge", "intra_community", "exempt", "out_of_scope"] }, "currency": { "type": "string" }, "lineItems": { "type": "array", "minItems": 1, "items": { "type": "object" } } } } } } },
        "responses": { "201": { "description": "Invoice draft created." }, "400": { "$ref": "#/components/responses/BadRequest" } }
      }
    },
    "/finance/invoices/import": {
      "post": {
        "summary": "Extract and import a customer invoice with Gemini",
        "description": "Accept a raw PDF or supported invoice image, validate it, extract customer, dates, currency, lines, tax, and payment account data with Gemini, and create an editable review-required Accounts Receivable draft. Duplicate source hashes reuse the existing draft.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }, { "in": "header", "name": "X-File-Name", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/pdf": { "schema": { "type": "string", "contentEncoding": "binary" } }, "image/jpeg": { "schema": { "type": "string", "contentEncoding": "binary" } }, "image/png": { "schema": { "type": "string", "contentEncoding": "binary" } } } },
        "responses": { "201": { "description": "Invoice extracted and draft created." }, "200": { "description": "Existing draft returned for a duplicate source file." }, "422": { "description": "Required invoice fields could not be extracted." } }
      }
    },
    "/finance/invoices/aging": {
      "get": {
        "summary": "Get Accounts Receivable aging",
        "description": "Return issued unpaid invoices in current, 1-30, 31-60, and 60+ day buckets by customer and currency.",
        "responses": { "200": { "description": "AR aging report." } }
      }
    },
    "/finance/invoices/{invoiceId}": {
      "get": {
        "summary": "Read an Accounts Receivable invoice",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Invoice details, retained revisions, delivery history, and match candidates." }, "404": { "description": "Invoice not found." } }
      },
      "patch": {
        "summary": "Edit an Accounts Receivable draft",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Draft invoice recalculated and updated." }, "409": { "description": "Stale revision or invoice is no longer a draft." } }
      },
      "delete": {
        "summary": "Delete an Accounts Receivable draft",
        "description": "Delete an unissued draft without consuming an invoice number. Issued, paid, and void invoices are retained.",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Draft invoice deleted." }, "409": { "description": "Issued invoices are retained." } }
      }
    },
    "/finance/invoices/rematch": {
      "post": {
        "summary": "Match issued receivables to bank transactions",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Matched invoices and ambiguous review candidates." } }
      }
    },
    "/finance/invoices/{invoiceId}/issue": {
      "post": {
        "summary": "Issue an immutable customer invoice",
        "description": "Atomically reserve the next year-scoped number, snapshot legal data, render a private PDF, and add the invoice to AR aging. Related lifecycle endpoints are revise, void, match, and unmatch.",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["expectedRevision"], "properties": { "expectedRevision": { "type": "integer" } } } } } },
        "responses": { "200": { "description": "Numbered invoice issued." }, "409": { "description": "Stale revision or invalid invoice state." } }
      }
    },
    "/finance/invoices/{invoiceId}/revise": {
      "post": {
        "summary": "Create a retained revision of an unsent invoice",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "New immutable PDF revision stored." }, "409": { "description": "Sent, paid, void, or stale invoice cannot be revised." } }
      }
    },
    "/finance/invoices/{invoiceId}/void": {
      "post": {
        "summary": "Void an unsent issued invoice",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Invoice voided with an audit reason." }, "409": { "description": "Delivered or paid invoice cannot be voided." } }
      }
    },
    "/finance/invoices/{invoiceId}/match": {
      "post": {
        "summary": "Match an invoice to an incoming bank transaction",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Issued invoice marked paid and linked to the transaction." }, "409": { "description": "Direction, amount, currency, state, or revision mismatch." } }
      }
    },
    "/finance/invoices/{invoiceId}/unmatch": {
      "post": {
        "summary": "Remove an invoice payment match",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Invoice returned to issued and transaction backlink removed." } }
      }
    },
    "/finance/invoices/{invoiceId}/send": {
      "post": {
        "summary": "Send an issued invoice",
        "description": "Send the current immutable PDF through the connected owner Gmail account and append delivery history. Requires finance:invoices:send, write, or Firebase Finance write access.",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["expectedRevision"], "properties": { "expectedRevision": { "type": "integer" }, "to": { "oneOf": [{ "type": "string", "format": "email" }, { "type": "array", "items": { "type": "string", "format": "email" } }] }, "cc": { "type": "array", "items": { "type": "string", "format": "email" } }, "subject": { "type": "string" }, "message": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Invoice sent and delivery recorded." }, "409": { "description": "Invoice or Gmail connection is not ready." } }
      }
    },
    "/finance/invoices/{invoiceId}/pdf": {
      "get": {
        "summary": "Download a private invoice PDF",
        "description": "Stream the hash-verified current or retained invoice revision with private no-store headers.",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }, { "in": "query", "name": "revision", "schema": { "oneOf": [{ "type": "integer" }, { "type": "string", "const": "current" }] } }],
        "responses": { "200": { "description": "Invoice PDF.", "content": { "application/pdf": { "schema": { "type": "string", "contentEncoding": "binary" } } } }, "404": { "description": "Invoice revision not found." } }
      }
    },
    "/finance/invoices/{invoiceId}/preview": {
      "get": {
        "summary": "Preview an invoice PDF",
        "description": "Render a clearly marked, non-numbered draft preview without storing it or consuming a sequence number. For issued invoices, stream the current hash-verified immutable PDF with inline no-store headers.",
        "parameters": [{ "in": "path", "name": "invoiceId", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Inline invoice PDF preview.", "content": { "application/pdf": { "schema": { "type": "string", "contentEncoding": "binary" } } } }, "404": { "description": "Invoice not found." } }
      }
    },
    "/finance/invoice-settings": {
      "get": { "summary": "Read invoice issuer settings", "responses": { "200": { "description": "Issuer, bank, and numbering settings." } } },
      "patch": {
        "summary": "Update invoice issuer settings",
        "description": "Owner-only legal issuer, payment-destination, and numbering configuration. Issuer VAT is optional; treatment-specific customer VAT requirements still apply.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "responses": { "200": { "description": "Invoice settings updated." }, "403": { "$ref": "#/components/responses/Forbidden" } }
      }
    },
    "/finance/exports/journal.csv": {
      "post": {
        "summary": "Export the canonical accounting journal",
        "description": "Return a bounded, deterministic UTF-8 CSV with formula-injection protection and transaction/decision provenance.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["dateFrom", "dateTo"], "properties": { "dateFrom": { "type": "string", "format": "date" }, "dateTo": { "type": "string", "format": "date" }, "accountIds": { "type": "array", "items": { "type": "string" } }, "includeTransfers": { "type": "boolean" }, "decisionStatus": { "type": "string", "enum": ["all", "coded", "human_verified"] } } } } } },
        "responses": { "200": { "description": "Accounting journal CSV.", "content": { "text/csv": { "schema": { "type": "string" } } } }, "413": { "description": "Requested export is too large." } }
      }
    },
    "/finance/exports/drive/accounts": {
      "get": {
        "summary": "Get the session Google account for Finance Drive export",
        "description": "Returns Drive readiness and the public Google Identity Services client configuration for the signed-in ONE session. The session email is authoritative and the account is not selectable. Existing matching legacy grants are accepted as a rollout fallback. Firebase user authentication is required; API keys cannot authorize, browse Drive, or invoke external writes.",
        "security": [{ "firebaseAuth": [] }],
        "responses": { "200": { "description": "Connected account capability summary." }, "403": { "$ref": "#/components/responses/Forbidden" } }
      }
    },
    "/finance/drive/authorization": {
      "post": {
        "summary": "Authorize Finance Drive access for the signed-in ONE user",
        "description": "Exchanges a Google Identity Services popup authorization code on the server, verifies the Google Drive profile email exactly matches the Firebase session email, and stores a dedicated per-user refresh token. Tokens are never returned to the browser. Requires X-Requested-With: XmlHttpRequest and an HTTPS or local-development redirect origin matching the request Origin. API keys are rejected.",
        "security": [{ "firebaseAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["code", "redirectUri"], "properties": { "code": { "type": "string", "description": "One-time code returned by Google Identity Services." }, "redirectUri": { "type": "string", "format": "uri", "description": "Current ONE browser origin used by the popup code flow." } } } } } },
        "responses": { "200": { "description": "Session-bound Drive authorization and capability summary." }, "400": { "description": "Invalid code, in-app header, or origin." }, "403": { "description": "The Google account email differs from the signed-in ONE session or the caller is not a Firebase user." }, "409": { "description": "Google did not grant the required Drive scopes or offline refresh access." } }
      }
    },
    "/finance/drive/browser": {
      "post": {
        "summary": "Browse Google Drive export folders",
        "description": "Lists My Drive and Shared Drives, or child folders beneath one selected folder, using only the Google authorization that matches the signed-in ONE email. Access tokens remain server-side. The endpoint is read-only, supports Shared Drives, returns sanitized folder capabilities, and rejects API keys.",
        "security": [{ "firebaseAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "googleAccountId": { "type": "string", "description": "Optional compatibility value; when supplied it must identify the Google authorization matching the signed-in ONE email." }, "folderId": { "type": "string" }, "resourceKey": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Current folder, child folders, Shared Drive context, and add/list-child capabilities." }, "403": { "$ref": "#/components/responses/Forbidden" }, "409": { "description": "The connected account must be reauthorized with Drive read-only access or the folder is unavailable." } }
      }
    },
    "/finance/drive/destination/verify": {
      "post": {
        "summary": "Verify a Google Drive export destination",
        "description": "Re-reads the ONE-browser-selected folder with the session-matched Google account and requires folder MIME type plus capabilities.canAddChildren=true. Supports Shared Drives and does not change the folder.",
        "security": [{ "firebaseAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["destinationFolderId"], "properties": { "googleAccountId": { "type": "string", "description": "Optional compatibility value; the session-matched Google account is authoritative." }, "destinationFolderId": { "type": "string" }, "destinationResourceKey": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Writable folder identity and capability." }, "403": { "description": "The folder is not writable by the session account." }, "409": { "description": "The session account must be reauthorized or the folder is unavailable." } }
      }
    },
    "/finance/drive/destination/create": {
      "post": {
        "summary": "Create a Google Drive export destination folder",
        "description": "Creates exactly one new app-owned folder inside the ONE browser's selected My Drive or Shared Drive parent. Revalidates capabilities.canAddChildren before creation, verifies the new folder and parent afterward, and uses a pre-generated Drive ID plus a stable idempotency key so retries do not create duplicates. Existing Drive content is never changed.",
        "security": [{ "firebaseAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["parentFolderId", "name", "creationId"], "properties": { "googleAccountId": { "type": "string", "description": "Optional compatibility value; the session-matched Google account is authoritative." }, "parentFolderId": { "type": "string" }, "parentResourceKey": { "type": "string" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "creationId": { "type": "string", "pattern": "^fdc_[A-Za-z0-9_-]{8,84}$" }, "retry": { "type": "boolean" } } } } } },
        "responses": { "201": { "description": "New or idempotently reused writable folder metadata." }, "403": { "description": "The selected parent does not permit adding children." }, "409": { "description": "Google reauthorization, parent reselection, or idempotency correction is required." } }
      }
    },
    "/finance/exports/drive": {
      "get": {
        "summary": "List Finance Drive exports",
        "description": "Returns up to 50 durable export records protected by Finance access. Optionally filter by YYYY-MM month.",
        "security": [{ "firebaseAuth": [] }],
        "parameters": [{ "in": "query", "name": "month", "schema": { "type": "string", "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$" } }],
        "responses": { "200": { "description": "Finance Drive export history." } }
      },
      "post": {
        "summary": "Queue an immutable monthly Finance Drive export",
        "description": "Queues a durable background export and returns immediately with an export record to poll at GET /finance/exports/drive/{exportId}. The worker creates a new ONE-owned run beneath a ONE-browser-selected Drive or Shared Drive folder, copies verified evidence/invoice bytes, creates native ING/Wise Google Sheets with links, and never moves, deletes, overwrites, or changes pre-existing Drive content. Requires a signed-in Finance editor/owner and a stable X-Idempotency-Key.",
        "security": [{ "firebaseAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["exportId", "month", "accountIds", "destinationFolderId"], "properties": { "exportId": { "type": "string", "pattern": "^fex_[A-Za-z0-9_-]{8,84}$" }, "month": { "type": "string", "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$" }, "accountIds": { "type": "array", "items": { "type": "string" }, "maxItems": 100 }, "decisionStatus": { "type": "string", "enum": ["all", "coded", "human_verified"], "default": "coded" }, "includeTransfers": { "type": "boolean", "default": false }, "includeEvidence": { "type": "boolean", "default": true }, "includeRevenueInvoices": { "type": "boolean", "default": true }, "googleAccountId": { "type": "string", "description": "Optional compatibility value; the export always uses the authorization matching the signed-in ONE email." }, "destinationFolderId": { "type": "string" }, "destinationResourceKey": { "type": "string" }, "destinationName": { "type": "string" }, "rootName": { "type": "string", "default": "ONE Finance Exports - GeoTech BV" } } } } } },
        "responses": { "202": { "description": "Queued or idempotently reused export record. Poll its status until complete, partial, or failed." }, "409": { "description": "Google reauthorization, destination reselection, or idempotency correction is required." }, "413": { "description": "The monthly row or evidence-file limit was exceeded." } }
      }
    },
    "/finance/exports/drive/{exportId}": {
      "get": {
        "summary": "Get Finance Drive export status",
        "description": "Returns progress, counts, warnings, error summary, and protected Drive completion links for one export.",
        "security": [{ "firebaseAuth": [] }],
        "parameters": [{ "in": "path", "name": "exportId", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Finance Drive export record." }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },
    "/finance/forecast/view": {
      "post": {
        "summary": "Calculate the Finance cash forecast",
        "description": "Return deterministic actual and forecast cash balances, Base/Upside/Downside paths, income and spending, category drivers, recurring patterns, planned events, FX provenance, and data-quality warnings. Requires finance:read, read, or write scope, or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanceCashForecastRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated cash outlook.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FinanceCashForecastResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Required exchange-rate coverage is unavailable."
          }
        }
      }
    },
    "/finance/forecast/events": {
      "post": {
        "summary": "Create a planned Finance cash event",
        "description": "Create a retry-safe planned inflow or outflow. Requires finance:write or write scope, or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "in": "header",
            "name": "X-Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 160
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanceCashPlanCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Cash plan created or an idempotent retry returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FinanceCashPlanMutationResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/finance/forecast/events/{eventId}": {
      "patch": {
        "summary": "Update, archive, or restore a Finance cash plan",
        "description": "Patch one cash plan using expectedUpdatedAt for optimistic concurrency. Requires finance:write or write scope, or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "eventId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanceCashPlanUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cash plan updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FinanceCashPlanMutationResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The plan changed after the supplied revision."
          }
        }
      }
    },
    "/finance/forecast/settings": {
      "put": {
        "summary": "Update Finance cash forecast settings",
        "description": "Update the global minimum-cash threshold and default cash scope/horizon with optimistic concurrency. Requires finance:write or write scope, or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanceCashForecastSettingsUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Forecast settings updated."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "description": "The settings changed after the supplied revision."
          }
        }
      }
    },
    "/finance/investment-portfolios": {
      "post": {
        "summary": "Record a reconciled investment portfolio snapshot",
        "description": "Save executed positions, cost basis, available brokerage cash, disclosed purchase costs, annual ING/fund costs, and estimated sale costs. Position values plus other items must reconcile to investedValueMinor; invested value plus available cash must reconcile to balanceMinor. Flow includes only available cash, while optional estimated ING service costs can be forecast as cash spending. Requires finance:write or write scope, or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "accountId",
                  "date",
                  "currency",
                  "balanceMinor",
                  "investedValueMinor",
                  "availableCashMinor",
                  "positions"
                ],
                "properties": {
                  "accountId": {
                    "type": "string",
                    "example": "ing-investment"
                  },
                  "date": {
                    "type": "string",
                    "format": "date"
                  },
                  "currency": {
                    "type": "string",
                    "pattern": "^[A-Z]{3}$"
                  },
                  "balanceMinor": {
                    "type": "string",
                    "pattern": "^\\d+$"
                  },
                  "investedValueMinor": {
                    "type": "string",
                    "pattern": "^\\d+$"
                  },
                  "availableCashMinor": {
                    "type": "string",
                    "pattern": "^\\d+$"
                  },
                  "forecastAnnualCashCosts": {
                    "type": "boolean",
                    "description": "Project estimated ING cash service costs in Flow. Fund costs remain embedded in NAV."
                  },
                  "sourceNote": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "positions": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "object",
                      "required": [
                        "name",
                        "units",
                        "currency",
                        "currentPrice",
                        "currentValueMinor",
                        "costBasisMinor",
                        "costs"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "isin": {
                          "type": "string",
                          "pattern": "^[A-Z0-9]{12}$"
                        },
                        "symbol": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        },
                        "units": {
                          "type": "string"
                        },
                        "currency": {
                          "type": "string"
                        },
                        "currentPrice": {
                          "type": "string"
                        },
                        "averagePurchasePrice": {
                          "type": "string"
                        },
                        "currentValueMinor": {
                          "type": "string",
                          "pattern": "^\\d+$"
                        },
                        "costBasisMinor": {
                          "type": "string",
                          "pattern": "^\\d+$"
                        },
                        "costs": {
                          "type": "object",
                          "required": [
                            "purchaseCostStatus",
                            "saleCostStatus"
                          ],
                          "description": "Disclosed or estimated purchase costs, annual ING/fund cost rates in basis points, and estimated sale costs."
                        }
                      }
                    }
                  },
                  "otherItems": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Investment portfolio snapshot saved and Finance recalculated."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/finance/market-quotes": {
      "get": {
        "summary": "Read delayed market prices for approved investment positions",
        "description": "Resolve approved portfolio ISIN mappings and return delayed Yahoo Finance prices. Quote failures are returned per position so clients can retain the latest ING statement price. Requires finance:read, read, or write scope, or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "isins",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated list of up to 20 security ISINs."
          },
          {
            "in": "query",
            "name": "isin",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "pattern": "^[A-Z0-9]{12}$"
              }
            },
            "style": "form",
            "explode": true,
            "description": "Repeated ISIN query parameter. Use either isin or isins."
          }
        ],
        "responses": {
          "200": {
            "description": "Delayed quotes and per-position availability.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "source": {
                      "type": "string",
                      "example": "Yahoo Finance delayed quotes"
                    },
                    "fetchedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "quotes": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "status",
                          "isin"
                        ],
                        "properties": {
                          "status": {
                            "type": "string",
                            "enum": [
                              "available",
                              "unavailable"
                            ]
                          },
                          "isin": {
                            "type": "string"
                          },
                          "symbol": {
                            "type": "string"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "price": {
                            "type": "string"
                          },
                          "previousClose": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "dayChange": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "dayChangePercent": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "dayLow": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "dayHigh": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "fiftyTwoWeekLow": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "fiftyTwoWeekHigh": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "marketTime": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "fetchedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "delayed": {
                            "type": "boolean"
                          },
                          "provider": {
                            "type": "string"
                          },
                          "providerUrl": {
                            "type": "string",
                            "format": "uri"
                          },
                          "error": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/finance/balance-screenshots": {
      "post": {
        "summary": "Recognize and save account balances from a bank screenshot",
        "description": "Validate a transient direct ING account, ING credit-card, or Wise screenshot, use Gemini to extract independently held current balances, deterministically match known Finance accounts, and add dated balance snapshots. ING investment Belegd/Gelddeel values are stored as investedValueMinor and availableCashMinor on one reconciled total, and only availableCashMinor enters cash forecasts. Credit cards use Current balance only (including zero), reject available credit, limits, and statement values, and normalize displayed debt as a negative cash position. The raw screenshot is never stored. Requires finance:write or write scope, or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "in": "header",
            "name": "X-File-Name",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 180
            }
          },
          {
            "in": "header",
            "name": "X-Balance-Date",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Optional as-of date. Defaults to a visible screenshot date or the current UTC date."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "image/jpeg": {
              "schema": {
                "type": "string",
                "contentEncoding": "binary"
              }
            },
            "image/png": {
              "schema": {
                "type": "string",
                "contentEncoding": "binary"
              }
            },
            "image/webp": {
              "schema": {
                "type": "string",
                "contentEncoding": "binary"
              }
            },
            "image/tiff": {
              "schema": {
                "type": "string",
                "contentEncoding": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Recognized matches saved or returned unchanged for an idempotent same-day retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "asOfDate": {
                      "type": "string",
                      "format": "date"
                    },
                    "model": {
                      "type": "string"
                    },
                    "rawScreenshotStored": {
                      "type": "boolean",
                      "const": false
                    },
                    "applied": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "accountId": {
                            "type": "string"
                          },
                          "date": {
                            "type": "string",
                            "format": "date"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "balanceMinor": {
                            "type": "string"
                          },
                          "investedValueMinor": {
                            "type": "string",
                            "description": "Investment-account securities value when a complete allocation was recognized."
                          },
                          "availableCashMinor": {
                            "type": "string",
                            "description": "Investment-account uninvested cash included in cash forecasts when a complete allocation was recognized."
                          },
                          "status": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "description": "The upload is not a supported image."
          },
          "422": {
            "description": "The screen is not a direct ING/Wise UI or no unambiguous balance could be matched."
          },
          "502": {
            "description": "Gemini recognition failed."
          }
        }
      }
    },
    "/finance/view": {
      "post": {
        "summary": "Search a bounded Finance transaction view",
        "description": "Return up to 500 filtered transactions, including the compact evidenceCount used by AssistHub to match a scan before upload. Requires finance:read or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanceViewRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Filtered Finance transaction window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FinanceViewResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/finance/fin-results/preview": {
      "post": {
        "summary": "Validate a Fin Results CSV before import",
        "description": "Validate the Bank26 Fin Results schema, normalize rows in memory, deterministically match existing bank transactions, and preview safe legacy-category mappings. This endpoint performs no database write and requires a Firebase user with Finance access.",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanceFinResultsFileRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validated Fin Results matching and mapping summary."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/finance/fin-results/import": {
      "post": {
        "summary": "Import Fin Results as review evidence",
        "description": "Append normalized Fin Results rows, hashes, deterministic matches, and category-mapping evidence without creating, replacing, or deleting canonical bank transactions or Manual Decisions. Exact reimports are idempotent. Requires a Firebase user with Finance access.",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanceFinResultsFileRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Fin Results review evidence imported or already present."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/finance/fin-results/transactions/{transactionId}/review": {
      "parameters": [
        {
          "name": "transactionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "summary": "Confirm or withdraw one Fin Results mapping",
        "description": "A human confirmation applies the safely mapped economic category as an approved Manual Decision while preserving project allocations. Import alone never approves a decision. Withdrawal retains both the current Manual Decision and append-only confirmation history. Requires a Firebase user with Finance access.",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rowId": {
                    "type": "string"
                  },
                  "active": {
                    "type": "boolean",
                    "default": true
                  }
                },
                "required": [
                  "rowId",
                  "active"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fin Results review recorded; confirmation may also apply a Manual Decision."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The Fin Results match changed or has no safe category mapping."
          }
        }
      }
    },
    "/finance/transactions/{transactionId}/evidence": {
      "parameters": [
        {
          "name": "transactionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "List private Finance evidence",
        "description": "List receipt/invoice metadata and authenticated API download paths. Storage paths and public URLs are never returned. Requires finance:evidence:read or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Evidence metadata for the transaction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FinanceEvidenceListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "summary": "Upload private Finance evidence",
        "description": "Upload one raw PDF or supported image body, up to 20 MB. Requires finance:evidence:write or a Firebase user with Finance access. Send a stable X-Idempotency-Key so retries reuse the same record; reusing the key with different bytes returns 409.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-File-Name",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 180
            }
          },
          {
            "name": "X-Evidence-Type",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "receipt",
                "invoice",
                "other"
              ]
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 180
            }
          },
          {
            "name": "X-Source-Reference",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 180
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/pdf": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/jpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/webp": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/tiff": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/heif": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Evidence stored or idempotently reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FinanceEvidenceWriteResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "File limit reached or idempotency key reused with different bytes."
          },
          "413": {
            "description": "Evidence file exceeds 20 MB."
          }
        }
      }
    },
    "/finance/transactions/{transactionId}/evidence/{evidenceId}": {
      "parameters": [
        {
          "name": "transactionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "evidenceId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "Preview or download private Finance evidence",
        "description": "Stream the original private PDF/image with no-store headers. Requires finance:evidence:read or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Original evidence bytes.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/tiff": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/heif": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "summary": "Delete one private Finance evidence file",
        "description": "Permanently remove only the selected evidence object and metadata. The transaction, imports, decisions, and other evidence are preserved. Requires finance:evidence:write or a Firebase user with Finance access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Evidence deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FinanceEvidenceDeleteResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/finance/categorization/retrieve": {
      "post": {
        "summary": "Match Finance transactions from vector memory",
        "description": "Page through uncategorized transactions in the supplied Finance filters, create transient query embeddings, and deterministically match only unambiguous exact history or strong multi-example vector consensus from current human-approved decision links. No categorization LLM is called. Results are saved as pending AI-provenance decisions for human review. Large, cash-treatment, Project, Payroll, allocation, weak, and conflicting matches abstain.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "geoCrmKey": []
          },
          {
            "firebaseAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filters": {
                    "type": "object"
                  },
                  "cursor": {
                    "type": "string",
                    "maxLength": 240
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 20,
                    "default": 20
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "maxLength": 160
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "One bounded retrieval page with applied pending decisions, abstentions, and an optional continuation cursor."
          }
        }
      }
    },
    "/finance/categorization/retrieval-settings": {
      "post": {
        "summary": "Get Finance embedding and retrieval settings",
        "description": "Return immutable embedding identity, conservative defaults, allowed ranges, saved retrieval-quality controls, and update audit metadata. Requires finance:read or an authenticated Finance user.",
        "responses": {
          "200": {
            "description": "Current Finance retrieval settings and embedding identity."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/finance/categorization/retrieval-settings/update": {
      "post": {
        "summary": "Update Finance retrieval-quality settings",
        "description": "Persist one validated, audited global settings record used by both website vector retrieval and future MCP categorization contexts. Embedding model, dimensions, index version, and namespace remain immutable. Requires finance:apply API scope or Firebase owner access.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "settings"
                ],
                "properties": {
                  "settings": {
                    "type": "object",
                    "required": [
                      "minimumSemanticSimilarity",
                      "requiredSemanticExamples",
                      "semanticConflictMargin",
                      "minimumCorrectionSimilarity",
                      "requiredCorrectionExamples"
                    ],
                    "properties": {
                      "minimumSemanticSimilarity": {
                        "type": "number",
                        "minimum": 0.75,
                        "maximum": 0.95,
                        "default": 0.82
                      },
                      "requiredSemanticExamples": {
                        "type": "integer",
                        "minimum": 2,
                        "maximum": 5,
                        "default": 2
                      },
                      "semanticConflictMargin": {
                        "type": "number",
                        "minimum": 0.02,
                        "maximum": 0.15,
                        "default": 0.05
                      },
                      "minimumCorrectionSimilarity": {
                        "type": "number",
                        "minimum": 0.85,
                        "maximum": 0.98,
                        "default": 0.9
                      },
                      "requiredCorrectionExamples": {
                        "type": "integer",
                        "minimum": 2,
                        "maximum": 5,
                        "default": 2
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved Finance retrieval settings and update audit metadata."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/finance/categorization/vector-memory/stats": {
      "post": {
        "summary": "Get Finance vector-memory stats",
        "description": "Return the active embedding namespace, model, dimensions, vector count, and latest operation. Requires finance:read or an authenticated Finance user.",
        "responses": {
          "200": {
            "description": "Current Finance vector-memory statistics."
          }
        }
      }
    },
    "/finance/categorization/vector-memory/sync": {
      "post": {
        "summary": "Sync Finance vector memory",
        "description": "Create or reuse embeddings for explicit human-approved transactions or one bounded YYYY-MM page with finance:suggest. With exactly one transaction ID and replaceExisting=true, remove its previous decision vector/link revisions and generate a fresh replacement; this mode requires finance:apply. Authenticated Finance users may use either mode.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "transactionIds": {
                    "type": "array",
                    "maxItems": 100,
                    "items": {
                      "type": "string"
                    }
                  },
                  "month": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
                  },
                  "cursor": {
                    "type": "string",
                    "maxLength": 240
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  },
                  "replaceExisting": {
                    "type": "boolean",
                    "description": "Replace vector memory for exactly one transaction ID."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Embedding sync counts and optional month-page cursor."
          }
        }
      }
    },
    "/finance/categorization/vector-memory/clear": {
      "post": {
        "summary": "Clear Finance vector memory",
        "description": "Remove all generated embeddings and vector links while preserving Finance source data and reset audit history. Requires finance:apply or Firebase owner access.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "confirmation": {
                    "type": "string",
                    "enum": [
                      "CLEAR EMBEDDINGS"
                    ]
                  }
                },
                "required": [
                  "confirmation"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Embedding reset summary."
          },
          "400": {
            "description": "The exact destructive confirmation was not supplied."
          }
        }
      }
    },
    "/me/agent-keys": {
      "get": {
        "summary": "List personal agent keys",
        "description": "Firebase-authenticated. Returns the signed-in user's personal keys and last-used metadata without secrets.",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Personal agent keys."
          }
        }
      },
      "post": {
        "summary": "Create a personal agent key",
        "description": "Firebase-authenticated. Returns the durable secret once. The key inherits live user/project access and may create tasks or edit only assigned tasks.",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Personal key and one-time secret."
          }
        }
      }
    },
    "/me/agent-keys/{keyId}": {
      "delete": {
        "summary": "Revoke a personal agent key",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Personal key revoked."
          }
        }
      }
    },
    "/me/work": {
      "get": {
        "summary": "Get My Work",
        "description": "Firebase user or personal-key endpoint returning validated current focus, open assignments across permitted projects, and visible team focus.",
        "responses": {
          "200": {
            "description": "Cross-project work dashboard data."
          }
        }
      }
    },
    "/me/focus": {
      "get": {
        "summary": "Get current focus tasks",
        "description": "Returns every validated focus task for the principal, limited to one task per project. The legacy focus field contains the most recently updated entry and focuses contains the complete list.",
        "responses": {
          "200": {
            "description": "Validated current focus tasks."
          }
        }
      },
      "put": {
        "summary": "Set current focus",
        "description": "The task must be assigned to the principal and must not be Done. Setting focus replaces only the current task for the same project.",
        "responses": {
          "200": {
            "description": "Current focus set."
          }
        }
      },
      "delete": {
        "summary": "Clear current focus",
        "description": "Supply both boardId and projectId to clear one project's focus. Omit both to clear every focus task.",
        "parameters": [
          {
            "name": "boardId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current focus cleared."
          }
        }
      }
    },
    "/team-focus": {
      "get": {
        "summary": "List visible team focus",
        "parameters": [
          {
            "name": "boardId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Validated team focus records restricted to visible projects."
          }
        }
      }
    },
    "/boards/{boardId}/projects/{projectId}/source-control": {
      "patch": {
        "summary": "Update a project's source-control configuration",
        "description": "Firebase-authenticated project-editor endpoint. Stores one GitHub repository or one Perforce configuration with multiple depot paths, but no connector credentials.",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ProjectId"
          }
        ],
        "responses": {
          "200": {
            "description": "Source-control configuration updated."
          },
          "400": {
            "description": "More than one source-control configuration was supplied."
          },
          "403": {
            "description": "Project editor access is required."
          }
        }
      }
    },
    "/webhooks/github": {
      "post": {
        "summary": "Receive GitHub source events",
        "description": "Requires a valid X-Hub-Signature-256. Tracks branch, commit, and pull-request metadata without accepting diffs or file content.",
        "security": [],
        "responses": {
          "202": {
            "description": "GitHub event accepted."
          }
        }
      }
    },
    "/source-events/perforce": {
      "post": {
        "summary": "Ingest sanitized Perforce changelist metadata",
        "description": "Service-key endpoint for pending, shelved, or submitted changelists. File contents and diffs are not accepted.",
        "responses": {
          "202": {
            "description": "Perforce events accepted."
          }
        }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks/{taskId}/automation/undo": {
      "post": {
        "summary": "Undo the latest AI-managed task values",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ProjectId"
          },
          {
            "$ref": "#/components/parameters/TaskId"
          }
        ],
        "responses": {
          "200": {
            "description": "AI-managed values restored."
          }
        }
      }
    },
    "/boards/{boardId}/projects/{projectId}/tasks/{taskId}/automation/suggestions/{fingerprint}/{decision}": {
      "post": {
        "summary": "Accept or reject an inferred work link",
        "security": [
          {
            "firebaseAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BoardId"
          },
          {
            "$ref": "#/components/parameters/ProjectId"
          },
          {
            "$ref": "#/components/parameters/TaskId"
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "decision",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "accept",
                "reject"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Suggestion decision recorded."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Use Authorization: Bearer <GEOCRM_API_KEY>."
      },
      "geoCrmKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-GeoCRM-Key",
        "description": "Alternative API key header."
      },
      "firebaseAuth": {
        "type": "http",
        "scheme": "bearer",
        "format": "JWT",
        "description": "Firebase ID token for signed-in ONE browser users."
      }
    },
    "parameters": {
      "BoardId": {
        "name": "boardId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ColumnId": {
        "name": "columnId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Column ID or exact column title."
      },
      "ItemId": {
        "name": "itemId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ProjectId": {
        "name": "projectId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "TaskId": {
        "name": "taskId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "SubtaskId": {
        "name": "subtaskId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "TeamId": {
        "name": "teamId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ChannelId": {
        "name": "channelId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ProposalId": {
        "name": "proposalId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "AssetId": {
        "name": "assetId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKey": {
        "name": "X-Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Stable caller-generated key. A retry with the same request returns the prior result; reuse with different input is rejected.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 180
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, inactive, or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key does not include the required scope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BadRequest": {
        "description": "The request body or parameters are invalid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Board or item was not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "FinanceFinResultsFileRequest": {
        "type": "object",
        "properties": {
          "file": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "maxLength": 180
              },
              "byteSize": {
                "type": "integer",
                "minimum": 1,
                "maximum": 2097152
              },
              "contentBase64": {
                "type": "string",
                "contentEncoding": "base64"
              }
            },
            "required": [
              "name",
              "byteSize",
              "contentBase64"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "file"
        ],
        "additionalProperties": false
      },
      "FinanceCashForecastRequest": {
        "type": "object",
        "properties": {
          "reportingCurrency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$",
            "default": "EUR"
          },
          "horizon": {
            "type": "string",
            "enum": [
              "13w",
              "12m"
            ],
            "default": "13w"
          },
          "cashScope": {
            "type": "string",
            "enum": [
              "liquid",
              "treasury"
            ],
            "default": "liquid"
          },
          "includeArchivedEvents": {
            "type": "boolean",
            "default": false
          }
        },
        "additionalProperties": false
      },
      "FinanceCashPlan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inflow",
              "outflow"
            ]
          },
          "amountMinor": {
            "type": "string",
            "pattern": "^[0-9]+$",
            "description": "Positive amount in currency minor units."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "recurrence": {
            "type": "string",
            "enum": [
              "one_off",
              "weekly",
              "monthly",
              "quarterly",
              "annual"
            ]
          },
          "endDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "certainty": {
            "type": "string",
            "enum": [
              "committed",
              "expected",
              "possible"
            ]
          },
          "categoryId": {
            "type": "string"
          },
          "subcategoryId": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "planKind": {
            "type": "string",
            "enum": [
              "cash",
              "investment_purchase"
            ]
          },
          "investmentIsin": {
            "type": "string",
            "pattern": "^[A-Z0-9]{12}$"
          },
          "note": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "archived"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "title",
          "direction",
          "amountMinor",
          "currency",
          "startDate",
          "recurrence",
          "certainty",
          "status",
          "createdAt",
          "updatedAt"
        ],
        "additionalProperties": true
      },
      "FinanceCashPlanCreate": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "direction": {
            "type": "string",
            "enum": [
              "inflow",
              "outflow"
            ]
          },
          "amountMinor": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "recurrence": {
            "type": "string",
            "enum": [
              "one_off",
              "weekly",
              "monthly",
              "quarterly",
              "annual"
            ]
          },
          "endDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "certainty": {
            "type": "string",
            "enum": [
              "committed",
              "expected",
              "possible"
            ]
          },
          "categoryId": {
            "type": "string"
          },
          "subcategoryId": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "planKind": {
            "type": "string",
            "enum": [
              "cash",
              "investment_purchase"
            ],
            "default": "cash"
          },
          "investmentIsin": {
            "type": "string",
            "pattern": "^[A-Z0-9]{12}$"
          },
          "note": {
            "type": "string",
            "maxLength": 1000
          },
          "idempotencyKey": {
            "type": "string",
            "minLength": 8,
            "maxLength": 160,
            "description": "Optional body mirror of the required X-Idempotency-Key header."
          }
        },
        "required": [
          "title",
          "direction",
          "amountMinor",
          "currency",
          "startDate",
          "recurrence",
          "certainty"
        ],
        "additionalProperties": false
      },
      "FinanceCashPlanUpdate": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "direction": {
            "type": "string",
            "enum": [
              "inflow",
              "outflow"
            ]
          },
          "amountMinor": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "recurrence": {
            "type": "string",
            "enum": [
              "one_off",
              "weekly",
              "monthly",
              "quarterly",
              "annual"
            ]
          },
          "endDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "certainty": {
            "type": "string",
            "enum": [
              "committed",
              "expected",
              "possible"
            ]
          },
          "categoryId": {
            "type": "string"
          },
          "subcategoryId": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "planKind": {
            "type": "string",
            "enum": [
              "cash",
              "investment_purchase"
            ]
          },
          "investmentIsin": {
            "type": "string",
            "pattern": "^[A-Z0-9]{12}$"
          },
          "note": {
            "type": "string",
            "maxLength": 1000
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "archived"
            ]
          },
          "expectedUpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "expectedUpdatedAt"
        ],
        "additionalProperties": false
      },
      "FinanceCashForecastSettingsUpdate": {
        "type": "object",
        "properties": {
          "minimumCashMinor": {
            "type": "string",
            "pattern": "^-?[0-9]+$"
          },
          "minimumCashCurrency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          },
          "defaultScope": {
            "type": "string",
            "enum": [
              "liquid",
              "treasury"
            ]
          },
          "defaultHorizon": {
            "type": "string",
            "enum": [
              "13w",
              "12m"
            ]
          },
          "expectedUpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "expectedUpdatedAt"
        ],
        "additionalProperties": false
      },
      "FinanceCashPlanMutationResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "event": {
            "$ref": "#/components/schemas/FinanceCashPlan"
          },
          "requestId": {
            "type": "string"
          }
        },
        "required": [
          "ok",
          "event"
        ],
        "additionalProperties": true
      },
      "FinanceCashForecastResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "forecast": {
            "type": "object",
            "properties": {
              "calculationVersion": {
                "type": "integer"
              },
              "asOfDate": {
                "type": "string",
                "format": "date"
              },
              "reportingCurrency": {
                "type": "string"
              },
              "horizon": {
                "type": "string",
                "enum": [
                  "13w",
                  "12m"
                ]
              },
              "cashScope": {
                "type": "string",
                "enum": [
                  "liquid",
                  "treasury"
                ]
              },
              "startingBalance": {
                "type": "object",
                "properties": {
                  "totalMinor": {
                    "type": "string"
                  },
                  "thresholdMinor": {
                    "type": "string"
                  },
                  "includedAccountCount": {
                    "type": "integer"
                  },
                  "coveredAccountCount": {
                    "type": "integer"
                  },
                  "accounts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                },
                "required": [
                  "totalMinor",
                  "thresholdMinor",
                  "includedAccountCount",
                  "coveredAccountCount",
                  "accounts"
                ],
                "additionalProperties": true
              },
              "payables": {
                "type": "object",
                "description": "Open Accounts Payable bills placed on the Flow timeline. Approved bills are committed forecast outflows; bills needing approval are returned as pending exposure but remain excluded from spending, net movement, and cash balances until approved.",
                "properties": {
                  "paymentPolicy": {
                    "type": "string",
                    "enum": [
                      "approval_gated_bill_due_date"
                    ]
                  },
                  "openBillCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "approvedBillCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "pendingApprovalBillCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "forecastApprovedBillCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "forecastPendingBillCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "outsideHorizonBillCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "overdueBillCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "unforecastableBillCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "openBalanceMinor": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  },
                  "approvedBalanceMinor": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  },
                  "pendingApprovalBalanceMinor": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  },
                  "forecastApprovedBalanceMinor": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  },
                  "forecastPendingBalanceMinor": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  },
                  "overdueBalanceMinor": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  },
                  "payments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "billId": {
                          "type": "string"
                        },
                        "invoiceNumber": {
                          "type": "string"
                        },
                        "vendorName": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "approved",
                            "needs_review"
                          ]
                        },
                        "dueDate": {
                          "type": "string",
                          "format": "date"
                        },
                        "forecastDate": {
                          "type": "string",
                          "format": "date"
                        },
                        "paymentBasis": {
                          "type": "string",
                          "enum": [
                            "due_date",
                            "overdue_as_of"
                          ]
                        },
                        "overdueDays": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "currency": {
                          "type": "string"
                        },
                        "sourceAmountMinor": {
                          "type": "string"
                        },
                        "amountMinor": {
                          "type": "string"
                        },
                        "withinHorizon": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "billId",
                        "vendorName",
                        "status",
                        "dueDate",
                        "forecastDate",
                        "paymentBasis",
                        "currency",
                        "sourceAmountMinor",
                        "amountMinor",
                        "withinHorizon"
                      ],
                      "additionalProperties": false
                    }
                  }
                },
                "required": [
                  "paymentPolicy",
                  "openBillCount",
                  "approvedBillCount",
                  "pendingApprovalBillCount",
                  "forecastApprovedBillCount",
                  "forecastPendingBillCount",
                  "outsideHorizonBillCount",
                  "overdueBillCount",
                  "unforecastableBillCount",
                  "openBalanceMinor",
                  "approvedBalanceMinor",
                  "pendingApprovalBalanceMinor",
                  "forecastApprovedBalanceMinor",
                  "forecastPendingBalanceMinor",
                  "overdueBalanceMinor",
                  "payments"
                ],
                "additionalProperties": false
              },
              "receivables": {
                "type": "object",
                "description": "Outstanding issued Accounts Receivable balances represented as expected cash payments. Due invoices use their invoice due date; overdue invoices use the forecast as-of date; matched, paid, void, draft, and zero-balance invoices are excluded.",
                "properties": {
                  "collectionPolicy": {
                    "type": "string",
                    "enum": [
                      "invoice_due_date"
                    ]
                  },
                  "openInvoiceCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "forecastInvoiceCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "outsideHorizonInvoiceCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "overdueInvoiceCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "unforecastableInvoiceCount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "openBalanceMinor": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  },
                  "forecastBalanceMinor": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  },
                  "overdueBalanceMinor": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  },
                  "payments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "invoiceId": {
                          "type": "string"
                        },
                        "invoiceNumber": {
                          "type": "string"
                        },
                        "clientName": {
                          "type": "string"
                        },
                        "dueDate": {
                          "type": "string",
                          "format": "date"
                        },
                        "forecastDate": {
                          "type": "string",
                          "format": "date"
                        },
                        "collectionBasis": {
                          "type": "string",
                          "enum": [
                            "due_date",
                            "overdue_as_of"
                          ]
                        },
                        "overdueDays": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "currency": {
                          "type": "string"
                        },
                        "sourceAmountMinor": {
                          "type": "string"
                        },
                        "amountMinor": {
                          "type": "string"
                        },
                        "withinHorizon": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "invoiceId",
                        "clientName",
                        "dueDate",
                        "forecastDate",
                        "collectionBasis",
                        "currency",
                        "sourceAmountMinor",
                        "amountMinor",
                        "withinHorizon"
                      ],
                      "additionalProperties": false
                    }
                  }
                },
                "required": [
                  "collectionPolicy",
                  "openInvoiceCount",
                  "forecastInvoiceCount",
                  "outsideHorizonInvoiceCount",
                  "overdueInvoiceCount",
                  "unforecastableInvoiceCount",
                  "openBalanceMinor",
                  "forecastBalanceMinor",
                  "overdueBalanceMinor",
                  "payments"
                ],
                "additionalProperties": false
              },
              "actualBuckets": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "monthlyReviewBuckets": {
                "type": "array",
                "description": "Twelve calendar months of external cash movement and exact manual-verification coverage for the selected cash scope.",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "scenarios": {
                "type": "object",
                "properties": {
                  "base": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "upside": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "downside": {
                    "type": "object",
                    "additionalProperties": true
                  }
                },
                "required": [
                  "base",
                  "upside",
                  "downside"
                ]
              },
              "categoryDrivers": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "recurringPatterns": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "events": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FinanceCashPlan"
                }
              },
              "dataQuality": {
                "type": "object",
                "additionalProperties": true
              },
              "fx": {
                "type": "object",
                "additionalProperties": true
              }
            },
            "required": [
              "calculationVersion",
              "asOfDate",
              "reportingCurrency",
              "horizon",
              "cashScope",
              "startingBalance",
              "payables",
              "receivables",
              "actualBuckets",
              "monthlyReviewBuckets",
              "scenarios",
              "categoryDrivers",
              "recurringPatterns",
              "events",
              "dataQuality",
              "fx"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "ok",
          "forecast"
        ],
        "additionalProperties": true
      },
      "FinanceViewRequest": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "transactions",
              "review"
            ],
            "default": "transactions"
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 500,
            "default": 500
          },
          "query": {
            "type": "string",
            "maxLength": 240
          },
          "accountFilter": {
            "type": "string",
            "maxLength": 120
          },
          "classificationFilter": {
            "type": "string",
            "maxLength": 80
          },
          "categoryFilter": {
            "type": "string",
            "maxLength": 120
          },
          "dateFilterMode": {
            "type": "string",
            "enum": [
              "all",
              "range",
              "month",
              "day"
            ]
          },
          "dateFilterValue": {
            "type": "string",
            "maxLength": 10
          },
          "dateRangeStart": {
            "type": "string",
            "maxLength": 10
          },
          "dateRangeEnd": {
            "type": "string",
            "maxLength": 10
          },
          "amountFilter": {
            "type": "string",
            "enum": [
              "",
              "incoming",
              "outgoing",
              "zero"
            ]
          },
          "sort": {
            "type": "object",
            "properties": {
              "column": {
                "type": "string",
                "enum": [
                  "date",
                  "account",
                  "description",
                  "classification",
                  "amount"
                ]
              },
              "direction": {
                "type": "string",
                "enum": [
                  "asc",
                  "desc"
                ]
              }
            }
          }
        },
        "additionalProperties": false
      },
      "FinanceViewResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "transactions": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "postedDate": {
                  "type": "string"
                },
                "amountMinor": {
                  "type": "string"
                },
                "currency": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "counterpartyName": {
                  "type": "string"
                },
                "evidenceCount": {
                  "type": "integer",
                  "minimum": 0
                },
                "evidenceUpdatedAt": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "additionalProperties": true
            }
          },
          "matchingTransactionCount": {
            "type": "integer",
            "minimum": 0
          },
          "matchingEvidenceTransactionCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of matching transaction rows with at least one evidence file."
          },
          "matchingVerifiedEvidenceFileCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of evidence files attached to matching transactions that are not pending verification."
          },
          "truncated": {
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "transactions",
          "matchingTransactionCount",
          "matchingEvidenceTransactionCount",
          "matchingVerifiedEvidenceFileCount",
          "truncated"
        ],
        "additionalProperties": true
      },
      "FinanceEvidence": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "transactionId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "receipt",
              "invoice",
              "other"
            ]
          },
          "contentType": {
            "type": "string"
          },
          "size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20971520
          },
          "sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "uploadedBy": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "source": {
                "type": "string",
                "enum": [
                  "user",
                  "api_key"
                ]
              }
            },
            "required": [
              "name",
              "source"
            ]
          },
          "sourceReference": {
            "type": "string"
          },
          "downloadPath": {
            "type": "string",
            "pattern": "^/api/finance/transactions/"
          }
        },
        "required": [
          "id",
          "transactionId",
          "name",
          "kind",
          "contentType",
          "size",
          "sha256",
          "createdAt",
          "downloadPath"
        ],
        "additionalProperties": false
      },
      "FinanceEvidenceListResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "transactionId": {
            "type": "string"
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "evidence": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/FinanceEvidence"
            }
          }
        },
        "required": [
          "ok",
          "transactionId",
          "count",
          "evidence"
        ]
      },
      "FinanceEvidenceWriteResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "reused": {
            "type": "boolean"
          },
          "transactionId": {
            "type": "string"
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "evidence": {
            "$ref": "#/components/schemas/FinanceEvidence"
          }
        },
        "required": [
          "ok",
          "reused",
          "transactionId",
          "count",
          "evidence"
        ]
      },
      "FinanceEvidenceDeleteResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "deleted": {
            "type": "boolean"
          },
          "transactionId": {
            "type": "string"
          },
          "evidenceId": {
            "type": "string"
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          }
        },
        "required": [
          "ok",
          "deleted",
          "transactionId",
          "evidenceId",
          "count"
        ]
      },
      "TaskAutomationState": {
        "type": "string",
        "enum": [
          "queued",
          "cancelled"
        ]
      },
      "TaskAutomationAssignmentRequest": {
        "type": "object",
        "properties": {
          "boardId": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "assigned": {
            "type": "boolean",
            "default": true
          },
          "triggerType": {
            "type": "string",
            "enum": [
              "assignment",
              "mention",
              "manual"
            ],
            "default": "assignment"
          },
          "triggerId": {
            "type": "string",
            "maxLength": 160
          },
          "idempotencyKey": {
            "type": "string",
            "maxLength": 160
          }
        },
        "required": [
          "boardId",
          "projectId",
          "taskId",
          "assigned"
        ],
        "additionalProperties": false
      },
      "TaskContext": {
        "type": "object",
        "properties": {
          "taskUrl": {
            "type": "string",
            "format": "uri"
          },
          "revision": {
            "type": "string",
            "description": "SHA-256 task revision used for optimistic concurrency."
          },
          "board": {
            "type": "object",
            "additionalProperties": true
          },
          "project": {
            "type": "object",
            "additionalProperties": true
          },
          "task": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "fields": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "valuesByColumnId": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "automation": {
                "type": "object",
                "properties": {
                  "agentId": {
                    "type": "string",
                    "const": "agent_codex_local"
                  },
                  "agentName": {
                    "type": "string",
                    "const": "Codex"
                  },
                  "assigned": {
                    "type": "boolean"
                  },
                  "status": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/TaskAutomationState"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "updatedAt": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "agentId",
                  "agentName",
                  "assigned",
                  "status",
                  "updatedAt"
                ]
              },
              "comments": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "attachments": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "images": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "required": [
              "id",
              "name",
              "fields",
              "automation",
              "comments",
              "attachments",
              "images"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "taskUrl",
          "revision",
          "board",
          "project",
          "task"
        ]
      },
      "TaskCommentRequest": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8000
          },
          "expectedRevision": {
            "type": "string"
          },
          "idempotencyKey": {
            "type": "string",
            "maxLength": 160
          },
          "agentRunId": {
            "type": "string",
            "maxLength": 160
          }
        },
        "required": [
          "text"
        ]
      },
      "TaskReviewStateRequest": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "In Progress",
              "Review"
            ]
          },
          "expectedRevision": {
            "type": "string"
          }
        },
        "required": [
          "status"
        ]
      },
      "TaskWriteResponse": {
        "type": "object",
        "properties": {
          "revision": {
            "type": "string"
          },
          "duplicate": {
            "type": "boolean"
          },
          "status": {
            "type": "string"
          },
          "comment": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "revision"
        ],
        "additionalProperties": true
      },
      "Health": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "service": {
            "type": "string",
            "const": "geocrm-api"
          }
        },
        "required": [
          "ok",
          "service"
        ]
      },
      "AppInfo": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "const": "ONE"
          },
          "product": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "urls": {
            "type": "object",
            "properties": {
              "primaryApp": {
                "type": "string",
                "format": "uri",
                "description": "Primary ONE website and login page."
              },
              "defaultHostingApp": {
                "type": "string",
                "format": "uri",
                "description": "Firebase Hosting fallback URL."
              },
              "apiBase": {
                "type": "string",
                "format": "uri"
              },
              "bridge": {
                "type": "string",
                "format": "uri"
              },
              "docs": {
                "type": "string",
                "format": "uri"
              },
              "llms": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "mcpManifest": {
                "type": "string",
                "format": "uri"
              }
            },
            "required": [
              "primaryApp",
              "defaultHostingApp",
              "apiBase",
              "bridge",
              "docs",
              "llms",
              "openapi",
              "mcpManifest"
            ]
          },
          "answerHints": {
            "type": "object",
            "properties": {
              "websiteQuestion": {
                "type": "string",
                "description": "Short natural-language answer for ONE website/login URL questions."
              }
            }
          }
        },
        "required": [
          "name",
          "product",
          "description",
          "urls",
          "answerHints"
        ]
      },
      "AppInfoResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "service": {
            "type": "string",
            "const": "geocrm-api"
          },
          "app": {
            "$ref": "#/components/schemas/AppInfo"
          }
        },
        "required": [
          "ok",
          "service",
          "app"
        ]
      },
      "BrandStandardsResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "standards": {
            "type": "object",
            "properties": {
              "schemaVersion": { "type": "string" },
              "brand": { "type": "string", "const": "GeoTech" },
              "title": { "type": "string" },
              "effectiveDate": { "type": "string", "format": "date" },
              "source": { "type": "object", "additionalProperties": true },
              "identity": { "type": "object", "additionalProperties": true },
              "requiredUsage": { "type": "object", "additionalProperties": true },
              "logo": { "type": "object", "additionalProperties": true },
              "colours": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": { "type": "string" },
                    "hex": { "type": "string", "pattern": "^#[0-9A-F]{6}$" },
                    "rgb": { "type": "array", "items": { "type": "integer" }, "minItems": 3, "maxItems": 3 },
                    "cmyk": { "type": "array", "items": { "type": "integer" }, "minItems": 4, "maxItems": 4 },
                    "pantone": { "type": "string" }
                  },
                  "required": ["name", "hex", "rgb", "cmyk", "pantone"]
                }
              },
              "typography": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
              "designSystem": {
                "type": "object",
                "properties": {
                  "title": { "type": "string" },
                  "description": { "type": "string" },
                  "appliesTo": { "type": "array", "items": { "type": "string" } },
                  "foundations": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
                  "workflow": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
                  "guardrails": { "type": "array", "items": { "type": "string" } },
                  "pages": {
                    "type": "object",
                    "description": "The Cover, Getting Started, Foundations, and Tokens design-system pages.",
                    "properties": {
                      "cover": { "type": "object", "additionalProperties": true },
                      "gettingStarted": { "type": "object", "additionalProperties": true },
                      "foundations": { "type": "object", "additionalProperties": true },
                      "tokens": { "type": "object", "additionalProperties": true }
                    },
                    "required": ["cover", "gettingStarted", "foundations", "tokens"]
                  },
                  "tokenCollections": {
                    "type": "array",
                    "description": "All 89 design variables grouped into Brand, Color, Scale, and Typography collections.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": { "type": "string", "enum": ["Brand", "Color", "Scale", "Typography"] },
                        "role": { "type": "string" },
                        "modes": { "type": "array", "items": { "type": "string" } },
                        "tokens": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": { "type": "string" },
                              "resolvedType": { "type": "string", "enum": ["COLOR", "FLOAT", "STRING"] },
                              "scopes": { "type": "array", "items": { "type": "string" } },
                              "codeSyntax": {
                                "type": "object",
                                "properties": { "web": { "type": "string" } },
                                "required": ["web"]
                              },
                              "values": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "mode": { "type": "string" },
                                    "value": { "oneOf": [{ "type": "string" }, { "type": "number" }] },
                                    "alias": { "type": "string" }
                                  },
                                  "required": ["mode", "value"]
                                }
                              },
                              "unit": { "type": "string" }
                            },
                            "required": ["name", "resolvedType", "scopes", "codeSyntax", "values"]
                          }
                        }
                      },
                      "required": ["name", "role", "modes", "tokens"]
                    }
                  },
                  "visualLanguage": { "type": "object", "additionalProperties": true }
                },
                "required": ["title", "description", "appliesTo", "foundations", "workflow", "guardrails", "pages", "tokenCollections", "visualLanguage"]
              },
              "pattern": { "type": "object", "additionalProperties": true },
              "resources": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
              "agentAccess": { "type": "object", "additionalProperties": true }
            },
            "required": ["schemaVersion", "brand", "title", "effectiveDate", "source", "requiredUsage", "logo", "colours", "typography", "designSystem", "pattern", "resources", "agentAccess"]
          }
        },
        "required": ["ok", "standards"]
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "read",
                "write",
                "tasks:read",
                "tasks:write",
                "tasks:comment",
                "tasks:review",
                "tasks:run",
                "brain_dump:read",
                "brain_dump:write",
                "finance:read",
                "finance:write",
                "finance:invoices:send",
                "finance:suggest",
                "finance:apply",
                "finance:evidence:read",
                "finance:evidence:write",
                "backups:read",
                "backups:write",
                "tasks:write",
                "tasks:create",
                "tasks:update_assigned",
                "tasks:comment_assigned",
                "tasks:review_assigned"
              ]
            }
          },
          "prefix": {
            "type": "string"
          },
          "allowedBoardIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Optional board allowlist for task-scoped or Brain Dump-only keys. Empty means all boards."
          }
        },
        "required": [
          "id",
          "name",
          "scopes"
        ]
      },
      "LogEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "atMs": {
            "type": "integer"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAtMs": {
            "type": "integer"
          },
          "level": {
            "type": "string",
            "enum": [
              "debug",
              "info",
              "warn",
              "error"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "app",
              "llm",
              "connector",
              "system"
            ]
          },
          "connector": {
            "type": "string",
            "description": "Connector or integration name, such as slack, gmail, openrouter, codex, api, or web."
          },
          "source": {
            "type": "string",
            "description": "Source component, such as web, bridge, mcp, or geoCrmApi."
          },
          "message": {
            "type": "string"
          },
          "details": {
            "description": "Optional diagnostic details. Do not include secrets or live tokens."
          },
          "userUid": {
            "type": "string"
          },
          "userEmail": {
            "type": "string"
          },
          "apiKeyId": {
            "type": "string"
          },
          "apiKeyName": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "at",
          "atMs",
          "level",
          "category",
          "source",
          "message"
        ]
      },
      "LogListResponse": {
        "type": "object",
        "properties": {
          "retentionSeconds": {
            "type": "integer",
            "const": 3600
          },
          "pruned": {
            "type": "integer"
          },
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "format": "date-time"
              },
              "to": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "filters": {
            "type": "object"
          },
          "logs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LogEntry"
            }
          }
        },
        "required": [
          "retentionSeconds",
          "window",
          "logs"
        ]
      },
      "WriteLogRequest": {
        "type": "object",
        "properties": {
          "level": {
            "type": "string",
            "enum": [
              "debug",
              "info",
              "warn",
              "error"
            ],
            "default": "info"
          },
          "category": {
            "type": "string",
            "enum": [
              "app",
              "llm",
              "connector",
              "system"
            ],
            "default": "system"
          },
          "connector": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "default": "api"
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          },
          "details": {
            "description": "Optional JSON-serializable diagnostic details. Do not include secrets or live tokens."
          },
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "atMs": {
            "type": "integer"
          }
        },
        "required": [
          "message"
        ]
      },
      "WriteLogResponse": {
        "type": "object",
        "properties": {
          "retentionSeconds": {
            "type": "integer",
            "const": 3600
          },
          "log": {
            "$ref": "#/components/schemas/LogEntry"
          }
        },
        "required": [
          "retentionSeconds",
          "log"
        ]
      },
      "BoardSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "itemCount": {
            "type": "integer"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "columns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Column"
            }
          },
          "groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Group"
            }
          }
        },
        "required": [
          "id",
          "name",
          "itemCount",
          "columns",
          "groups"
        ]
      },
      "DeleteBoardRequest": {
        "type": "object",
        "properties": {
          "confirmName": {
            "type": "string",
            "minLength": 1,
            "description": "Must match the board name."
          },
          "reason": {
            "type": "string",
            "description": "Optional audit reason stored with the archived board copy."
          }
        },
        "required": [
          "confirmName"
        ]
      },
      "DeleteBoardResponse": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "boolean"
          },
          "board": {
            "$ref": "#/components/schemas/BoardSummary"
          },
          "deletedAt": {
            "type": "string",
            "format": "date-time"
          },
          "deletedBy": {
            "type": "object",
            "additionalProperties": true
          },
          "archivedPath": {
            "type": "string"
          }
        },
        "required": [
          "deleted",
          "board",
          "deletedAt",
          "deletedBy",
          "archivedPath"
        ]
      },
      "Column": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "title",
          "type"
        ]
      },
      "EnsureColumnRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1
          },
          "type": {
            "type": "string",
            "default": "text"
          },
          "id": {
            "type": "string",
            "description": "Optional explicit column ID."
          }
        },
        "required": [
          "title"
        ]
      },
      "EnsureColumnResponse": {
        "type": "object",
        "properties": {
          "column": {
            "$ref": "#/components/schemas/Column"
          },
          "created": {
            "type": "boolean"
          }
        },
        "required": [
          "column",
          "created"
        ]
      },
      "ArchiveColumnRequest": {
        "type": "object",
        "properties": {
          "confirmTitle": {
            "type": "string",
            "minLength": 1,
            "description": "Must match the current column title."
          }
        },
        "required": [
          "confirmTitle"
        ]
      },
      "ArchiveColumnResponse": {
        "type": "object",
        "properties": {
          "archived": {
            "type": "boolean"
          },
          "column": {
            "$ref": "#/components/schemas/Column"
          },
          "board": {
            "$ref": "#/components/schemas/BoardSummary"
          }
        },
        "required": [
          "archived",
          "column",
          "board"
        ]
      },
      "FieldMap": {
        "type": "object",
        "additionalProperties": {
          "type": [
            "string",
            "number",
            "boolean",
            "null"
          ]
        }
      },
      "FieldFilterMap": {
        "type": "object",
        "additionalProperties": {
          "oneOf": [
            {
              "type": [
                "string",
                "number",
                "boolean",
                "null"
              ]
            },
            {
              "type": "array",
              "items": {
                "type": [
                  "string",
                  "number",
                  "boolean",
                  "null"
                ]
              }
            }
          ]
        },
        "description": "Exact field filters keyed by column ID or column title. Array values match any listed value."
      },
      "Group": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "title"
        ]
      },
      "ItemSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "boardId": {
            "type": "string"
          },
          "boardName": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "group": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "title": {
                "type": "string"
              }
            }
          },
          "state": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "valuesByColumnId": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "updateCount": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "boardId",
          "boardName",
          "name",
          "fields",
          "valuesByColumnId",
          "updateCount"
        ]
      },
      "ItemDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ItemSummary"
          },
          {
            "type": "object",
            "properties": {
              "updates": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Update"
                }
              },
              "assets": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "subitems": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            },
            "required": [
              "updates",
              "assets",
              "subitems"
            ]
          }
        ]
      },
      "UpsertContactRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Contact display name."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Primary contact email. Used for duplicate detection when provided."
          },
          "companyName": {
            "type": "string",
            "description": "Company name or lookup text. Existing Companies rows are matched by name or domain text."
          },
          "companyDomain": {
            "type": "string",
            "description": "Optional company domain, such as thefamousgroup.com."
          },
          "fields": {
            "$ref": "#/components/schemas/FieldMap"
          },
          "companyFields": {
            "$ref": "#/components/schemas/FieldMap"
          },
          "note": {
            "type": "string",
            "description": "Optional contact update note."
          }
        },
        "required": [
          "name"
        ]
      },
      "UpsertContactResponse": {
        "type": "object",
        "properties": {
          "contact": {
            "$ref": "#/components/schemas/ItemDetail"
          },
          "company": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ItemSummary"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdContact": {
            "type": "boolean"
          },
          "createdCompany": {
            "type": "boolean"
          }
        },
        "required": [
          "contact",
          "company",
          "createdContact",
          "createdCompany"
        ]
      },
      "DuplicateCandidateResponse": {
        "type": "object",
        "properties": {
          "board": {
            "$ref": "#/components/schemas/BoardSummary"
          },
          "candidateGroups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactDuplicateGroup"
            }
          },
          "totalCandidateGroups": {
            "type": "integer"
          },
          "nameThreshold": {
            "type": "number"
          }
        },
        "required": [
          "board",
          "candidateGroups",
          "totalCandidateGroups",
          "nameThreshold"
        ]
      },
      "ContactDuplicateGroup": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "enum": [
              "shared_email",
              "shared_phone",
              "same_name",
              "similar_name"
            ]
          },
          "confidence": {
            "type": "string",
            "enum": [
              "high",
              "medium"
            ]
          },
          "similarity": {
            "type": "number",
            "description": "Present for similar-name matches."
          },
          "emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "phones": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "normalizedName": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ItemSummary"
            }
          }
        },
        "required": [
          "key",
          "reason",
          "confidence",
          "items"
        ]
      },
      "BoardItemDuplicateMergeRequest": {
        "type": "object",
        "properties": {
          "primaryItemId": {
            "type": "string",
            "description": "Primary board item ID to keep."
          },
          "duplicateItemIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "description": "Duplicate board item IDs to merge into the primary item."
          },
          "dryRun": {
            "type": "boolean",
            "default": false,
            "description": "Preview the merge without writing to Firebase."
          },
          "mergedCandidatesColumnTitle": {
            "type": "string",
            "default": "Merged candidate entities"
          }
        },
        "required": [
          "primaryItemId",
          "duplicateItemIds"
        ]
      },
      "BoardItemDuplicateMergeResponse": {
        "type": "object",
        "properties": {
          "dryRun": {
            "type": "boolean"
          },
          "board": {
            "$ref": "#/components/schemas/BoardSummary"
          },
          "primaryItem": {
            "$ref": "#/components/schemas/ItemDetail"
          },
          "mergedItemIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdColumns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Column"
            }
          },
          "mergedCandidatesColumn": {
            "$ref": "#/components/schemas/Column"
          },
          "plannedUpdateCount": {
            "type": "integer"
          },
          "auditUpdate": {
            "$ref": "#/components/schemas/Update"
          }
        },
        "required": [
          "dryRun",
          "board",
          "primaryItem",
          "mergedItemIds",
          "createdColumns",
          "mergedCandidatesColumn",
          "plannedUpdateCount",
          "auditUpdate"
        ]
      },
      "ContactDuplicateMergeRequest": {
        "type": "object",
        "properties": {
          "boardId": {
            "type": "string",
            "description": "Optional Contacts board ID. Defaults to the board named Contacts."
          },
          "primaryItemId": {
            "type": "string",
            "description": "Primary contact item ID to keep."
          },
          "duplicateItemIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "description": "Duplicate contact item IDs to merge into the primary contact."
          },
          "dryRun": {
            "type": "boolean",
            "default": false,
            "description": "Preview the merge without writing to Firebase."
          },
          "secondaryEmailsColumnTitle": {
            "type": "string",
            "default": "Secondary emails"
          },
          "mergedCandidatesColumnTitle": {
            "type": "string",
            "default": "Merged candidate entities"
          }
        },
        "required": [
          "primaryItemId",
          "duplicateItemIds"
        ]
      },
      "ContactDuplicateMergeResponse": {
        "type": "object",
        "properties": {
          "dryRun": {
            "type": "boolean"
          },
          "board": {
            "$ref": "#/components/schemas/BoardSummary"
          },
          "primaryItem": {
            "$ref": "#/components/schemas/ItemDetail"
          },
          "mergedItemIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "secondaryEmails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdColumns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Column"
            }
          },
          "mergedCandidatesColumn": {
            "$ref": "#/components/schemas/Column"
          },
          "plannedUpdateCount": {
            "type": "integer"
          },
          "auditUpdate": {
            "$ref": "#/components/schemas/Update"
          }
        },
        "required": [
          "dryRun",
          "board",
          "primaryItem",
          "mergedItemIds",
          "secondaryEmails",
          "createdColumns",
          "mergedCandidatesColumn",
          "plannedUpdateCount",
          "auditUpdate"
        ]
      },
      "Update": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "creator": {
            "type": [
              "object",
              "null"
            ]
          },
          "replies": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        },
        "required": [
          "id",
          "text"
        ]
      },
      "SetFileFieldRequest": {
        "type": "object",
        "properties": {
          "columnTitle": {
            "type": "string",
            "default": "Picture"
          },
          "columnId": {
            "type": "string"
          },
          "fileName": {
            "type": "string",
            "minLength": 1
          },
          "mimeType": {
            "type": "string",
            "default": "application/octet-stream"
          },
          "dataUrl": {
            "type": "string",
            "description": "Data URL for authenticated ONE-only storage."
          },
          "url": {
            "type": "string",
            "description": "External URL for linked file storage."
          },
          "thumbnailUrl": {
            "type": "string"
          },
          "thumbnailDataUrl": {
            "type": "string"
          },
          "fileSize": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "fileName"
        ]
      },
      "SetFileFieldResponse": {
        "type": "object",
        "properties": {
          "item": {
            "$ref": "#/components/schemas/ItemDetail"
          },
          "column": {
            "$ref": "#/components/schemas/Column"
          },
          "asset": {
            "type": "object"
          }
        },
        "required": [
          "item",
          "column",
          "asset"
        ]
      },
      "UpdateItemRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean",
                "null"
              ]
            },
            "description": "Field values keyed by column ID or column title."
          }
        },
        "minProperties": 1
      },
      "MoveItemsRequest": {
        "type": "object",
        "properties": {
          "itemIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Optional explicit item IDs to consider."
          },
          "query": {
            "type": "string",
            "description": "Optional text query that matching items must satisfy."
          },
          "fieldFilters": {
            "$ref": "#/components/schemas/FieldFilterMap"
          },
          "status": {
            "type": "string",
            "description": "Convenience shortcut for fieldFilters.Status."
          },
          "fromGroupId": {
            "type": "string",
            "description": "Optional source group ID, or active/archived virtual group."
          },
          "fromGroupTitle": {
            "type": "string",
            "description": "Optional source group title, or Active/Archived."
          },
          "toGroupId": {
            "type": "string",
            "description": "Target group ID. Use archived to move into the Archived section."
          },
          "toGroupTitle": {
            "type": "string",
            "description": "Target group title. Use Archived to move into the Archived section."
          },
          "dryRun": {
            "type": "boolean",
            "default": false,
            "description": "Preview matched items without writing changes."
          },
          "maxItems": {
            "type": "integer",
            "minimum": 1,
            "maximum": 500,
            "default": 500,
            "description": "Safety cap for moved items."
          },
          "reason": {
            "type": "string",
            "description": "Optional audit reason appended to each moved item update."
          }
        },
        "anyOf": [
          {
            "required": [
              "itemIds"
            ]
          },
          {
            "required": [
              "fieldFilters"
            ]
          },
          {
            "required": [
              "status"
            ]
          },
          {
            "required": [
              "fromGroupId"
            ]
          },
          {
            "required": [
              "fromGroupTitle"
            ]
          },
          {
            "required": [
              "query"
            ]
          }
        ],
        "example": {
          "toGroupTitle": "Archived",
          "fieldFilters": {
            "Status": "Done"
          },
          "dryRun": true
        }
      },
      "MoveItemsResponse": {
        "type": "object",
        "properties": {
          "dryRun": {
            "type": "boolean"
          },
          "board": {
            "$ref": "#/components/schemas/BoardSummary"
          },
          "targetGroup": {
            "$ref": "#/components/schemas/Group"
          },
          "sourceGroup": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Group"
              },
              {
                "type": "null"
              }
            ]
          },
          "fieldFilters": {
            "$ref": "#/components/schemas/FieldFilterMap"
          },
          "query": {
            "type": [
              "string",
              "null"
            ]
          },
          "maxItems": {
            "type": "integer"
          },
          "matchedItemCount": {
            "type": "integer"
          },
          "movedItemCount": {
            "type": "integer"
          },
          "skippedAlreadyInTargetCount": {
            "type": "integer"
          },
          "exceedsMaxItems": {
            "type": "boolean"
          },
          "movedItemIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "movedItems": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ItemSummary"
            }
          },
          "createdGroup": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Group"
              },
              {
                "type": "null"
              }
            ]
          },
          "plannedUpdateCount": {
            "type": "integer"
          },
          "auditNote": {
            "type": "string"
          }
        },
        "required": [
          "dryRun",
          "board",
          "targetGroup",
          "matchedItemCount",
          "movedItemCount",
          "movedItemIds",
          "movedItems"
        ]
      },
      "AddUpdateRequest": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1
          }
        },
        "required": [
          "text"
        ]
      },
      "SignatureRecipient": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "contactId": {
            "type": "string"
          },
          "personId": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "enum": [
              "sign",
              "receive_copy"
            ]
          },
          "routingOrder": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20
          },
          "roleName": {
            "type": "string"
          },
          "privateMessage": {
            "type": "string"
          },
          "canDecline": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "delivered",
              "signed",
              "declined"
            ]
          }
        },
        "required": [
          "name",
          "email"
        ]
      },
      "SignatureSettings": {
        "type": "object",
        "properties": {
          "routingMode": {
            "type": "string",
            "enum": [
              "parallel",
              "sequential"
            ]
          },
          "reminders": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "delayDays": {
                "type": "integer",
                "minimum": 1,
                "maximum": 30
              },
              "frequencyDays": {
                "type": "integer",
                "minimum": 1,
                "maximum": 30
              }
            }
          },
          "expiration": {
            "type": "object",
            "properties": {
              "days": {
                "type": "integer",
                "minimum": 1,
                "maximum": 365
              },
              "warningDays": {
                "type": "integer",
                "minimum": 0,
                "maximum": 30
              }
            }
          }
        }
      },
      "SignatureField": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "signature",
              "initials",
              "text",
              "name",
              "email",
              "title",
              "date_signed",
              "checkbox"
            ]
          },
          "signerId": {
            "type": "string"
          },
          "documentId": {
            "type": "string"
          },
          "page": {
            "type": "integer",
            "minimum": 1
          },
          "x": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "y": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "width": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1
          },
          "height": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1
          },
          "label": {
            "type": "string"
          },
          "required": {
            "type": "boolean"
          }
        },
        "required": [
          "type",
          "signerId",
          "documentId",
          "page",
          "x",
          "y",
          "width",
          "height"
        ]
      },
      "SignatureRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "fileName": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "delivered",
              "completed",
              "declined",
              "voided",
              "expired"
            ]
          },
          "documents": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "contentType": {
                  "type": "string"
                },
                "size": {
                  "type": "integer"
                },
                "pageCount": {
                  "type": "integer"
                }
              }
            }
          },
          "signers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignatureRecipient"
            }
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignatureField"
            }
          },
          "subject": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "settings": {
            "$ref": "#/components/schemas/SignatureSettings"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "sentAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time"
          },
          "history": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "documents",
          "signers",
          "history"
        ]
      },
      "SignatureRequestResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "request": {
            "$ref": "#/components/schemas/SignatureRequest"
          }
        },
        "required": [
          "ok",
          "request"
        ]
      },
      "SignatureRequestListResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "count": {
            "type": "integer"
          },
          "requests": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignatureRequest"
            }
          }
        },
        "required": [
          "ok",
          "count",
          "requests"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "details": {}
        },
        "required": [
          "error"
        ]
      },
      "CreateItemRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Optional explicit item ID. Omit to generate one."
          },
          "name": {
            "type": "string",
            "minLength": 1
          },
          "groupId": {
            "type": "string",
            "description": "Optional board group ID or title."
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean",
                "null"
              ]
            },
            "description": "Field values keyed by existing column ID or column title."
          },
          "note": {
            "type": "string",
            "description": "Optional initial update note."
          }
        },
        "required": [
          "name"
        ]
      },
      "DeleteItemRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "description": "Optional audit reason stored as an item update."
          }
        }
      }
    }
  }
}
