{
  "openapi": "3.1.0",
  "info": {
    "title": "Frihet API",
    "version": "2026-03-18",
    "description": "The Frihet REST API provides programmatic access to your business data\nin Frihet, the AI-native business management platform for modern businesses.\n\n## Authentication\n\nAll API requests require an API key passed via the `X-API-Key` header.\nGenerate keys from **Settings > API Keys** in the Frihet app.\n\n```\nX-API-Key: fri_your_api_key_here\n```\n\n## Rate Limiting\n\nThe API enforces a rate limit of **100 requests per minute** per API key.\nWhen exceeded, the API returns `429 Too Many Requests` with an integer\ndelta-seconds `Retry-After` header and matching `retryAfter` JSON field.\n\nEvery authenticated response includes rate limit headers:\n\n| Header | Description |\n|--------|-------------|\n| `X-RateLimit-Limit` | Maximum requests per window (100) |\n| `X-RateLimit-Remaining` | Remaining requests in current window |\n| `X-RateLimit-Reset` | Unix timestamp (seconds) when the window resets |\n| `Retry-After` | Integer seconds until the window resets (429 responses) |\n\n## Idempotency\n\nMost POST requests accept an `Idempotency-Key` header to prevent duplicate\nresource creation from network retries. For protected routes, the key is\nreserved against the operation *before* the request is executed, and it stays\nreserved for 24 hours.\n\n```\nIdempotency-Key: 550e8400-e29b-41d4-a716-446655440000\n```\n\nKeys must be at most 64 characters. We recommend UUID v4.\n\nA retry of the same operation gets one of three answers:\n\n| Situation | Response |\n|---|---|\n| The first request finished | The **original status and body**, verbatim, with `X-Idempotent-Replayed: true` |\n| The first request is still running, or its outcome could not be recorded | `409 IDEMPOTENCY_REQUEST_IN_PROGRESS` |\n| The key is live but was used for a **different** operation | `409 IDEMPOTENCY_KEY_REUSED` — the second operation is not executed |\n\nThe replayed status is the **stored** one. A `201` replays as `201`, not `200`.\nThis includes error responses: a request that answered `4xx` or `5xx` replays\nthat same status and body, it does not re-execute.\n\n### When a retry is refused\n\nA `409 IDEMPOTENCY_REQUEST_IN_PROGRESS` after the first request already\nreturned means the outcome could not be recorded — the response was too large\nor unserializable, or the bookkeeping write failed. The API refuses rather than\nexecute a second time, because for a fiscal document a duplicate is not\nrecoverable.\n\n**Reconcile; do not simply retry with a fresh key.** Read the resource back\n(`GET` the collection, or look for the document the operation would have\ncreated) and decide from its actual state. A new key would happily create a\nsecond document, which is exactly what the first key existed to prevent.\n\nAuthentication, rate limiting, and the maximum-request-size check happen before\nthe idempotency gate. Their `401`, `429`, and `413` responses do not reserve a\nkey and are never idempotency replays. Fiscal-send scope authorization also\nhappens before replay for `einvoice/export`, `face/submit`, and\n`ticketbai/submit`; a current `403` does not reserve or alter the key. Authorized\nfiscal sends remain idempotency-protected.\n\nThe current-state report `POST /v1/gestoria/aging` deliberately bypasses the idempotency gate. It ignores `Idempotency-Key` and always re-evaluates current workspace membership.\n\nIdentity note: for every idempotency-protected `POST`, the complete received\nJSON request body and parsed query parameters are part of the operation\nidentity. Object-key order does not matter, including in nested objects, but\narray order does. The credit-note endpoint applies its documented defaults\nfirst, so omitting `fullCredit` is equivalent to sending `fullCredit: true`.\nReusing the same key and path with a different body or query returns\n`409 IDEMPOTENCY_KEY_REUSED` without executing the request.\n\n## API Versioning\n\nEvery response includes an `X-API-Version` header (e.g., `2026-03-18`)\nindicating the current API version. Monitor this header to detect changes.\n\n## Pagination\n\nList endpoints support two pagination strategies:\n\n**Offset pagination:** Use `limit` and `offset` query parameters.\nDefault limit is 50, maximum is 100.\n\n**Cursor pagination:** Use `cursor` and `limit` query parameters.\nPass the `nextCursor` value from a previous response to fetch the next page.\nCursor pagination is more efficient for large datasets and avoids skipped/duplicated\nitems when data changes between requests. When `cursor` is provided, `offset` is ignored.\n\nResponses include `total`, `limit`, `offset`, and optionally `nextCursor`.\n\n## Filtering\n\nList endpoints support filtering by `status` (where applicable), date\nranges using `from` and `to` query parameters in ISO 8601 format (`YYYY-MM-DD`),\nand resource-specific filters like `clientId`, `seriesId`, `vendorId`,\n`category`, `isActive`, and `stage`.\n\n## Field Selection\n\nAll GET endpoints support a `fields` query parameter to select specific fields.\nPass a comma-separated list of field names: `?fields=id,name,total`.\nThe `id` field is always included.\n\n## Errors\n\nAll errors follow a consistent format:\n\n```json\n{\n  \"error\": \"Human-readable error message\",\n  \"details\": [],\n  \"meta\": { \"requestId\": \"uuid\" }\n}\n```\n\nThe `details` array is only present on validation errors (Zod issues).\nThe `meta.requestId` is included on every error response for tracing.\n\n## Resources\n\n- [Documentation](https://docs.frihet.io/desarrolladores/api-rest)\n- [MCP Server](https://mcp.frihet.io)\n- [Status](https://status.frihet.io)\n",
    "contact": {
      "name": "Frihet API Support",
      "url": "https://docs.frihet.io/desarrolladores/api-rest",
      "email": "api@frihet.io"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    },
    "x-logo": {
      "url": "https://frihet.io/logos/frihet-logo.svg"
    }
  },
  "servers": [
    {
      "url": "https://api.frihet.io",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Invoices",
      "description": "Create, read, update, and delete invoices. Includes PDF generation, email sending, and payment tracking."
    },
    {
      "name": "Expenses",
      "description": "Manage business expenses with categories, tax deductions, and receipt attachments."
    },
    {
      "name": "Clients",
      "description": "Manage your client database with contact details, tax IDs, and addresses."
    },
    {
      "name": "Products",
      "description": "Product and service catalog with pricing, tax rates, and SKUs."
    },
    {
      "name": "Quotes",
      "description": "Create and manage quotes with PDF generation and email delivery. Convert accepted quotes to invoices."
    },
    {
      "name": "Vendors",
      "description": "Manage your vendor database with contact details, tax IDs, and addresses."
    },
    {
      "name": "Summary",
      "description": "Financial dashboard data including revenue, expenses, and profit aggregations."
    },
    {
      "name": "Intelligence",
      "description": "AI-optimized endpoints providing business context, monthly P&L, and quarterly tax figures in a single call."
    },
    {
      "name": "Search",
      "description": "Read-only global search across Frihet business records."
    },
    {
      "name": "Banking",
      "description": "Bank accounts, transactions, categorization, and read-only reconciliation helpers."
    },
    {
      "name": "Webhooks",
      "description": "CRUD endpoints for managing webhook subscriptions that receive real-time event notifications."
    },
    {
      "name": "Contacts",
      "description": "Contact persons associated with a client. Manage multiple contacts per client for CRM."
    },
    {
      "name": "Activities",
      "description": "Immutable activity timeline for a client. System activities are auto-generated; manual activities can be created via API."
    },
    {
      "name": "Notes",
      "description": "Free-form notes attached to a client."
    },
    {
      "name": "Deposits",
      "description": "Manage client deposits and prepayments. Apply deposits against invoices or process refunds."
    },
    {
      "name": "Batch",
      "description": "Batch creation of resources (up to 50 items per request)."
    },
    {
      "name": "Reservations",
      "description": "Frihet Stay: Manage vacation rental reservations with guest info, dates, and compliance tracking."
    },
    {
      "name": "Properties",
      "description": "Frihet Stay: Manage rental properties with owner info, capacity, and licensing."
    },
    {
      "name": "Guests",
      "description": "Frihet Stay: Read compliance records for guest check-in (document verification, police reports)."
    },
    {
      "name": "Channels",
      "description": "Frihet Stay: Manage channel connections (iCal feeds) for calendar sync with OTAs."
    },
    {
      "name": "E-Invoicing",
      "description": "EN16931-compliant e-invoicing export and B2G submission endpoints.\nSupports 9 formats (Facturae, XRechnung-CII/UBL, Factur-X, FatturaPA, PEPPOL-BIS-3, FA-2-KSeF, UBL, CII).\nFACe (Spain B2G portal) and TicketBAI (Bizkaia BATUZ) submission pipelines.\nRequires owner role or API key with `einvoice:*` scope.\n"
    }
  ],
  "paths": {
    "/v1/search/global": {
      "get": {
        "operationId": "globalSearch",
        "summary": "Global search",
        "description": "Read-only bounded search across invoices, expenses, vendors, clients, and products. Uses existing workspace-scoped search paths and does not mutate data.",
        "tags": [
          "Search"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search query, maximum 200 characters.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "types",
            "in": "query",
            "description": "Optional comma-separated resource types to search.",
            "schema": {
              "type": "string",
              "example": "invoices,expenses,clients"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "Typed global search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/GlobalSearchResult"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "query": {
                      "type": "string"
                    },
                    "types": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "truncated": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/gestoria/aging": {
      "post": {
        "operationId": "gestoriaAgingConsolidated",
        "summary": "Consolidated AR aging across client workspaces",
        "description": "For an accountant/gestor, returns accounts-receivable aging consolidated across the given client workspaces. Each workspaceId is authorised per-workspace: the caller must be an active accountant or owner member of that workspace, otherwise it is silently rejected and listed in rejectedWorkspaceIds (no data returned for it). This current-state report deliberately ignores Idempotency-Key and re-checks membership on every request; an old completed marker is never replayed.\n",
        "tags": [
          "Gestoria"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "workspaceIds"
                ],
                "properties": {
                  "workspaceIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 200,
                    "description": "Client workspace UIDs the caller manages."
                  },
                  "asOf": {
                    "type": "string",
                    "format": "date",
                    "description": "Reference date (YYYY-MM-DD). Defaults to today."
                  },
                  "bustCache": {
                    "type": "boolean",
                    "description": "Honoured only by the callable CF; REST always computes fresh."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consolidated aging report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "workspaces": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "workspaceId": {
                                "type": "string"
                              },
                              "ownerName": {
                                "type": "string"
                              },
                              "buckets": {
                                "type": "object",
                                "properties": {
                                  "current": {
                                    "type": "number"
                                  },
                                  "days1_30": {
                                    "type": "number"
                                  },
                                  "days31_60": {
                                    "type": "number"
                                  },
                                  "days61_90": {
                                    "type": "number"
                                  },
                                  "days90plus": {
                                    "type": "number"
                                  }
                                }
                              },
                              "grandTotal": {
                                "type": "number"
                              },
                              "totalOverdue": {
                                "type": "number"
                              },
                              "asOf": {
                                "type": "string"
                              },
                              "currency": {
                                "type": "string"
                              },
                              "topDebtors": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "clientId": {
                                      "type": "string"
                                    },
                                    "clientName": {
                                      "type": "string"
                                    },
                                    "totalOutstanding": {
                                      "type": "number"
                                    },
                                    "daysOldestInvoice": {
                                      "type": "number"
                                    },
                                    "invoiceCount": {
                                      "type": "number"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        },
                        "rejectedWorkspaceIds": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Workspaces the caller is not an authorised member of."
                        },
                        "consolidatedBuckets": {
                          "type": "object",
                          "properties": {
                            "current": {
                              "type": "number"
                            },
                            "days1_30": {
                              "type": "number"
                            },
                            "days31_60": {
                              "type": "number"
                            },
                            "days61_90": {
                              "type": "number"
                            },
                            "days90plus": {
                              "type": "number"
                            }
                          }
                        },
                        "consolidatedTotal": {
                          "type": "number"
                        },
                        "consolidatedOverdue": {
                          "type": "number"
                        },
                        "asOf": {
                          "type": "string"
                        },
                        "generatedAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/invoices": {
      "get": {
        "operationId": "listInvoices",
        "summary": "List invoices",
        "description": "Retrieve a paginated list of invoices with optional filters for status, date range, and search.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by invoice status",
            "schema": {
              "$ref": "#/components/schemas/InvoiceStatus"
            }
          },
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/FilterClientId"
          },
          {
            "$ref": "#/components/parameters/FilterSeriesId"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of invoices",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Invoice"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createInvoice",
        "summary": "Create an invoice",
        "description": "Create a new invoice. The invoice is created in `draft` status by default.",
        "tags": [
          "Invoices"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invoice created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}": {
      "get": {
        "operationId": "getInvoice",
        "summary": "Get an invoice",
        "description": "Retrieve a single invoice by ID.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "updateInvoice",
        "summary": "Full update an invoice",
        "description": "Replace all fields on an existing invoice. All required fields must be provided.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "patchInvoice",
        "summary": "Partial update an invoice",
        "description": "Update only the provided fields on an existing invoice. All fields are optional.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteInvoice",
        "summary": "Delete an invoice",
        "description": "Delete an invoice. Draft invoices are permanently deleted (204).\nNon-draft invoices are soft-deleted (cancelled) to preserve VeriFactu\nSHA-256 hash chain integrity (RD 1007/2023 compliance), returning 200\nwith the cancelled document body.\n",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice soft-deleted (cancelled). Returned for non-draft invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "example": "cancelled"
                        },
                        "cancelledVia": {
                          "type": "string",
                          "example": "api"
                        }
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "requestId": {
                          "type": "string"
                        },
                        "timestamp": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "204": {
            "description": "Invoice permanently deleted (draft invoices only)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/pdf": {
      "get": {
        "operationId": "getInvoicePdf",
        "summary": "Download invoice PDF",
        "description": "Generate and download the invoice as a PDF document.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "PDF document",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/send": {
      "post": {
        "operationId": "sendInvoice",
        "summary": "Send invoice by email",
        "description": "Send the invoice to a recipient via email. If the invoice is in `draft` status, it will be automatically changed to `sent`.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendEmailRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "messageId": {
                      "type": "string",
                      "description": "Email delivery message ID"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Fiscal profile not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/paid": {
      "post": {
        "operationId": "markInvoicePaid",
        "summary": "Mark invoice as paid",
        "description": "Mark an invoice as paid. Optionally specify the payment date.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "paidDate": {
                    "type": "string",
                    "format": "date",
                    "description": "Payment date in ISO format. Defaults to today.",
                    "example": "2026-03-09"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice marked as paid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "status": {
                      "type": "string",
                      "example": "paid"
                    },
                    "paidAt": {
                      "type": "string",
                      "format": "date"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses": {
      "get": {
        "operationId": "listExpenses",
        "summary": "List expenses",
        "description": "Retrieve a paginated list of expenses with optional date range and search filters.",
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/FilterVendorId"
          },
          {
            "$ref": "#/components/parameters/FilterCategory"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of expenses",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Expense"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createExpense",
        "summary": "Create an expense",
        "description": "Record a new business expense.",
        "tags": [
          "Expenses"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExpenseCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Expense created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Expense"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses/attachments/uploads": {
      "post": {
        "operationId": "createExpenseAttachmentUpload",
        "summary": "Prepare an expense attachment upload reference",
        "description": "Prepare a metadata-only attachment reference for an expense receipt.\nThis endpoint does not accept raw bytes, local file paths, signed URLs,\nor secrets. It returns an opaque fileReferenceId plus limits that a full\nMCP/API client can use before linking the uploaded file to an expense.\n",
        "tags": [
          "Expenses"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileAttachmentUploadCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Attachment upload reference prepared",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FileAttachmentUpload"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses/{expenseId}": {
      "get": {
        "operationId": "getExpense",
        "summary": "Get an expense",
        "description": "Retrieve a single expense by ID.",
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ExpenseId"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Expense details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Expense"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "updateExpense",
        "summary": "Full update an expense",
        "description": "Replace all fields on an existing expense record.",
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ExpenseId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExpenseCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Expense updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Expense"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "patchExpense",
        "summary": "Partial update an expense",
        "description": "Update only the provided fields on an existing expense record.",
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ExpenseId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExpenseCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Expense updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Expense"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteExpense",
        "summary": "Delete an expense",
        "description": "Permanently delete an expense record.",
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ExpenseId"
          }
        ],
        "responses": {
          "204": {
            "description": "Expense deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses/{expenseId}/attachments": {
      "post": {
        "operationId": "attachExpenseFileReference",
        "summary": "Attach a file reference to an expense",
        "description": "Appends an existing safe file reference to an expense. The request\naccepts metadata only: no raw bytes, local paths, signed URLs, or\nsecrets. MIME type and size are validated server-side.\n",
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ExpenseId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileAttachmentInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Attachment reference appended",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "expenseId": {
                          "type": "string"
                        },
                        "attachment": {
                          "$ref": "#/components/schemas/FileAttachment"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/clients": {
      "get": {
        "operationId": "listClients",
        "summary": "List clients",
        "description": "Retrieve a paginated list of clients with optional search.",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/FilterStage"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of clients",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Client"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createClient",
        "summary": "Create a client",
        "description": "Add a new client to your database.\n\nIf `taxId` is provided and another client in your workspace already has the\nsame taxId (normalized: case-insensitive, ignoring separators like dots and\nhyphens), the request is rejected with `409 CLIENT_TAXID_EXISTS` and the\nexisting client's id is returned in `existingClientId`.\n",
        "tags": [
          "Clients"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "A client with this taxId already exists in your workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "CLIENT_TAXID_EXISTS"
                    },
                    "message": {
                      "type": "string",
                      "example": "Client with this taxId already exists"
                    },
                    "existingClientId": {
                      "type": "string",
                      "description": "Document id of the existing client with the same taxId."
                    },
                    "taxId": {
                      "type": "string",
                      "description": "GDPR-masked form of the conflicting taxId (e.g. \"43******K\")."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/clients/{clientId}": {
      "get": {
        "operationId": "getClient",
        "summary": "Get a client",
        "description": "Retrieve a single client by ID.",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Client details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "updateClient",
        "summary": "Full update a client",
        "description": "Replace all fields on an existing client. Returns `409 CLIENT_TAXID_EXISTS`\nif the new `taxId` is already in use by another client in your workspace.\n",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Client updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Another client with this taxId already exists in your workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "CLIENT_TAXID_EXISTS"
                    },
                    "existingClientId": {
                      "type": "string"
                    },
                    "taxId": {
                      "type": "string",
                      "description": "GDPR-masked form of the conflicting taxId."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "patchClient",
        "summary": "Partial update a client",
        "description": "Update only the provided fields on an existing client. Returns\n`409 CLIENT_TAXID_EXISTS` if the new `taxId` is already in use by\nanother client in your workspace.\n",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Client updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Another client with this taxId already exists in your workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "CLIENT_TAXID_EXISTS"
                    },
                    "existingClientId": {
                      "type": "string"
                    },
                    "taxId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteClient",
        "summary": "Delete a client",
        "description": "Permanently delete a client from your database.",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          }
        ],
        "responses": {
          "204": {
            "description": "Client deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/clients/{clientId}/contacts": {
      "get": {
        "operationId": "listClientContacts",
        "summary": "List client contacts",
        "description": "Retrieve all contact persons for a client.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          }
        ],
        "responses": {
          "200": {
            "description": "List of contacts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createClientContact",
        "summary": "Create a client contact",
        "description": "Add a new contact person to a client.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contact created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/clients/{clientId}/contacts/{contactId}": {
      "get": {
        "operationId": "getClientContact",
        "summary": "Get a client contact",
        "description": "Retrieve a single contact person by ID.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "200": {
            "description": "Contact details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateClientContact",
        "summary": "Update a client contact",
        "description": "Update fields on an existing contact person.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contact updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteClientContact",
        "summary": "Delete a client contact",
        "description": "Permanently delete a contact person from a client.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "204": {
            "description": "Contact deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/clients/{clientId}/activities": {
      "get": {
        "operationId": "listClientActivities",
        "summary": "List client activities",
        "description": "Retrieve the activity timeline for a client.",
        "tags": [
          "Activities"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          }
        ],
        "responses": {
          "200": {
            "description": "List of activities",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Activity"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createClientActivity",
        "summary": "Create a client activity",
        "description": "Add a manual activity to the client timeline. Activities are immutable once created.",
        "tags": [
          "Activities"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActivityCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Activity created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/clients/{clientId}/activities/{activityId}": {
      "get": {
        "operationId": "getClientActivity",
        "summary": "Get a client activity",
        "description": "Retrieve a single activity by ID.",
        "tags": [
          "Activities"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          },
          {
            "$ref": "#/components/parameters/ActivityId"
          }
        ],
        "responses": {
          "200": {
            "description": "Activity details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/clients/{clientId}/notes": {
      "get": {
        "operationId": "listClientNotes",
        "summary": "List client notes",
        "description": "Retrieve all notes for a client.",
        "tags": [
          "Notes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          }
        ],
        "responses": {
          "200": {
            "description": "List of notes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Note"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createClientNote",
        "summary": "Create a client note",
        "description": "Add a new note to a client.",
        "tags": [
          "Notes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NoteCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Note created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/clients/{clientId}/notes/{noteId}": {
      "get": {
        "operationId": "getClientNote",
        "summary": "Get a client note",
        "description": "Retrieve a single note by ID.",
        "tags": [
          "Notes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          },
          {
            "$ref": "#/components/parameters/NoteId"
          }
        ],
        "responses": {
          "200": {
            "description": "Note details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateClientNote",
        "summary": "Update a client note",
        "description": "Update the content of an existing note.",
        "tags": [
          "Notes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          },
          {
            "$ref": "#/components/parameters/NoteId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NoteCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Note updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteClientNote",
        "summary": "Delete a client note",
        "description": "Permanently delete a note from a client.",
        "tags": [
          "Notes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientId"
          },
          {
            "$ref": "#/components/parameters/NoteId"
          }
        ],
        "responses": {
          "204": {
            "description": "Note deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/products": {
      "get": {
        "operationId": "listProducts",
        "summary": "List products",
        "description": "Retrieve a paginated list of products and services with optional search.",
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/FilterIsActive"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of products",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Product"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createProduct",
        "summary": "Create a product",
        "description": "Add a new product or service to your catalog.",
        "tags": [
          "Products"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProductCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Product created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/products/{productId}": {
      "get": {
        "operationId": "getProduct",
        "summary": "Get a product",
        "description": "Retrieve a single product by ID.",
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ProductId"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Product details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "updateProduct",
        "summary": "Full update a product",
        "description": "Replace all fields on an existing product or service.",
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ProductId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProductCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Product updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "patchProduct",
        "summary": "Partial update a product",
        "description": "Update only the provided fields on an existing product or service.",
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ProductId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProductCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Product updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteProduct",
        "summary": "Delete a product",
        "description": "Permanently delete a product from your catalog.",
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ProductId"
          }
        ],
        "responses": {
          "204": {
            "description": "Product deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes": {
      "get": {
        "operationId": "listQuotes",
        "summary": "List quotes",
        "description": "Retrieve a paginated list of quotes with optional filters for status, date range, and search.",
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by quote status",
            "schema": {
              "$ref": "#/components/schemas/QuoteStatus"
            }
          },
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/FilterClientId"
          },
          {
            "$ref": "#/components/parameters/FilterSeriesId"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of quotes",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Quote"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createQuote",
        "summary": "Create a quote",
        "description": "Create a new quote. The quote is created in `draft` status by default.",
        "tags": [
          "Quotes"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Quote created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/{quoteId}": {
      "get": {
        "operationId": "getQuote",
        "summary": "Get a quote",
        "description": "Retrieve a single quote by ID.",
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/QuoteId"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Quote details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "updateQuote",
        "summary": "Full update a quote",
        "description": "Replace all fields on an existing quote.",
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/QuoteId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "patchQuote",
        "summary": "Partial update a quote",
        "description": "Update only the provided fields on an existing quote.",
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/QuoteId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteQuote",
        "summary": "Delete a quote",
        "description": "Delete a quote. Draft quotes are permanently deleted (204).\nNon-draft quotes are soft-deleted (cancelled) to preserve VeriFactu\nhash chain integrity, returning 200 with the cancelled document body.\n",
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/QuoteId"
          }
        ],
        "responses": {
          "200": {
            "description": "Quote soft-deleted (cancelled). Returned for non-draft quotes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "example": "cancelled"
                        },
                        "cancelledVia": {
                          "type": "string",
                          "example": "api"
                        }
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "requestId": {
                          "type": "string"
                        },
                        "timestamp": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "204": {
            "description": "Quote permanently deleted (draft quotes only)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/{quoteId}/pdf": {
      "get": {
        "operationId": "getQuotePdf",
        "summary": "Download quote PDF",
        "description": "Generate and download the quote as a PDF document.",
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/QuoteId"
          }
        ],
        "responses": {
          "200": {
            "description": "PDF document",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/{quoteId}/send": {
      "post": {
        "operationId": "sendQuote",
        "summary": "Send quote by email",
        "description": "Send the quote to a recipient via email. If the quote is in `draft` status, it will be automatically changed to `sent`.",
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/QuoteId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendEmailRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "messageId": {
                      "type": "string",
                      "description": "Email delivery message ID"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Fiscal profile not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/summary": {
      "get": {
        "operationId": "getSummary",
        "summary": "Financial summary",
        "description": "Get an aggregated financial summary including revenue, expenses, profit,\nentity counts, and invoice status breakdown. Supports optional date range filtering.\n",
        "tags": [
          "Summary"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          }
        ],
        "responses": {
          "200": {
            "description": "Financial summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Summary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vendors": {
      "get": {
        "operationId": "listVendors",
        "summary": "List vendors",
        "description": "Retrieve a paginated list of vendors.",
        "tags": [
          "Vendors"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of vendors",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Vendor"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createVendor",
        "summary": "Create a vendor",
        "description": "Add a new vendor to your database.",
        "tags": [
          "Vendors"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VendorCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Vendor created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vendor"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vendors/{vendorId}": {
      "get": {
        "operationId": "getVendor",
        "summary": "Get a vendor",
        "description": "Retrieve a single vendor by ID.",
        "tags": [
          "Vendors"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/VendorId"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Vendor details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vendor"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "updateVendor",
        "summary": "Full update a vendor",
        "description": "Replace all fields on an existing vendor.",
        "tags": [
          "Vendors"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/VendorId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VendorCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vendor updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vendor"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "patchVendor",
        "summary": "Partial update a vendor",
        "description": "Update only the provided fields on an existing vendor.",
        "tags": [
          "Vendors"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/VendorId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VendorCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vendor updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vendor"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteVendor",
        "summary": "Delete a vendor",
        "description": "Permanently delete a vendor from your database.",
        "tags": [
          "Vendors"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/VendorId"
          }
        ],
        "responses": {
          "204": {
            "description": "Vendor deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/context": {
      "get": {
        "operationId": "getBusinessContext",
        "summary": "Business context snapshot",
        "description": "Returns a comprehensive business context snapshot in a single call,\noptimized for AI agents and MCP integrations. Includes business info,\ndefaults, plan usage, invoice series, recent activity, top clients,\nand current month financials.\n",
        "tags": [
          "Intelligence"
        ],
        "responses": {
          "200": {
            "description": "Business context data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BusinessContext"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/monthly": {
      "get": {
        "operationId": "getMonthlySummary",
        "summary": "Monthly P&L",
        "description": "Monthly financial summary with revenue breakdown (tax base, tax, IRPF),\nexpense breakdown (deductible, tax), profit (gross/net), invoice status\ncounts, top clients, expenses by category, and estimated tax liability\n(Modelo 303).\n",
        "tags": [
          "Intelligence"
        ],
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "description": "Month in YYYY-MM format. Defaults to current month.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}$",
              "example": "2026-03"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Monthly financial summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MonthlySummary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quarterly": {
      "get": {
        "operationId": "getQuarterlySummary",
        "summary": "Quarterly tax figures",
        "description": "Quarterly tax preparation data including Modelo 303 (VAT) and\nModelo 130 (quarterly income payment) pre-filled figures.\nIncludes base imponible, cuotas, and estimated payments.\n",
        "tags": [
          "Intelligence"
        ],
        "parameters": [
          {
            "name": "quarter",
            "in": "query",
            "description": "Quarter in YYYY-Q1 format. Defaults to current quarter.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-Q[1-4]$",
              "example": "2026-Q1"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Quarterly tax summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuarterlySummary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhooks",
        "description": "Retrieve all webhook subscriptions for the authenticated user.",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "List of webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Create a webhook",
        "description": "Register a new webhook subscription to receive event notifications.",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/webhooks/{webhookId}": {
      "get": {
        "operationId": "getWebhook",
        "summary": "Get a webhook",
        "description": "Retrieve a single webhook subscription by ID.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "updateWebhook",
        "summary": "Full update a webhook",
        "description": "Replace all fields on an existing webhook subscription.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "patchWebhook",
        "summary": "Partial update a webhook",
        "description": "Update only the provided fields on an existing webhook subscription.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook",
        "description": "Permanently delete a webhook subscription.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "responses": {
          "204": {
            "description": "Webhook deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/{resource}/batch": {
      "post": {
        "operationId": "batchCreate",
        "summary": "Batch create resources",
        "description": "Create multiple resources in a single request. The request body must be\nan array of items (max 50). Each item is validated independently.\nReturns 201 if all succeed, 207 if partially successful, 400 if all fail.\n",
        "tags": [
          "Batch"
        ],
        "parameters": [
          {
            "name": "resource",
            "in": "path",
            "required": true,
            "description": "Resource type to batch create",
            "schema": {
              "type": "string",
              "enum": [
                "invoices",
                "expenses",
                "clients",
                "vendors",
                "products",
                "quotes"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "object"
                },
                "minItems": 1,
                "maxItems": 50
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "All items created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "207": {
            "description": "Partial success (some items failed validation)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/deposits": {
      "get": {
        "operationId": "listDeposits",
        "summary": "List deposits",
        "description": "Retrieve a paginated list of deposits with optional date range, client, and search filters.",
        "tags": [
          "Deposits"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/FilterClientId"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of deposits",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Deposit"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createDeposit",
        "summary": "Create a deposit",
        "description": "Record a new client deposit or prepayment.",
        "tags": [
          "Deposits"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DepositCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Deposit created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/deposits/{depositId}": {
      "get": {
        "operationId": "getDeposit",
        "summary": "Get a deposit",
        "description": "Retrieve a single deposit by ID.",
        "tags": [
          "Deposits"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DepositId"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Deposit details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "updateDeposit",
        "summary": "Full update a deposit",
        "description": "Replace all fields on an existing deposit.",
        "tags": [
          "Deposits"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DepositId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DepositCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deposit updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "patchDeposit",
        "summary": "Partial update a deposit",
        "description": "Update only the provided fields on an existing deposit.",
        "tags": [
          "Deposits"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DepositId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DepositCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deposit updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteDeposit",
        "summary": "Delete a deposit",
        "description": "Permanently delete a deposit record.",
        "tags": [
          "Deposits"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DepositId"
          }
        ],
        "responses": {
          "204": {
            "description": "Deposit deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/deposits/{depositId}/apply": {
      "post": {
        "operationId": "applyDeposit",
        "summary": "Apply deposit to an invoice",
        "description": "Apply all or part of a deposit's remaining balance to a specific invoice.\nReturns an error if the requested amount exceeds the remaining balance or\nif the deposit has already been applied to the given invoice.\n",
        "tags": [
          "Deposits"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DepositId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "invoiceId",
                  "invoiceNumber",
                  "amount"
                ],
                "properties": {
                  "invoiceId": {
                    "type": "string",
                    "description": "ID of the invoice to apply the deposit to",
                    "example": "abc123def456"
                  },
                  "invoiceNumber": {
                    "type": "string",
                    "description": "Invoice number (for audit trail)",
                    "example": "FAC-2026-0042"
                  },
                  "amount": {
                    "type": "number",
                    "description": "Amount to apply (must be positive and not exceed remaining balance)",
                    "example": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deposit applied successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "depositId": {
                      "type": "string",
                      "example": "dep_abc123"
                    },
                    "appliedAmount": {
                      "type": "number",
                      "example": 500
                    },
                    "remainingBalance": {
                      "type": "number",
                      "example": 250
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "partially_applied",
                        "fully_applied"
                      ],
                      "example": "partially_applied"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/deposits/{depositId}/refund": {
      "post": {
        "operationId": "refundDeposit",
        "summary": "Refund a deposit",
        "description": "Refund all or part of a deposit's remaining balance to the client.\nIf `amount` is omitted, the full remaining balance is refunded.\nReturns an error if the deposit is already fully refunded or if the\nrequested amount exceeds the remaining balance.\n",
        "tags": [
          "Deposits"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DepositId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "number",
                    "description": "Amount to refund. Omit to refund the full remaining balance.",
                    "example": 250
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deposit refunded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "depositId": {
                      "type": "string",
                      "example": "dep_abc123"
                    },
                    "refundedAmount": {
                      "type": "number",
                      "example": 250
                    },
                    "remainingBalance": {
                      "type": "number",
                      "example": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/xml": {
      "get": {
        "operationId": "getInvoiceXml",
        "summary": "Download e-invoice XML",
        "description": "Download the Facturae 3.2.2 XML for an invoice. The XML is generated\nwhen the invoice is saved or sent. Returns 404 if the e-invoice XML\nhas not yet been generated for this invoice.\n",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "E-invoice XML document",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/credit-note": {
      "post": {
        "operationId": "createCreditNote",
        "summary": "Create a credit note draft",
        "description": "Create a rectificative invoice (credit note) as a **draft**, by differences\n(`TipoRectificativa = I`), for the full amount of an existing invoice.\n\nThe endpoint never issues. The draft carries no fiscal number, no hash and\nno VeriFactu submission: those happen when a person issues it in the app.\nA previous version created the document with `sent` status — that behaviour\nis gone.\n\nRequirements:\n  * Plan `pro` or above. Otherwise `403 PLAN_UPGRADE_REQUIRED`.\n  * An `Idempotency-Key` header. Otherwise `400 IDEMPOTENCY_KEY_REQUIRED`.\n  * The original invoice must not be `draft`, `cancelled`, or already\n    fully credited.\n\nErrors carry a stable machine-readable `code` alongside `error`.\n",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Unique key identifying this credit-note operation. REQUIRED here (unlike\nother POST endpoints, where it is optional): a credit note is a fiscal\ndocument, so a retry must replay rather than create a second one.\n\nThe key is reserved against the operation — method, path AND request\nbody — before the handler runs, and the draft plus the stored response\nare written in a single transaction. Two concurrent calls with the same\nkey therefore produce exactly one draft.\n\nA retry receives the **stored `201`** with the same `id` and the header\n`X-Idempotent-Replayed: true`. It is not downgraded to `200`. The same\nkey sent with a DIFFERENT body is a different operation and is refused\nwith `409 IDEMPOTENCY_KEY_REUSED`; the same key on a different endpoint\nis refused the same way, without executing.\n",
            "schema": {
              "type": "string",
              "maxLength": 64,
              "example": "550e8400-e29b-41d4-a716-446655440000"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "enum": [
                      "refund",
                      "discount",
                      "error",
                      "cancellation",
                      "other"
                    ],
                    "description": "Reason for the credit note. Maps to the R-type (R1 for `error`, R4 otherwise).",
                    "example": "error"
                  },
                  "reasonDescription": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Optional free-text description of the reason",
                    "example": "Incorrect unit price on line 2"
                  },
                  "fullCredit": {
                    "type": "boolean",
                    "default": true,
                    "description": "Only `true` (or omitted) is supported: a full credit by differences.\nSending `false` returns `400 PARTIAL_CREDIT_NOT_IMPLEMENTED` — partial\ncorrections need the corrected lines, which this endpoint does not accept.\nCreate those in the app.\n",
                    "example": true
                  },
                  "issueDate": {
                    "type": "string",
                    "format": "date",
                    "description": "Issue date for the credit note. Defaults to today.",
                    "example": "2026-04-07"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Credit note draft created — and the status a REPLAY returns too. A retry\nwith the same Idempotency-Key receives this same `201`, the same `id` and\nthe same body, plus `X-Idempotent-Replayed: true`; no second document is\nwritten. (Earlier versions replayed as `200`.)\n",
            "headers": {
              "X-Idempotent-Replayed": {
                "$ref": "#/components/headers/X-Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditNoteResult"
                }
              }
            }
          },
          "400": {
            "description": "Validation error, or one of `IDEMPOTENCY_KEY_REQUIRED`,\n`PARTIAL_CREDIT_NOT_IMPLEMENTED`, `ORIGINAL_INVOICE_IS_DRAFT`,\n`ORIGINAL_INVOICE_IS_CANCELLED`, `ORIGINAL_INVOICE_FULLY_CREDITED`,\n`ORIGINAL_INVOICE_MISSING_FISCAL_ZONE` (the original invoice has no\n`clientLocation`, so the correct rectificativa form cannot be determined).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditNoteError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The effective plan is below `pro` (`PLAN_UPGRADE_REQUIRED`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditNoteError"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Either `IDEMPOTENCY_KEY_REUSED` — the key is live for a DIFFERENT\noperation (another endpoint, or this one with a different body), which\nis refused without executing — or `IDEMPOTENCY_REQUEST_IN_PROGRESS`:\na request with this key is still running, or its outcome could not be\nrecorded. Reconcile the invoice's credit notes before starting another\noperation; a fresh key would create a second document.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditNoteError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/late-fee": {
      "post": {
        "operationId": "applyLateFee",
        "summary": "Apply a late payment fee",
        "description": "Calculate and apply a late payment fee to an overdue or sent invoice.\nUses the EU Late Payment Directive default rate of 8% per annum if `amount`\nis not specified. Can only be applied once per invoice.\n",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "number",
                    "description": "Fixed fee amount. If omitted, auto-calculated at 8% p.a. on the invoice total.",
                    "example": 42.5
                  },
                  "daysOverdue": {
                    "type": "integer",
                    "description": "Override the number of overdue days used for auto-calculation. Defaults to actual days past due date.",
                    "example": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Late fee applied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "feeAmount": {
                      "type": "number",
                      "description": "Fee amount applied",
                      "example": 42.5
                    },
                    "invoiceId": {
                      "type": "string",
                      "example": "abc123def456"
                    },
                    "daysOverdue": {
                      "type": "integer",
                      "example": 30
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses/{expenseId}/billable": {
      "post": {
        "operationId": "markExpenseBillable",
        "summary": "Mark expense as billable",
        "description": "Mark an expense as billable to a specific client, optionally with a markup\npercentage. The expense must not already be invoiced. Once billable, the\nexpense can be included in a client invoice.\n",
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ExpenseId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "clientId"
                ],
                "properties": {
                  "clientId": {
                    "type": "string",
                    "description": "ID of the client to bill this expense to",
                    "example": "client_abc123"
                  },
                  "markup": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1000,
                    "description": "Markup percentage to apply when invoicing (e.g. 20 = 20% markup)",
                    "example": 20
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Expense marked as billable",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "expenseId": {
                      "type": "string",
                      "example": "exp_abc123"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "unmarkExpenseBillable",
        "summary": "Remove billable flag from expense",
        "description": "Remove the billable flag from an expense. The expense must not already\nbe invoiced — once invoiced, the billable flag cannot be removed.\n",
        "tags": [
          "Expenses"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ExpenseId"
          }
        ],
        "responses": {
          "200": {
            "description": "Billable flag removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "expenseId": {
                      "type": "string",
                      "example": "exp_abc123"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/reservations": {
      "get": {
        "operationId": "listReservations",
        "summary": "List reservations",
        "description": "Retrieve a paginated list of Stay reservations with optional filters.",
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ReservationStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of reservations",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Reservation"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/reservations/{reservationId}": {
      "get": {
        "operationId": "getReservation",
        "summary": "Get a reservation",
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Reservation details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Reservation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/properties": {
      "get": {
        "operationId": "listProperties",
        "summary": "List properties",
        "description": "Retrieve a paginated list of Stay properties.",
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/PropertyStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of properties",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Property"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/properties/{propertyId}": {
      "get": {
        "operationId": "getProperty",
        "summary": "Get a property",
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Property details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Property"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/guests": {
      "get": {
        "operationId": "listGuests",
        "summary": "List guest compliance records",
        "description": "Retrieve compliance records for guest check-ins (document verification status).",
        "tags": [
          "Guests"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of guest compliance records",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Guest"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/guests/{guestId}": {
      "get": {
        "operationId": "getGuest",
        "summary": "Get a guest compliance record",
        "tags": [
          "Guests"
        ],
        "parameters": [
          {
            "name": "guestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Guest compliance record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Guest"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/channels": {
      "get": {
        "operationId": "listChannels",
        "summary": "List channels",
        "description": "Retrieve channel connections (iCal feeds) for calendar sync.",
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ChannelStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of channels",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Channel"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createChannel",
        "summary": "Create a channel (add iCal feed)",
        "tags": [
          "Channels"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChannelCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Channel created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Channel"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/channels/{channelId}": {
      "delete": {
        "operationId": "deleteChannel",
        "summary": "Remove a channel",
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Channel deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/channels/{channelId}/sync": {
      "post": {
        "operationId": "syncChannel",
        "summary": "Trigger channel sync",
        "description": "Triggers a sync of the iCal feed for this channel.",
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sync triggered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        },
                        "message": {
                          "type": "string"
                        },
                        "channelId": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/einvoice/export": {
      "post": {
        "operationId": "exportEInvoice",
        "summary": "Export e-invoice XML",
        "description": "Generate and return an e-invoice XML document for the invoice in one of 9 supported formats.\nOptionally sign with XAdES-EPES (Facturae only). Conforms to EN16931 semantic model.\n\nSupported formats: Facturae 3.2.2, XRechnung-CII, XRechnung-UBL, Factur-X (EN16931),\nFatturaPA 1.2.2, PEPPOL-BIS-3, FA-2-KSeF (Polish KSeF FA(3)), UBL 2.1, CII D16B.\n\nRequires owner role or API key with `einvoice:*` scope.\n",
        "tags": [
          "E-Invoicing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "format"
                ],
                "additionalProperties": false,
                "properties": {
                  "format": {
                    "type": "string",
                    "enum": [
                      "Facturae",
                      "XRechnung-CII",
                      "XRechnung-UBL",
                      "Factur-X",
                      "FatturaPA",
                      "PEPPOL-BIS-3",
                      "FA-2-KSeF",
                      "UBL",
                      "CII"
                    ],
                    "description": "Target e-invoice format.\n- Facturae: Spanish B2G (Agencia Tributaria, FACe)\n- XRechnung-CII/UBL: German B2G (PEPPOL/ZRE/OZG-RE)\n- Factur-X: French standard (PDF/A-3 XML component)\n- FatturaPA: Italian SDI clearance\n- PEPPOL-BIS-3: Pan-European PEPPOL network\n- FA-2-KSeF: Polish KSeF FA(3) XML\n- UBL/CII: Generic cross-border EU\n",
                    "example": "Facturae"
                  },
                  "signed": {
                    "type": "boolean",
                    "default": false,
                    "description": "Sign the XML with XAdES-EPES. Only supported for `format: Facturae`.\nRequires a valid PKCS#12 certificate uploaded in Settings > Compliance.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "E-invoice XML generated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "xml": {
                          "type": "string",
                          "description": "E-invoice XML content (UTF-8)"
                        },
                        "contentType": {
                          "type": "string",
                          "example": "application/xml"
                        },
                        "filename": {
                          "type": "string",
                          "example": "F2026-001_Facturae.xml"
                        },
                        "signed": {
                          "type": "boolean",
                          "description": "Whether the XML was signed with XAdES-EPES"
                        },
                        "format": {
                          "type": "string",
                          "example": "Facturae"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid format, signing not supported for this format, or body validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "XML generation failed or certificate not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/face/submit": {
      "post": {
        "operationId": "submitFaceInvoice",
        "summary": "Submit invoice to FACe B2G portal",
        "description": "Submit a signed Facturae XML to Spain's FACe B2G portal. If the invoice was already accepted, the prior result is returned without resubmission. Requires owner role or an API key with `einvoice:*` scope and a configured PKCS#12 certificate.",
        "tags": [
          "E-Invoicing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "mock",
                      "sandbox",
                      "production"
                    ],
                    "description": "FACe submission mode. Defaults to `ES_FACE_MODE` env var (default `mock`)."
                  },
                  "correo": {
                    "type": "string",
                    "format": "email",
                    "description": "Recipient notification email (required by FACe protocol). Defaults to fiscal billing email."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice submitted to FACe",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "type": "string",
                          "example": "submitted"
                        },
                        "numeroRegistro": {
                          "type": "string",
                          "description": "FACe registration number (never fabricated)",
                          "example": "REG/2026/000042"
                        },
                        "codigo": {
                          "type": "string",
                          "description": "FACe result code",
                          "example": "0"
                        },
                        "descripcion": {
                          "type": "string",
                          "description": "FACe result description",
                          "example": "Registrada"
                        },
                        "idempotent": {
                          "type": "boolean",
                          "description": "Present and true when returning a prior accepted submission"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "FACe rejection, certificate missing, or XML generation error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "codigo": {
                            "type": "string"
                          },
                          "descripcion": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/face/status": {
      "get": {
        "operationId": "getFaceInvoiceStatus",
        "summary": "Get FACe submission status",
        "description": "Query the current tramitación status of a previously submitted FACe invoice. Requires owner role or an API key with `einvoice:*` scope.",
        "tags": [
          "E-Invoicing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "FACe tramitación status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "numeroRegistro": {
                          "type": "string",
                          "description": "FACe registration number recorded for the submission."
                        },
                        "estadoTramitacion": {
                          "type": "string",
                          "nullable": true,
                          "description": "FACe tramitación code (e.g. '1200' = Registrada, '1300' = En tramitación)"
                        },
                        "codigo": {
                          "type": "string",
                          "nullable": true
                        },
                        "descripcion": {
                          "type": "string",
                          "nullable": true
                        },
                        "motivo": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No FACe submission found for this invoice",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Certificate required or FACe transport error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/ticketbai/submit": {
      "post": {
        "operationId": "submitTicketBaiInvoice",
        "summary": "Submit invoice to TicketBAI (Bizkaia BATUZ)",
        "description": "Submit a signed TicketBAI XML to the Bizkaia BATUZ tax authority. If the invoice was already accepted, the prior result is returned without resubmission. Requires owner role or an API key with `einvoice:*` scope and a configured TicketBAI certificate.",
        "tags": [
          "E-Invoicing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "sandbox": {
                    "type": "boolean",
                    "description": "Override to sandbox mode (default from user integration config)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "TicketBAI submission accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "accepted": {
                          "type": "boolean",
                          "example": true
                        },
                        "csv": {
                          "type": "string",
                          "description": "CSV de verificación (never fabricated)",
                          "example": "TBAI-48-120226-000001-2H"
                        },
                        "tbaiIdentifier": {
                          "type": "string",
                          "description": "TicketBAI identifier string",
                          "example": "TBAI-B-000001"
                        },
                        "qrUrl": {
                          "type": "string",
                          "description": "QR code URL for invoice printing"
                        },
                        "idempotent": {
                          "type": "boolean",
                          "description": "Present and true when returning a prior accepted submission"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "TicketBAI rejection, certificate missing, or chain broken",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}/ticketbai/status": {
      "get": {
        "operationId": "getTicketBaiInvoiceStatus",
        "summary": "Get TicketBAI submission status",
        "description": "Read the accepted or rejected status, CSV, identifier and QR URL for a previously submitted TicketBAI invoice. Requires owner role or an API key with `einvoice:*` scope.",
        "tags": [
          "E-Invoicing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/InvoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "TicketBAI submission status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "accepted": {
                          "type": "boolean"
                        },
                        "csv": {
                          "type": "string",
                          "description": "TicketBAI verification CSV."
                        },
                        "tbaiIdentifier": {
                          "type": "string"
                        },
                        "estado": {
                          "type": "string",
                          "description": "accepted | rejected"
                        },
                        "qrUrl": {
                          "type": "string",
                          "nullable": true
                        },
                        "submittedAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "errors": {
                          "type": "array",
                          "nullable": true,
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No TicketBAI submission found for this invoice",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/kitchen/tickets": {
      "get": {
        "operationId": "listKitchenTickets",
        "summary": "List tickets",
        "tags": [
          "Kitchen"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of tickets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/kitchen/tickets/{id}": {
      "get": {
        "operationId": "getKitchenTickets",
        "summary": "Get tickets by ID",
        "tags": [
          "Kitchen"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tickets details"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateKitchenTickets",
        "summary": "Update tickets",
        "tags": [
          "Kitchen"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tickets updated"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/kitchen/stations": {
      "get": {
        "operationId": "listKitchenStations",
        "summary": "List stations",
        "tags": [
          "Kitchen"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of stations",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/kitchen/menuItems": {
      "get": {
        "operationId": "listKitchenMenuItems",
        "summary": "List menuItems",
        "tags": [
          "Kitchen"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of menuItems",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/kitchen/menu-items": {
      "get": {
        "operationId": "listKitchenMenuItemsAlias",
        "summary": "List menu items (compatibility alias)",
        "deprecated": true,
        "tags": [
          "Kitchen"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of menuItems",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/pos/sales": {
      "get": {
        "operationId": "listPosSales",
        "summary": "List sales",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of sales",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createPosSales",
        "summary": "Create sales",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sales created"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/pos/sales/{id}": {
      "get": {
        "operationId": "getPosSales",
        "summary": "Get sales by ID",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sales details"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updatePosSales",
        "summary": "Update sales",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sales updated"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deletePosSales",
        "summary": "Delete sales",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Sales deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/pos/products": {
      "get": {
        "operationId": "listPosProducts",
        "summary": "List products",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of products",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createPosProducts",
        "summary": "Create products",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Products created"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/pos/products/{id}": {
      "get": {
        "operationId": "getPosProducts",
        "summary": "Get products by ID",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Products details"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updatePosProducts",
        "summary": "Update products",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Products updated"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deletePosProducts",
        "summary": "Delete products",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Products deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/pos/terminals": {
      "get": {
        "operationId": "listPosTerminals",
        "summary": "List terminals",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of terminals",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createPosTerminals",
        "summary": "Create terminals",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Terminals created"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/pos/terminals/{id}": {
      "get": {
        "operationId": "getPosTerminals",
        "summary": "Get terminals by ID",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Terminals details"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updatePosTerminals",
        "summary": "Update terminals",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Terminals updated"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deletePosTerminals",
        "summary": "Delete terminals",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Terminals deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/pos/cash_sessions": {
      "get": {
        "operationId": "listPosCashSessions",
        "summary": "List cash sessions",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of cash sessions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createPosCashSessions",
        "summary": "Create cash sessions",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Cash sessions created"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/pos/cash_sessions/{id}": {
      "get": {
        "operationId": "getPosCashSessions",
        "summary": "Get cash sessions by ID",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cash sessions details"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updatePosCashSessions",
        "summary": "Update cash sessions",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cash sessions updated"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deletePosCashSessions",
        "summary": "Delete cash sessions",
        "tags": [
          "Pos"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Cash sessions deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/stay/reservations": {
      "get": {
        "operationId": "listStayReservations",
        "summary": "List reservations",
        "tags": [
          "Stay"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of reservations",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/stay/reservations/{id}": {
      "get": {
        "operationId": "getStayReservations",
        "summary": "Get reservations by ID",
        "tags": [
          "Stay"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reservations details"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/stay/properties": {
      "get": {
        "operationId": "listStayProperties",
        "summary": "List properties",
        "tags": [
          "Stay"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of properties",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/team/members": {
      "get": {
        "operationId": "listTeamMembers",
        "summary": "List team members",
        "description": "Returns active team members plus every non-terminal invitation for the workspace, each labelled with its effective state computed at read time (`pending`, `expired`, or `invalid`). Accepted and revoked invitations are not listed. This endpoint is the recovery path for `invalid` rows: list them, then revoke via DELETE /v1/team/members/{memberId}.",
        "tags": [
          "Team"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "role",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "owner",
                "admin",
                "viewer",
                "editor",
                "accountant"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "pending",
                "expired",
                "invalid"
              ]
            },
            "description": "Filter by effective state. `active` = joined member. `pending` = invitation not yet accepted and not yet past its expiry. `expired` = invitation proven past `expiresAt` (holds no seat). `invalid` = invitation whose validity cannot be proven (missing or unparseable `expiresAt`, unknown persisted status, or a role that is not an assignable team role). An `invalid` invitation conservatively HOLDS its seat and can only be released by revoking it via DELETE /v1/team/members/{memberId}."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of team members and pending invitations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamMember"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/team/members/invite": {
      "post": {
        "operationId": "inviteTeamMember",
        "summary": "Invite a team member",
        "description": "Send an email invitation to join the workspace. Subject to plan seat limits (free: 1; pro, Premium and enterprise: unlimited). Invitation expires in 7 days.",
        "tags": [
          "Team"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamInviteRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invitation sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending"
                          ]
                        },
                        "expiresAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "Conflict — user already a member, invite already pending, or seat limit reached"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/team/members/{memberId}": {
      "delete": {
        "operationId": "removeTeamMember",
        "summary": "Remove team member or revoke pending invitation",
        "description": "Removes an active team member or revokes a pending invitation by ID. The workspace owner cannot be removed. For pending invitations, pass the invitation document ID returned by POST /v1/team/members/invite.",
        "tags": [
          "Team"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "memberId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Active member ID or pending invitation ID"
          }
        ],
        "responses": {
          "204": {
            "description": "Member removed or invitation revoked"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Cannot remove workspace owner"
          },
          "404": {
            "description": "Member or invitation not found"
          },
          "409": {
            "description": "Invitation is no longer revocable. Only the terminal states (`accepted`, `revoked`) refuse revocation; `pending`, `expired` and `invalid` invitations are all revocable so the owner can release the seat."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/team/members/{memberId}/role": {
      "patch": {
        "operationId": "updateTeamMemberRole",
        "summary": "Change a team member's role",
        "description": "Update the role of an active team member. Cannot change the workspace owner's role.",
        "tags": [
          "Team"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "memberId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamRoleUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Role updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        },
                        "updatedAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Cannot change workspace owner role"
          },
          "404": {
            "description": "Team member not found"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/banking/accounts": {
      "get": {
        "operationId": "listBankAccounts",
        "summary": "List bank accounts",
        "description": "Returns the connected bank accounts (bank connections) for the workspace.",
        "tags": [
          "Banking"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "provider",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "isActive",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of bank accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BankAccount"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "hasMore": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/banking/accounts/{accountId}": {
      "get": {
        "operationId": "getBankAccount",
        "summary": "Get bank account",
        "description": "Returns a single bank account (connection) by ID.",
        "tags": [
          "Banking"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "accountId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bank account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankAccount"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/banking/transactions": {
      "get": {
        "operationId": "listBankTransactions",
        "summary": "List bank transactions",
        "description": "Returns bank transactions for the workspace. Filter by account, date range, status, or category.",
        "tags": [
          "Banking"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "accountId",
            "in": "query",
            "description": "Filter by bank account (connection) ID.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start date (ISO YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End date (ISO YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "raw",
                "recognized",
                "matched",
                "categorized",
                "excluded"
              ]
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "reconciled",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of bank transactions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BankTransaction"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "hasMore": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createBankTransaction",
        "summary": "Create a bank transaction (manual entry)",
        "description": "Manually create a bank transaction. The transaction is attached to the\naccount identified by accountId (which must belong to your workspace).\nReconciliation state is force-initialised (reconciled=false, status=raw);\na caller cannot create an already-reconciled transaction.\n",
        "tags": [
          "Banking"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankTransactionCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Transaction created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankTransaction"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/banking/transactions/{transactionId}/suggestions": {
      "get": {
        "operationId": "getBankTransactionSuggestions",
        "summary": "Get reconciliation suggestions",
        "description": "Read-only ranked reconciliation candidates for a bank transaction.\nThis endpoint does not mutate the transaction, invoices, expenses,\nledger entries, or reconciled fields.\n",
        "tags": [
          "Banking"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "transactionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 5
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Read-only reconciliation suggestions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "transactionId": {
                          "type": "string"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "suggestions": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ReconciliationSuggestion"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/banking/transactions/{transactionId}/categorize": {
      "patch": {
        "operationId": "categorizeBankTransaction",
        "summary": "Categorize a bank transaction",
        "description": "Set the category (and optional notes) on a bank transaction.",
        "tags": [
          "Banking"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "transactionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankCategorize"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transaction categorized",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankTransaction"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/banking/transactions/{transactionId}/match": {
      "post": {
        "operationId": "matchBankTransaction",
        "summary": "Match a bank transaction to an invoice or expense",
        "description": "Link a bank transaction to an invoice or expense in your workspace\n(status becomes 'matched'). This records the match WITHOUT posting ledger\n(GL) entries — full GL reconciliation is performed in the supervised app\nflow. Fails with 409 if the transaction is already matched/reconciled.\n",
        "tags": [
          "Banking"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "transactionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankMatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transaction matched",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankTransaction"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Transaction already matched/reconciled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key for authentication. Keys start with `fri_` prefix.\nGenerate keys from Settings > API Keys in the Frihet app.\n"
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Number of items to return (default 50, max 100)",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 50
        }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "description": "Number of items to skip for pagination (max 10000)",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10000,
          "default": 0
        }
      },
      "From": {
        "name": "from",
        "in": "query",
        "description": "Start date filter in ISO format (YYYY-MM-DD)",
        "schema": {
          "type": "string",
          "format": "date",
          "example": "2026-01-01"
        }
      },
      "To": {
        "name": "to",
        "in": "query",
        "description": "End date filter in ISO format (YYYY-MM-DD)",
        "schema": {
          "type": "string",
          "format": "date",
          "example": "2026-12-31"
        }
      },
      "InvoiceId": {
        "name": "invoiceId",
        "in": "path",
        "required": true,
        "description": "Invoice unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "ExpenseId": {
        "name": "expenseId",
        "in": "path",
        "required": true,
        "description": "Expense unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "ClientId": {
        "name": "clientId",
        "in": "path",
        "required": true,
        "description": "Client unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "ProductId": {
        "name": "productId",
        "in": "path",
        "required": true,
        "description": "Product unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "QuoteId": {
        "name": "quoteId",
        "in": "path",
        "required": true,
        "description": "Quote unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "VendorId": {
        "name": "vendorId",
        "in": "path",
        "required": true,
        "description": "Vendor unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "WebhookId": {
        "name": "webhookId",
        "in": "path",
        "required": true,
        "description": "Webhook unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "ContactId": {
        "name": "contactId",
        "in": "path",
        "required": true,
        "description": "Contact person unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "ActivityId": {
        "name": "activityId",
        "in": "path",
        "required": true,
        "description": "Activity unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "NoteId": {
        "name": "noteId",
        "in": "path",
        "required": true,
        "description": "Note unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "DepositId": {
        "name": "depositId",
        "in": "path",
        "required": true,
        "description": "Deposit unique identifier",
        "schema": {
          "type": "string"
        }
      },
      "Search": {
        "name": "q",
        "in": "query",
        "description": "Full-text search query. Searches across relevant fields per resource type\n(e.g., clientName, documentNumber for invoices; name, email for clients).\nCase-insensitive.\n",
        "schema": {
          "type": "string",
          "example": "acme"
        }
      },
      "Fields": {
        "name": "fields",
        "in": "query",
        "description": "Comma-separated list of fields to include in the response.\nThe `id` field is always included. Example: `fields=id,name,total`\n",
        "schema": {
          "type": "string",
          "example": "id,name,total"
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "description": "Cursor for cursor-based pagination. Use the `nextCursor` value from\na previous response to fetch the next page. When provided, `offset`\nis ignored. The cursor is a base64url-encoded JSON string.\n",
        "schema": {
          "type": "string",
          "example": "eyJpc3N1ZURhdGUiOiIyMDI2LTAzLTIwIiwiX19pZCI6ImFiYzEyMyJ9"
        }
      },
      "FilterClientId": {
        "name": "clientId",
        "in": "query",
        "description": "Filter invoices or quotes by client ID",
        "schema": {
          "type": "string"
        }
      },
      "FilterSeriesId": {
        "name": "seriesId",
        "in": "query",
        "description": "Filter invoices or quotes by invoice series ID",
        "schema": {
          "type": "string"
        }
      },
      "FilterVendorId": {
        "name": "vendorId",
        "in": "query",
        "description": "Filter expenses by vendor ID",
        "schema": {
          "type": "string"
        }
      },
      "FilterCategory": {
        "name": "category",
        "in": "query",
        "description": "Filter expenses by category",
        "schema": {
          "type": "string",
          "example": "office_supplies"
        }
      },
      "FilterIsActive": {
        "name": "isActive",
        "in": "query",
        "description": "Filter products by active status",
        "schema": {
          "type": "boolean",
          "example": true
        }
      },
      "FilterStage": {
        "name": "stage",
        "in": "query",
        "description": "Filter clients by pipeline stage",
        "schema": {
          "type": "string",
          "enum": [
            "lead",
            "contacted",
            "proposal",
            "active",
            "inactive",
            "lost"
          ],
          "example": "active"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Unique key to prevent duplicate resource creation from network retries on\nidempotency-protected POST routes. Reserved against the operation (method +\npath + complete received JSON request body + parsed query parameters) before\nthe request runs, and held for 24 hours. Object-key order does not matter,\nincluding in nested objects, but array order does. Only the credit-note\nendpoint applies its documented schema defaults before fingerprinting; all\nother protected POSTs use the received body.\n\nA retry of the same operation returns the ORIGINAL status and body, verbatim,\nwith an `X-Idempotent-Replayed: true` header — a `201` replays as `201`, and\na stored error replays as that same error rather than re-executing. The same\nkey used for a DIFFERENT operation gets `409 IDEMPOTENCY_KEY_REUSED` and the\nsecond operation is not executed; a request still in flight, or one whose\noutcome could not be recorded, gets `409 IDEMPOTENCY_REQUEST_IN_PROGRESS`\n(reconcile the resource rather than retrying with a new key).\n\nAuthentication, rate limiting, maximum-request-size checks, and current\nfiscal-send scope authorization happen before this gate; those refusals do\nnot reserve or alter the key. `POST /v1/gestoria/aging` bypasses this gate and ignores the header so current workspace membership is always rechecked.\n\nMax 64 characters. UUID v4 recommended.\n",
        "schema": {
          "type": "string",
          "maxLength": 64,
          "example": "550e8400-e29b-41d4-a716-446655440000"
        }
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Maximum number of requests allowed per window",
        "schema": {
          "type": "integer",
          "example": 100
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Number of requests remaining in the current window",
        "schema": {
          "type": "integer",
          "example": 87
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix timestamp (seconds) when the rate limit window resets",
        "schema": {
          "type": "integer",
          "example": 1742310060
        }
      },
      "X-API-Version": {
        "description": "Current API version date. Monitor for breaking changes.",
        "schema": {
          "type": "string",
          "example": "2026-03-18"
        }
      },
      "X-Idempotent-Replayed": {
        "description": "Present and set to \"true\" when this response is a stored replay of an earlier\nrequest with the same Idempotency-Key. The status and body are the ORIGINAL\nones; nothing was executed to produce this response.\n",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      }
    },
    "schemas": {
      "CreditNoteResult": {
        "type": "object",
        "description": "Result of `POST /v1/invoices/{invoiceId}/credit-note`. `status` is always\n`draft` — the endpoint creates, it does not issue.\n",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean",
                "example": true
              },
              "creditNote": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "ID of the newly created credit note draft",
                    "example": "cn_abc123"
                  },
                  "documentNumber": {
                    "type": "string",
                    "description": "Provisional reference, not a fiscal number. Numbering happens on issue.",
                    "example": "CN-FAC-2026-0042"
                  },
                  "originalInvoiceId": {
                    "type": "string",
                    "example": "abc123def456"
                  },
                  "reason": {
                    "type": "string",
                    "example": "error"
                  },
                  "fullCredit": {
                    "type": "boolean",
                    "example": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft"
                    ],
                    "description": "Always `draft`.",
                    "example": "draft"
                  },
                  "rectificationMethod": {
                    "type": "string",
                    "enum": [
                      "I"
                    ],
                    "description": "Always `I` (por diferencias). `S` is not offered over the API.",
                    "example": "I"
                  },
                  "totalCredited": {
                    "type": "number",
                    "description": "Tax-inclusive amount credited, as a positive number.",
                    "example": 544.5
                  },
                  "replayed": {
                    "type": "boolean",
                    "deprecated": true,
                    "description": "DEPRECATED and no longer emitted. A replay is now signalled by the\n`X-Idempotent-Replayed: true` response header, and the replayed\nbody is byte-identical to the original — which a body-level flag\nwould contradict. Documented only so existing clients know the\nfield is absent rather than false.\n",
                    "example": false
                  }
                }
              }
            }
          }
        }
      },
      "CreditNoteError": {
        "type": "object",
        "description": "Credit-note refusal, with a stable machine-readable code.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message.",
            "example": "Credit notes require the pro plan or above."
          },
          "code": {
            "type": "string",
            "enum": [
              "IDEMPOTENCY_KEY_REQUIRED",
              "PLAN_UPGRADE_REQUIRED",
              "PARTIAL_CREDIT_NOT_IMPLEMENTED",
              "INVOICE_NOT_FOUND",
              "ORIGINAL_INVOICE_IS_DRAFT",
              "ORIGINAL_INVOICE_IS_CANCELLED",
              "ORIGINAL_INVOICE_FULLY_CREDITED",
              "ORIGINAL_INVOICE_MISSING_FISCAL_ZONE",
              "IDEMPOTENCY_KEY_REUSED",
              "IDEMPOTENCY_REQUEST_IN_PROGRESS"
            ],
            "example": "PLAN_UPGRADE_REQUIRED"
          }
        }
      },
      "BankAccount": {
        "type": "object",
        "description": "A connected bank account.",
        "properties": {
          "id": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "nullable": true,
            "description": "saltedge | tink | truelayer | yodlee | revolut | manual"
          },
          "bankName": {
            "type": "string",
            "nullable": true
          },
          "accountName": {
            "type": "string",
            "nullable": true
          },
          "accountNumber": {
            "type": "string",
            "nullable": true,
            "description": "Last 4 digits for display."
          },
          "iban": {
            "type": "string",
            "nullable": true,
            "description": "Masked IBAN."
          },
          "currency": {
            "type": "string",
            "nullable": true
          },
          "isActive": {
            "type": "boolean"
          },
          "lastSyncAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "BankCategorize": {
        "type": "object",
        "required": [
          "category"
        ],
        "properties": {
          "category": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "notes": {
            "type": "string",
            "maxLength": 2000
          }
        }
      },
      "BankMatch": {
        "type": "object",
        "required": [
          "documentId",
          "documentType"
        ],
        "properties": {
          "documentId": {
            "type": "string",
            "description": "ID of the invoice or expense to match. Must belong to your workspace."
          },
          "documentType": {
            "type": "string",
            "enum": [
              "invoice",
              "expense"
            ]
          },
          "notes": {
            "type": "string",
            "maxLength": 2000
          }
        }
      },
      "BankTransaction": {
        "type": "object",
        "description": "A bank transaction. Positive amount = income, negative = expense.",
        "properties": {
          "id": {
            "type": "string"
          },
          "connectionId": {
            "type": "string",
            "description": "ID of the owning bank account (accountId)."
          },
          "transactionId": {
            "type": "string",
            "description": "Provider transaction ID (manual-* for API-created entries)."
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "amount": {
            "type": "number",
            "description": "Positive for income, negative for expenses."
          },
          "currency": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "reference": {
            "type": "string",
            "nullable": true
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "counterparty": {
            "type": "object",
            "nullable": true,
            "properties": {
              "name": {
                "type": "string"
              },
              "accountNumber": {
                "type": "string"
              },
              "iban": {
                "type": "string"
              }
            }
          },
          "reconciled": {
            "type": "boolean"
          },
          "reconciledWith": {
            "type": "string",
            "enum": [
              "invoice",
              "expense"
            ],
            "nullable": true
          },
          "reconciledId": {
            "type": "string",
            "nullable": true
          },
          "reconciledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "raw",
              "recognized",
              "matched",
              "categorized",
              "excluded"
            ]
          },
          "source": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "BankTransactionCreate": {
        "type": "object",
        "required": [
          "accountId",
          "amount",
          "date",
          "description"
        ],
        "properties": {
          "accountId": {
            "type": "string",
            "description": "ID of the bank account (connection) to attach the transaction to. Must belong to your workspace."
          },
          "amount": {
            "type": "number",
            "description": "Non-zero. Positive for income, negative for expenses."
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "ISO date (YYYY-MM-DD)."
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          },
          "currency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3,
            "description": "ISO 4217 code. Defaults to the account currency or EUR."
          },
          "reference": {
            "type": "string",
            "maxLength": 500
          },
          "category": {
            "type": "string",
            "maxLength": 200
          },
          "counterparty": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "accountNumber": {
                "type": "string"
              },
              "iban": {
                "type": "string"
              }
            },
            "required": [
              "name"
            ]
          }
        }
      },
      "ReconciliationSuggestion": {
        "type": "object",
        "properties": {
          "documentType": {
            "type": "string",
            "enum": [
              "invoice",
              "expense"
            ]
          },
          "documentId": {
            "type": "string"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "confidence": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "suggested": {
            "type": "boolean"
          },
          "amountDiff": {
            "type": "number"
          },
          "dateDiff": {
            "type": "number"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "breakdown": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "signal": {
                  "type": "string",
                  "enum": [
                    "amount",
                    "date",
                    "reference",
                    "counterparty"
                  ]
                },
                "points": {
                  "type": "integer"
                },
                "similarity": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "reason": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "GlobalSearchResult": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "invoices",
              "expenses",
              "vendors",
              "clients",
              "products"
            ]
          },
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "secondary": {
            "type": "string",
            "nullable": true
          },
          "date": {
            "type": "string",
            "nullable": true
          },
          "amount": {
            "type": "number",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "InvoiceStatus": {
        "type": "string",
        "enum": [
          "draft",
          "sent",
          "partial",
          "paid",
          "overdue",
          "cancelled"
        ]
      },
      "QuoteStatus": {
        "type": "string",
        "enum": [
          "draft",
          "sent",
          "accepted",
          "rejected",
          "expired"
        ]
      },
      "LineItem": {
        "type": "object",
        "required": [
          "description",
          "quantity",
          "unitPrice"
        ],
        "properties": {
          "description": {
            "type": "string",
            "description": "Item description",
            "example": "Web development services"
          },
          "quantity": {
            "type": "number",
            "description": "Quantity",
            "example": 10
          },
          "unitPrice": {
            "type": "number",
            "description": "Price per unit",
            "example": 85
          }
        }
      },
      "Address": {
        "type": "object",
        "description": "Structured address. Canonical fields are `province` and `zip`.\nThe aliases `state` and `postalCode` are also accepted for backward compatibility.\n",
        "properties": {
          "street": {
            "type": "string",
            "example": "Calle Gran Via 28"
          },
          "city": {
            "type": "string",
            "example": "Madrid"
          },
          "province": {
            "type": "string",
            "description": "Province or state (canonical field)",
            "example": "Madrid"
          },
          "zip": {
            "type": "string",
            "description": "Postal/ZIP code (canonical field)",
            "example": "28013"
          },
          "country": {
            "type": "string",
            "example": "ES"
          },
          "state": {
            "type": "string",
            "description": "Alias for `province` (backward compatibility)",
            "deprecated": true
          },
          "postalCode": {
            "type": "string",
            "description": "Alias for `zip` (backward compatibility)",
            "deprecated": true
          }
        }
      },
      "InvoiceCreate": {
        "type": "object",
        "required": [
          "clientName",
          "items"
        ],
        "properties": {
          "clientId": {
            "type": "string",
            "description": "Client ID. When provided, the API auto-snapshots\n`clientTaxId`, `clientAddress`, `clientLocation`, `clientName`,\nand `clientEmail` from the referenced client at create time\n(immutable post-create — VeriFactu RD 1007/2023). Caller-provided\nvalues for those fields override the snapshot.\n",
            "example": "client_abc123"
          },
          "clientName": {
            "type": "string",
            "description": "Client name (snapshot field — frozen at create)",
            "example": "Acme Corp"
          },
          "clientTaxId": {
            "type": "string",
            "description": "Client tax ID / NIF / VAT (snapshot field — frozen at create).\nAuto-populated from clientId reference if not provided.\n",
            "example": "B12345678"
          },
          "clientAddress": {
            "type": "string",
            "description": "Client billing address — single-line or structured. Snapshot\nfield, frozen at create.\n",
            "example": "Calle Mayor 12, 38001 Santa Cruz de Tenerife"
          },
          "clientLocation": {
            "type": "string",
            "enum": [
              "peninsula",
              "canarias",
              "ceuta_melilla",
              "eu",
              "world"
            ],
            "description": "Fiscal zone — drives tax label rendering on PDF + tax math\n(IVA/IGIC/IPSI/Reverse charge/Exempt). Snapshot field, frozen\nat create. Auto-populated from clientId reference if absent.\n",
            "example": "canarias"
          },
          "clientEmail": {
            "type": "string",
            "format": "email",
            "description": "Client email (snapshot field — frozen at create)."
          },
          "documentNumber": {
            "type": "string",
            "maxLength": 50,
            "description": "Optional caller-supplied invoice number (e.g. migration or\nimport flows carrying an externally-issued number). When\nprovided it is honored verbatim and the workspace auto-numbering\ncounter is NOT advanced. When omitted, a gapless sequential\nnumber is generated from your numbering settings (VeriFactu\nRD 1007/2023).\n",
            "example": "FAC-2026-0042"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItem"
            },
            "minItems": 1
          },
          "issueDate": {
            "type": "string",
            "format": "date",
            "description": "Issue date in ISO format",
            "example": "2026-03-09"
          },
          "dueDate": {
            "type": "string",
            "format": "date",
            "description": "Due date in ISO format",
            "example": "2026-04-09"
          },
          "status": {
            "$ref": "#/components/schemas/InvoiceStatus"
          },
          "notes": {
            "type": "string",
            "description": "Additional notes",
            "example": "Payment due within 30 days"
          },
          "taxRate": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Tax rate percentage. Combined with `clientLocation` to produce\nthe label (IGIC for canarias, IPSI for ceuta_melilla, etc.).\n",
            "example": 21
          },
          "irpfRate": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "IRPF withholding rate percentage (Spain).",
            "example": 15
          },
          "discountRate": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Invoice-level discount percentage.",
            "example": 10
          },
          "recurring": {
            "$ref": "#/components/schemas/InvoiceRecurringConfig"
          }
        }
      },
      "Invoice": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier",
                "example": "abc123def456"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "description": "Creation timestamp"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time",
                "description": "Last update timestamp"
              }
            }
          },
          {
            "$ref": "#/components/schemas/InvoiceCreate"
          },
          {
            "type": "object",
            "properties": {
              "documentNumber": {
                "type": "string",
                "description": "Auto-generated invoice number",
                "example": "FAC-2026-0042"
              },
              "subtotal": {
                "type": "number",
                "description": "Subtotal before tax",
                "example": 850
              },
              "tax": {
                "type": "number",
                "description": "Tax amount",
                "example": 178.5
              },
              "total": {
                "type": "number",
                "description": "Total amount including tax",
                "example": 1028.5
              },
              "currency": {
                "type": "string",
                "description": "Currency code",
                "example": "EUR"
              },
              "verifactuHash": {
                "type": "string",
                "description": "VeriFactu SHA-256 hash (Spanish tax compliance)"
              }
            }
          }
        ]
      },
      "FileAttachmentUploadCreate": {
        "type": "object",
        "required": [
          "fileName",
          "contentType",
          "sizeBytes"
        ],
        "additionalProperties": false,
        "properties": {
          "fileName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Filename only; local paths are rejected.",
            "example": "receipt.pdf"
          },
          "contentType": {
            "type": "string",
            "enum": [
              "application/pdf",
              "image/jpeg",
              "image/jpg",
              "image/png",
              "image/webp",
              "image/heic",
              "image/heif",
              "image/tiff"
            ]
          },
          "sizeBytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10485760
          },
          "expenseId": {
            "type": "string",
            "description": "Optional expense ID to scope the upload reference to."
          },
          "checksumSha256": {
            "type": "string",
            "pattern": "^[A-Fa-f0-9]{64}$"
          }
        }
      },
      "FileAttachmentUpload": {
        "type": "object",
        "properties": {
          "uploadId": {
            "type": "string"
          },
          "fileReferenceId": {
            "type": "string",
            "description": "Opaque file reference to pass to attachExpenseFileReference."
          },
          "fileId": {
            "type": "string",
            "description": "Backward-compatible alias for fileReferenceId."
          },
          "fileName": {
            "type": "string"
          },
          "originalName": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "sizeBytes": {
            "type": "integer"
          },
          "size": {
            "type": "integer"
          },
          "maxSizeBytes": {
            "type": "integer"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "example": "prepared"
          },
          "attach": {
            "type": "object",
            "properties": {
              "expenseId": {
                "type": "string",
                "nullable": true
              },
              "endpoint": {
                "type": "string"
              },
              "body": {
                "type": "object"
              }
            }
          }
        }
      },
      "FileAttachmentInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "fileId": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9_-]+$",
            "description": "Opaque uploaded file reference. Not a path or URL."
          },
          "fileReferenceId": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9_-]+$",
            "description": "Opaque uploaded file reference. Not a path or URL."
          },
          "originalName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Filename only; local paths are rejected."
          },
          "fileName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Filename only; local paths are rejected."
          },
          "contentType": {
            "type": "string",
            "enum": [
              "application/pdf",
              "image/jpeg",
              "image/jpg",
              "image/png",
              "image/webp",
              "image/heic",
              "image/heif",
              "image/tiff"
            ]
          },
          "size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10485760
          },
          "sizeBytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10485760
          },
          "checksumSha256": {
            "type": "string",
            "pattern": "^[A-Fa-f0-9]{64}$"
          }
        }
      },
      "FileAttachment": {
        "type": "object",
        "required": [
          "fileId",
          "originalName",
          "contentType",
          "size"
        ],
        "additionalProperties": false,
        "properties": {
          "fileId": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9_-]+$",
            "description": "Opaque uploaded file reference. Not a path or URL."
          },
          "originalName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Filename only; local paths are rejected."
          },
          "contentType": {
            "type": "string",
            "enum": [
              "application/pdf",
              "image/jpeg",
              "image/jpg",
              "image/png",
              "image/webp",
              "image/heic",
              "image/heif",
              "image/tiff"
            ]
          },
          "size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10485760
          },
          "checksumSha256": {
            "type": "string",
            "pattern": "^[A-Fa-f0-9]{64}$"
          }
        }
      },
      "ExpenseCreate": {
        "type": "object",
        "required": [
          "description",
          "amount"
        ],
        "properties": {
          "description": {
            "type": "string",
            "description": "Expense description",
            "example": "Office supplies"
          },
          "amount": {
            "type": "number",
            "description": "Expense amount",
            "example": 45.99
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "Expense date in ISO format",
            "example": "2026-03-09"
          },
          "paidDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Effective payment date (fecha de pago, YYYY-MM-DD). Drives cash-basis period assignment for Modelos 111/115/130. When absent: under accrual (devengo) criteria the expense `date` is used; under the opt-in Modelo 130 cash-basis criterion (criterio de cobros y pagos) an absent paidDate means not yet paid and the expense is excluded until paid. On PATCH, an explicit null clears the field.",
            "example": "2026-03-12"
          },
          "category": {
            "type": "string",
            "description": "Expense category",
            "example": "office"
          },
          "vendor": {
            "type": "string",
            "description": "Vendor or supplier name",
            "example": "Office Depot"
          },
          "taxDeductible": {
            "type": "boolean",
            "description": "Whether the expense is tax deductible",
            "example": true
          },
          "currency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3,
            "description": "ISO 4217 transaction currency. Defaults to EUR.",
            "example": "EUR",
            "default": "EUR"
          },
          "exchangeRate": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Exchange rate used to convert amount into functionalCurrency."
          },
          "exchangeRateSource": {
            "type": "string",
            "maxLength": 200,
            "description": "Source/provider of the exchange rate snapshot."
          },
          "exchangeRateDate": {
            "type": "string",
            "format": "date",
            "description": "Exchange rate date (YYYY-MM-DD)."
          },
          "functionalCurrency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3,
            "description": "Workspace/reporting currency. Defaults to EUR.",
            "example": "EUR",
            "default": "EUR"
          },
          "functionalAmount": {
            "type": "number",
            "description": "Amount converted into functionalCurrency."
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileAttachment"
            },
            "readOnly": true
          },
          "recurring": {
            "$ref": "#/components/schemas/ExpenseRecurringConfig"
          }
        }
      },
      "Expense": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          {
            "$ref": "#/components/schemas/ExpenseCreate"
          }
        ]
      },
      "InvoiceRecurringConfig": {
        "type": "object",
        "required": [
          "enabled",
          "frequency",
          "nextDate"
        ],
        "description": "Recurring invoice configuration. Set on a template invoice to auto-generate copies on schedule.",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Whether recurring generation is active",
            "example": true
          },
          "frequency": {
            "type": "string",
            "enum": [
              "weekly",
              "biweekly",
              "monthly",
              "quarterly",
              "yearly"
            ],
            "description": "How often to generate a new invoice",
            "example": "monthly"
          },
          "nextDate": {
            "type": "string",
            "format": "date",
            "description": "Next scheduled generation date (ISO YYYY-MM-DD)",
            "example": "2026-04-01"
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "description": "Stop generating after this date (optional)",
            "example": "2027-03-31"
          },
          "maxOccurrences": {
            "type": "integer",
            "minimum": 1,
            "description": "Maximum number of invoices to generate (optional)",
            "example": 12
          },
          "autoSend": {
            "type": "boolean",
            "description": "Automatically send generated invoices to the client",
            "default": false,
            "example": false
          },
          "occurrencesGenerated": {
            "type": "integer",
            "readOnly": true,
            "description": "Number of invoices generated so far (server-managed, read-only)",
            "example": 0
          },
          "lastGeneratedId": {
            "type": "string",
            "readOnly": true,
            "description": "ID of the last auto-generated invoice (server-managed, read-only)"
          }
        }
      },
      "ExpenseRecurringConfig": {
        "type": "object",
        "required": [
          "enabled",
          "frequency",
          "nextDate"
        ],
        "description": "Recurring expense configuration. Set on a template expense to auto-generate copies on schedule.",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Whether recurring generation is active",
            "example": true
          },
          "frequency": {
            "type": "string",
            "enum": [
              "weekly",
              "biweekly",
              "monthly",
              "quarterly",
              "yearly"
            ],
            "description": "How often to generate a new expense",
            "example": "monthly"
          },
          "nextDate": {
            "type": "string",
            "format": "date",
            "description": "Next scheduled generation date (ISO YYYY-MM-DD)",
            "example": "2026-04-01"
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "description": "Stop generating after this date (optional)",
            "example": "2027-03-31"
          },
          "maxOccurrences": {
            "type": "integer",
            "minimum": 1,
            "description": "Maximum number of expenses to generate (optional)",
            "example": 12
          },
          "occurrencesGenerated": {
            "type": "integer",
            "readOnly": true,
            "description": "Number of expenses generated so far (server-managed, read-only)",
            "example": 0
          }
        }
      },
      "ClientCreate": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Client name (person or company)",
            "example": "Acme Corp"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Contact email",
            "example": "billing@acme.com"
          },
          "phone": {
            "type": "string",
            "description": "Phone number",
            "example": "+34 912 345 678"
          },
          "taxId": {
            "type": "string",
            "description": "Tax identification number (NIF/CIF/VAT)",
            "example": "B12345678"
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          },
          "integrationSource": {
            "type": "string",
            "enum": [
              "hubspot",
              "pipedrive",
              "intercom",
              "mailchimp"
            ],
            "description": "Source integration when the client is imported from an external CRM or marketing tool."
          },
          "nextAction": {
            "$ref": "#/components/schemas/ClientNextAction"
          }
        }
      },
      "ClientNextAction": {
        "type": "object",
        "required": [
          "title",
          "status"
        ],
        "additionalProperties": false,
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 500,
            "description": "Current next step for this client.",
            "example": "Call to validate the accepted quote"
          },
          "dueDate": {
            "type": "string",
            "format": "date",
            "description": "Optional due date for the next action.",
            "example": "2026-07-10"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "done"
            ],
            "description": "Whether the next action is still open or already completed."
          },
          "source": {
            "type": "string",
            "enum": [
              "manual",
              "api",
              "integration",
              "system"
            ],
            "description": "Origin of the next action."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Client": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          {
            "$ref": "#/components/schemas/ClientCreate"
          }
        ]
      },
      "ContactCreate": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Contact person name",
            "example": "Maria Garcia"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Contact email",
            "example": "maria@acme.es"
          },
          "phone": {
            "type": "string",
            "description": "Phone number",
            "example": "+34 612 345 678"
          },
          "role": {
            "type": "string",
            "description": "Job title or role",
            "example": "CFO"
          },
          "isPrimary": {
            "type": "boolean",
            "description": "Whether this is the primary contact for the client",
            "default": false
          }
        }
      },
      "Contact": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          {
            "$ref": "#/components/schemas/ContactCreate"
          }
        ]
      },
      "ActivityType": {
        "type": "string",
        "enum": [
          "call",
          "email",
          "email_sent",
          "meeting",
          "note_added",
          "task",
          "stage_changed",
          "client_created"
        ],
        "description": "CRM activity types accepted for manual/API-created entries. The legacy `email` value is stored as `email_sent`. Invoice, quote and expense system activity types are auto-generated."
      },
      "ActivityCreate": {
        "type": "object",
        "required": [
          "type",
          "title"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/ActivityType"
          },
          "title": {
            "type": "string",
            "description": "Descriptive title for the activity",
            "example": "Follow-up call on Q2 budget"
          },
          "description": {
            "type": "string",
            "description": "Detailed description",
            "example": "Discussed budget terms. Awaiting confirmation."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form additional data. For `task` activities, use string fields such as `status` and `dueDate` to mirror the client's current next action."
          }
        }
      },
      "Activity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique identifier"
          },
          "type": {
            "type": "string",
            "description": "Activity type (manual or system-generated)"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "NoteCreate": {
        "type": "object",
        "required": [
          "content"
        ],
        "properties": {
          "content": {
            "type": "string",
            "description": "Note content",
            "example": "Client interested in Business plan. Follow up in April."
          }
        }
      },
      "Note": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          {
            "$ref": "#/components/schemas/NoteCreate"
          }
        ]
      },
      "ProductCreate": {
        "type": "object",
        "required": [
          "name",
          "unitPrice"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Product or service name",
            "example": "Consulting Hour"
          },
          "unitPrice": {
            "type": "number",
            "description": "Unit price",
            "example": 120
          },
          "description": {
            "type": "string",
            "description": "Product description",
            "example": "One hour of strategic consulting"
          },
          "taxRate": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Default tax rate percentage",
            "example": 21
          }
        }
      },
      "Product": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          {
            "$ref": "#/components/schemas/ProductCreate"
          }
        ]
      },
      "QuoteCreate": {
        "type": "object",
        "required": [
          "clientName",
          "items"
        ],
        "properties": {
          "clientName": {
            "type": "string",
            "description": "Client name",
            "example": "Acme Corp"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItem"
            },
            "minItems": 1
          },
          "validUntil": {
            "type": "string",
            "format": "date",
            "description": "Quote expiration date",
            "example": "2026-04-09"
          },
          "status": {
            "$ref": "#/components/schemas/QuoteStatus"
          },
          "notes": {
            "type": "string",
            "description": "Additional notes",
            "example": "Valid for 30 days"
          }
        }
      },
      "Quote": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          {
            "$ref": "#/components/schemas/QuoteCreate"
          },
          {
            "type": "object",
            "properties": {
              "documentNumber": {
                "type": "string",
                "description": "Auto-generated quote number",
                "example": "PRE-2026-0015"
              },
              "subtotal": {
                "type": "number",
                "description": "Subtotal before tax"
              },
              "tax": {
                "type": "number",
                "description": "Tax amount"
              },
              "total": {
                "type": "number",
                "description": "Total amount including tax"
              }
            }
          }
        ]
      },
      "SendEmailRequest": {
        "type": "object",
        "required": [
          "recipientEmail"
        ],
        "properties": {
          "recipientEmail": {
            "type": "string",
            "format": "email",
            "description": "Recipient email address",
            "example": "client@example.com"
          },
          "recipientName": {
            "type": "string",
            "description": "Recipient name",
            "example": "John Doe"
          },
          "customMessage": {
            "type": "string",
            "description": "Optional custom message to include in the email",
            "example": "Please find attached your invoice."
          },
          "locale": {
            "type": "string",
            "enum": [
              "es",
              "en"
            ],
            "default": "es",
            "description": "Email language"
          }
        }
      },
      "Summary": {
        "type": "object",
        "properties": {
          "period": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "format": "date",
                "nullable": true
              },
              "to": {
                "type": "string",
                "format": "date",
                "nullable": true
              }
            }
          },
          "revenue": {
            "type": "object",
            "properties": {
              "invoiced": {
                "type": "number",
                "description": "Total invoiced amount",
                "example": 25000
              },
              "paid": {
                "type": "number",
                "description": "Total paid amount",
                "example": 18500
              },
              "pending": {
                "type": "number",
                "description": "Total pending (sent but unpaid)",
                "example": 4500
              },
              "overdue": {
                "type": "number",
                "description": "Total overdue amount",
                "example": 2000
              }
            }
          },
          "expenses": {
            "type": "object",
            "properties": {
              "total": {
                "type": "number",
                "description": "Total expenses",
                "example": 8200
              }
            }
          },
          "profit": {
            "type": "number",
            "description": "Net profit (paid revenue minus expenses)",
            "example": 10300
          },
          "counts": {
            "type": "object",
            "properties": {
              "invoices": {
                "type": "integer",
                "example": 42
              },
              "quotes": {
                "type": "integer",
                "example": 15
              },
              "expenses": {
                "type": "integer",
                "example": 87
              },
              "clients": {
                "type": "integer",
                "example": 23
              },
              "products": {
                "type": "integer",
                "example": 12
              }
            }
          },
          "invoicesByStatus": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "example": {
              "draft": 3,
              "sent": 8,
              "paid": 28,
              "overdue": 3
            }
          },
          "overdue": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer",
                "example": 3
              },
              "amount": {
                "type": "number",
                "example": 2000
              }
            }
          }
        }
      },
      "PaginatedResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {}
          },
          "total": {
            "type": "integer",
            "description": "Total number of items matching the query"
          },
          "limit": {
            "type": "integer",
            "description": "Number of items per page"
          },
          "offset": {
            "type": "integer",
            "description": "Current offset"
          },
          "nextCursor": {
            "type": "string",
            "description": "Cursor for fetching the next page of results. Only present when there\nare more results available. Pass this value as the `cursor` query parameter\nin the next request. Base64url-encoded.\n",
            "example": "eyJpc3N1ZURhdGUiOiIyMDI2LTAzLTIwIiwiX19pZCI6ImFiYzEyMyJ9"
          },
          "truncated": {
            "type": "boolean",
            "description": "Present and true when the result set was truncated. Narrow the search query to retrieve a complete result set.",
            "example": true
          }
        }
      },
      "DepositStatus": {
        "type": "string",
        "enum": [
          "active",
          "partially_applied",
          "fully_applied",
          "refunded"
        ]
      },
      "DepositCreate": {
        "type": "object",
        "required": [
          "clientId",
          "clientName",
          "amount",
          "description",
          "receivedDate"
        ],
        "properties": {
          "clientId": {
            "type": "string",
            "description": "ID of the client who made the deposit",
            "example": "client_abc123"
          },
          "clientName": {
            "type": "string",
            "description": "Client name (denormalized for display)",
            "example": "Acme Corp"
          },
          "amount": {
            "type": "number",
            "description": "Deposit amount (must be positive)",
            "example": 750
          },
          "currency": {
            "type": "string",
            "description": "Currency code (ISO 4217)",
            "default": "EUR",
            "example": "EUR"
          },
          "description": {
            "type": "string",
            "description": "Description of the deposit",
            "example": "Prepayment for Q2 project"
          },
          "receivedDate": {
            "type": "string",
            "format": "date",
            "description": "Date the deposit was received (YYYY-MM-DD)",
            "example": "2026-04-01"
          },
          "paymentMethod": {
            "type": "string",
            "description": "Payment method used",
            "example": "bank_transfer"
          },
          "paymentReference": {
            "type": "string",
            "description": "Payment reference or transaction ID",
            "example": "TRF-2026-0042"
          }
        }
      },
      "Deposit": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier",
                "example": "dep_abc123"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          {
            "$ref": "#/components/schemas/DepositCreate"
          },
          {
            "type": "object",
            "properties": {
              "status": {
                "$ref": "#/components/schemas/DepositStatus"
              },
              "appliedAmount": {
                "type": "number",
                "description": "Total amount already applied to invoices",
                "example": 250
              },
              "remainingBalance": {
                "type": "number",
                "description": "Remaining balance available for application or refund",
                "example": 500
              },
              "refundedAmount": {
                "type": "number",
                "description": "Total amount refunded to the client",
                "example": 0
              },
              "applications": {
                "type": "array",
                "description": "List of invoice applications for this deposit",
                "items": {
                  "type": "object",
                  "properties": {
                    "invoiceId": {
                      "type": "string"
                    },
                    "invoiceNumber": {
                      "type": "string"
                    },
                    "amount": {
                      "type": "number"
                    },
                    "appliedAt": {
                      "type": "string",
                      "format": "date"
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "VendorCreate": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Vendor name (person or company)",
            "example": "Office Depot"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Contact email",
            "example": "billing@officedepot.com"
          },
          "phone": {
            "type": "string",
            "description": "Phone number",
            "example": "+34 912 345 678"
          },
          "taxId": {
            "type": "string",
            "description": "Tax identification number (NIF/CIF/VAT)",
            "example": "B87654321"
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "Vendor": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          {
            "$ref": "#/components/schemas/VendorCreate"
          }
        ]
      },
      "WebhookCreate": {
        "type": "object",
        "required": [
          "name",
          "url",
          "events"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Webhook name",
            "maxLength": 200,
            "example": "Invoice notifications"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Endpoint URL to receive webhook events",
            "maxLength": 2000,
            "example": "https://example.com/webhooks/frihet"
          },
          "secret": {
            "type": "string",
            "description": "HMAC secret for verifying webhook signatures. Write-only: echoed once in the create response, never returned by read endpoints (see hasSecret).",
            "maxLength": 500
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 100
            },
            "minItems": 1,
            "description": "List of event types to subscribe to",
            "example": [
              "invoice.created",
              "invoice.paid",
              "expense.created"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive"
            ],
            "default": "active"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Arbitrary metadata to attach to the webhook"
          }
        }
      },
      "WebhookUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2000
          },
          "secret": {
            "type": "string",
            "description": "HMAC secret for verifying webhook signatures. Write-only: never returned by read endpoints (see hasSecret).",
            "maxLength": 500
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive",
              "paused"
            ]
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Webhook": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Unique identifier"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              },
              "userId": {
                "type": "string"
              },
              "hasSecret": {
                "type": "boolean",
                "description": "True if an HMAC secret is configured. The secret value itself is never returned by read endpoints."
              }
            }
          },
          {
            "$ref": "#/components/schemas/WebhookCreate"
          }
        ]
      },
      "BatchResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer",
                  "description": "Index of the item in the original request array"
                },
                "success": {
                  "type": "boolean"
                },
                "data": {
                  "type": "object",
                  "description": "Created resource (only when success is true)"
                },
                "error": {
                  "type": "string",
                  "description": "Error message (only when success is false)"
                },
                "details": {
                  "type": "array",
                  "description": "Validation error details (only on validation failure)",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer",
                "description": "Total items in request",
                "example": 10
              },
              "succeeded": {
                "type": "integer",
                "description": "Number of successfully created items",
                "example": 8
              },
              "failed": {
                "type": "integer",
                "description": "Number of failed items",
                "example": 2
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "requestId": {
                "type": "string"
              },
              "timestamp": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "BusinessContext": {
        "type": "object",
        "description": "Comprehensive business context snapshot for AI agents",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "business": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "taxId": {
                    "type": "string"
                  },
                  "fiscalZone": {
                    "type": "string"
                  },
                  "currency": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string"
                  }
                }
              },
              "defaults": {
                "type": "object",
                "properties": {
                  "taxRate": {
                    "type": "number"
                  },
                  "irpfRate": {
                    "type": "number"
                  },
                  "dueDays": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string"
                  }
                }
              },
              "plan": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "pro"
                  },
                  "invoices": {
                    "type": "object",
                    "properties": {
                      "used": {
                        "type": "integer"
                      },
                      "limit": {
                        "type": "integer"
                      }
                    }
                  },
                  "expenses": {
                    "type": "object",
                    "properties": {
                      "used": {
                        "type": "integer"
                      },
                      "limit": {
                        "type": "integer"
                      }
                    }
                  },
                  "aiMessages": {
                    "type": "object",
                    "properties": {
                      "used": {
                        "type": "integer"
                      },
                      "limit": {
                        "type": "integer"
                      }
                    }
                  }
                }
              },
              "series": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "prefix": {
                      "type": "string"
                    },
                    "current": {
                      "type": "integer"
                    },
                    "year": {
                      "type": "integer"
                    }
                  }
                }
              },
              "recentActivity": {
                "type": "object",
                "properties": {
                  "lastInvoice": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "number": {
                        "type": "string"
                      },
                      "date": {
                        "type": "string"
                      },
                      "client": {
                        "type": "string"
                      }
                    }
                  },
                  "lastExpense": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "date": {
                        "type": "string"
                      },
                      "vendor": {
                        "type": "string"
                      },
                      "amount": {
                        "type": "number"
                      }
                    }
                  },
                  "overdueCount": {
                    "type": "integer"
                  },
                  "overdueAmount": {
                    "type": "number"
                  },
                  "unpaidCount": {
                    "type": "integer"
                  }
                }
              },
              "topClients": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "totalRevenue": {
                      "type": "number"
                    },
                    "invoiceCount": {
                      "type": "integer"
                    }
                  }
                }
              },
              "currentMonth": {
                "type": "object",
                "properties": {
                  "revenue": {
                    "type": "number"
                  },
                  "expenses": {
                    "type": "number"
                  },
                  "profit": {
                    "type": "number"
                  },
                  "invoiceCount": {
                    "type": "integer"
                  },
                  "expenseCount": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "requestId": {
                "type": "string"
              },
              "timestamp": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "MonthlySummary": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "period": {
                "type": "string",
                "example": "2026-03"
              },
              "revenue": {
                "type": "object",
                "properties": {
                  "total": {
                    "type": "number"
                  },
                  "taxBase": {
                    "type": "number"
                  },
                  "tax": {
                    "type": "number"
                  },
                  "irpf": {
                    "type": "number"
                  }
                }
              },
              "expenses": {
                "type": "object",
                "properties": {
                  "total": {
                    "type": "number"
                  },
                  "deductible": {
                    "type": "number"
                  },
                  "tax": {
                    "type": "number"
                  }
                }
              },
              "profit": {
                "type": "object",
                "properties": {
                  "gross": {
                    "type": "number"
                  },
                  "net": {
                    "type": "number"
                  }
                }
              },
              "invoices": {
                "type": "object",
                "properties": {
                  "created": {
                    "type": "integer"
                  },
                  "sent": {
                    "type": "integer"
                  },
                  "paid": {
                    "type": "integer"
                  },
                  "overdue": {
                    "type": "integer"
                  }
                }
              },
              "topClients": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "totalRevenue": {
                      "type": "number"
                    },
                    "invoiceCount": {
                      "type": "integer"
                    }
                  }
                }
              },
              "byCategory": {
                "type": "object",
                "additionalProperties": {
                  "type": "number"
                },
                "description": "Expense totals grouped by category"
              },
              "taxLiability": {
                "type": "object",
                "properties": {
                  "vatPayable": {
                    "type": "number",
                    "description": "Net VAT payable (output - input)"
                  },
                  "irpfRetained": {
                    "type": "number"
                  },
                  "estimatedModel303": {
                    "type": "number"
                  }
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "requestId": {
                "type": "string"
              },
              "timestamp": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "QuarterlySummary": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "period": {
                "type": "string",
                "example": "2026-Q1"
              },
              "months": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [
                  "2026-01",
                  "2026-02",
                  "2026-03"
                ]
              },
              "modelo303": {
                "type": "object",
                "description": "Pre-filled Modelo 303 (VAT return) figures",
                "properties": {
                  "baseImponible": {
                    "type": "number",
                    "description": "Total tax base"
                  },
                  "cuotaRepercutida": {
                    "type": "number",
                    "description": "Output VAT (charged to clients)"
                  },
                  "baseDeducible": {
                    "type": "number",
                    "description": "Deductible expense base"
                  },
                  "cuotaDeducible": {
                    "type": "number",
                    "description": "Input VAT (paid on deductible expenses)"
                  },
                  "resultado": {
                    "type": "number",
                    "description": "Net VAT (output - input)"
                  }
                }
              },
              "modelo130": {
                "type": "object",
                "description": "Pre-filled Modelo 130 (quarterly income payment) figures",
                "properties": {
                  "ingresos": {
                    "type": "number",
                    "description": "Total income (tax base)"
                  },
                  "gastos": {
                    "type": "number",
                    "description": "Total deductible expenses"
                  },
                  "rendimientoNeto": {
                    "type": "number",
                    "description": "Net income (ingresos - gastos)"
                  },
                  "pagoFraccionado": {
                    "type": "number",
                    "description": "Estimated quarterly payment (20% of net income)"
                  }
                }
              },
              "summary": {
                "type": "object",
                "properties": {
                  "totalRevenue": {
                    "type": "number"
                  },
                  "totalExpenses": {
                    "type": "number"
                  },
                  "invoiceCount": {
                    "type": "integer"
                  },
                  "clientCount": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "requestId": {
                "type": "string"
              },
              "timestamp": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message"
          },
          "details": {
            "type": "array",
            "description": "Validation error details (Zod issues). Only present on 400 validation errors.",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                },
                "path": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "Unique request identifier for tracing"
              }
            }
          }
        }
      },
      "ReservationStatus": {
        "type": "string",
        "enum": [
          "Pending",
          "Confirmed",
          "Checked In",
          "Checked Out",
          "Cancelled"
        ]
      },
      "PropertyStatus": {
        "type": "string",
        "enum": [
          "Active",
          "Maintenance",
          "Inactive"
        ]
      },
      "ChannelStatus": {
        "type": "string",
        "enum": [
          "active",
          "paused",
          "error"
        ]
      },
      "Reservation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "guestName": {
            "type": "string"
          },
          "guestEmail": {
            "type": "string",
            "nullable": true
          },
          "propertyId": {
            "type": "string"
          },
          "checkIn": {
            "type": "string",
            "format": "date-time"
          },
          "checkOut": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "$ref": "#/components/schemas/ReservationStatus"
          },
          "amount": {
            "type": "number"
          },
          "channel": {
            "type": "string"
          },
          "confirmationCode": {
            "type": "string",
            "nullable": true
          },
          "complianceStatus": {
            "type": "string"
          },
          "nights": {
            "type": "integer"
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Property": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PropertyStatus"
          },
          "ownerName": {
            "type": "string"
          },
          "ownerEmail": {
            "type": "string",
            "nullable": true
          },
          "commissionRate": {
            "type": "number"
          },
          "licenseNumber": {
            "type": "string",
            "nullable": true
          },
          "maxGuests": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Guest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "guestName": {
            "type": "string"
          },
          "documentType": {
            "type": "string",
            "nullable": true,
            "enum": [
              "DNI",
              "Passport",
              "NIE"
            ]
          },
          "documentNumber": {
            "type": "string",
            "nullable": true,
            "description": "Redacted for privacy"
          },
          "nationality": {
            "type": "string",
            "nullable": true
          },
          "reservationId": {
            "type": "string"
          },
          "documentVerified": {
            "type": "boolean"
          },
          "signatureCaptured": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ChannelCreate": {
        "type": "object",
        "required": [
          "propertyId",
          "name"
        ],
        "properties": {
          "propertyId": {
            "type": "string",
            "description": "ID of the property this channel belongs to"
          },
          "name": {
            "type": "string",
            "description": "Channel name (e.g., \"Airbnb\", \"Booking.com\")",
            "example": "Airbnb"
          },
          "type": {
            "type": "string",
            "enum": [
              "ical_import",
              "ical_export",
              "api"
            ],
            "default": "ical_import"
          },
          "feedUrl": {
            "type": "string",
            "format": "uri",
            "description": "iCal feed URL for import",
            "example": "https://www.airbnb.com/calendar/ical/12345.ics"
          },
          "status": {
            "$ref": "#/components/schemas/ChannelStatus"
          }
        }
      },
      "Channel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "propertyId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "feedUrl": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "lastSync": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastSyncEvents": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TeamMember": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Member or invitation document ID"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "nullable": true,
            "enum": [
              "owner",
              "admin",
              "viewer",
              "editor",
              "accountant"
            ],
            "description": "Null only on an invitation row whose persisted role is not an assignable team role. Such a row is always reported with `status: invalid`; the API will not invent a valid role for it. Revoke the row via DELETE /v1/team/members/{memberId} to release the seat it holds."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "pending",
              "expired",
              "invalid"
            ],
            "description": "Effective state computed at read time, not merely the persisted field. `expired` is only reported when expiry is PROVEN and the row holds no seat; `invalid` means validity could not be proven and the row conservatively still holds its seat."
          },
          "joinedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Set for active members"
          },
          "invitedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Set for pending invitations"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Invite expiry (pending only)"
          }
        }
      },
      "TeamInviteRequest": {
        "type": "object",
        "required": [
          "email",
          "role"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 255
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "editor",
              "accountant",
              "viewer"
            ]
          },
          "name": {
            "type": "string",
            "maxLength": 200
          }
        },
        "additionalProperties": false
      },
      "TeamRoleUpdateRequest": {
        "type": "object",
        "required": [
          "role"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "editor",
              "accountant",
              "viewer"
            ]
          }
        },
        "additionalProperties": false
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad request - invalid parameters or request body",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Validation error",
              "details": [
                {
                  "code": "invalid_type",
                  "message": "Required",
                  "path": [
                    "clientName"
                  ]
                }
              ],
              "meta": {
                "requestId": "550e8400-e29b-41d4-a716-446655440000"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Authentication required or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Invalid or expired API key",
              "meta": {
                "requestId": "550e8400-e29b-41d4-a716-446655440000"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "Insufficient permissions",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Insufficient permissions",
              "meta": {
                "requestId": "550e8400-e29b-41d4-a716-446655440000"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Resource not found",
              "meta": {
                "requestId": "550e8400-e29b-41d4-a716-446655440000"
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (100 requests per minute)",
        "headers": {
          "Retry-After": {
            "description": "Integer seconds until the current rate-limit window resets",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 60
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "example": "Rate limit exceeded"
                },
                "message": {
                  "type": "string",
                  "example": "Maximum 100 requests per minute"
                },
                "retryAfter": {
                  "type": "integer",
                  "description": "Seconds until rate limit resets",
                  "example": 60
                },
                "meta": {
                  "type": "object",
                  "properties": {
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Internal server error",
              "meta": {
                "requestId": "550e8400-e29b-41d4-a716-446655440000"
              }
            }
          }
        }
      }
    }
  }
}
