{
  "openapi": "3.1.0",
  "info": {
    "title": "Invly API",
    "version": "1.0.0",
    "summary": "REST API for Invly, Lithuanian VAT invoicing and bookkeeping software.",
    "description": "Read and write the same invoices, clients, products and expenses a user sees in the Invly app (https://www.invly.lt). All amounts are EUR, VAT rates are Lithuanian (21, 12, 5, 0 %).\n\nAccess: the Komandos plan (or a Verslas plan during its trial). Create a key in the app at https://app.invly.lt/app/api, or let an AI connector obtain one through OAuth 2.0 (authorization code + PKCE, metadata at https://www.invly.lt/.well-known/oauth-authorization-server). Both give a bearer token of the form `ily_...`.\n\nRate limits: 60 requests per minute per key and 300 per minute per IP address. Every response carries `RateLimit` and `RateLimit-Policy` headers (IETF draft-ietf-httpapi-ratelimit-headers): the per-key `\"api-v1\"` policy once the key is accepted, the per-IP `\"api-v1-ip\"` policy before that (health check, 401). A 429 adds `Retry-After` in seconds.\n\nErrors always have the shape `{ \"error\": { \"code\": \"...\", \"message\": \"...\" } }`. Invoice status values are Lithuanian because they are stored that way: juodraštis (draft), išsiųsta (sent), apmokėta (paid), vėluoja (overdue, derived), atšaukta (cancelled).\n\nVersioning and deprecation: the major version is in the path (`/v1`). Within v1 changes are additive only: new endpoints, new optional request fields, new response fields and new enum values, which clients should ignore or tolerate. A breaking change ships under a new path (`/v2`) while `/v1` keeps working. Before an endpoint or field is removed, its responses carry a `Deprecation` header (RFC 9745) and a `Sunset` header (RFC 8594) with the removal date, and the change is announced on https://www.invly.lt/docs/api, at least 90 days ahead.\n\nAI agents: an MCP server with the same data is at https://www.invly.lt/mcp (streamable HTTP, OAuth or the same bearer key).",
    "contact": { "name": "Invly", "email": "info@invly.lt", "url": "https://www.invly.lt/docs/api" }
  },
  "externalDocs": { "description": "API documentation", "url": "https://www.invly.lt/docs/api" },
  "servers": [{ "url": "https://www.invly.lt/api", "description": "Production" }],
  "security": [{ "bearerApiKey": [] }, { "oauth2": ["read"] }],
  "tags": [
    { "name": "Meta", "description": "Service information." },
    { "name": "Invoices", "description": "Invoices, advance invoices, quotes and credit notes." },
    { "name": "Clients", "description": "The user's customers." },
    { "name": "Products", "description": "The user's product and service catalog." },
    { "name": "Expenses", "description": "Purchase invoices and receipts (costs)." }
  ],
  "paths": {
    "/v1": {
      "get": {
        "operationId": "getApiInfo",
        "tags": ["Meta"],
        "summary": "Health check",
        "description": "Returns the API name, version and documentation URL. Needs no authentication. Limited per IP address only (300 per minute), and the response carries that policy in its RateLimit headers.",
        "security": [],
        "responses": {
          "200": {
            "description": "API is up.",
            "headers": {
              "RateLimit": { "$ref": "#/components/headers/RateLimit" },
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data"],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "name": { "type": "string", "examples": ["Invly API"] },
                        "version": { "type": "string", "examples": ["v1"] },
                        "docs": { "type": "string", "format": "uri" }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/invoices": {
      "get": {
        "operationId": "listInvoices",
        "tags": ["Invoices"],
        "summary": "List invoices",
        "description": "Lists the user's invoices, newest issue date first, without lines. Use getInvoice for the lines.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["read"] }],
        "parameters": [
          { "name": "limit", "in": "query", "description": "Maximum rows to return.", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } },
          { "name": "status", "in": "query", "description": "Only invoices with this status.", "schema": { "$ref": "#/components/schemas/InvoiceStatus" } },
          { "name": "since", "in": "query", "description": "Only invoices created at or after this ISO date or datetime.", "schema": { "type": "string", "examples": ["2026-01-01"] } }
        ],
        "responses": {
          "200": {
            "description": "Invoices.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/InvoiceSummary" } } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      },
      "post": {
        "operationId": "createInvoice",
        "tags": ["Invoices"],
        "summary": "Create an invoice",
        "description": "Creates an invoice (or advance invoice, quote, credit note) with its lines. The number is allocated from the user's numbering sequence when invoice_number is omitted, and totals are computed server-side. Created as a draft unless status says otherwise; it is never emailed by the API.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["write"] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InvoiceCreate" } } }
        },
        "responses": {
          "201": {
            "description": "Created invoice with lines.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "$ref": "#/components/schemas/Invoice" } } } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/invoices/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/Id" }],
      "get": {
        "operationId": "getInvoice",
        "tags": ["Invoices"],
        "summary": "Get one invoice",
        "description": "Returns a single invoice with all its lines.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["read"] }],
        "responses": {
          "200": {
            "description": "Invoice with lines.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "$ref": "#/components/schemas/Invoice" } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      },
      "patch": {
        "operationId": "updateInvoiceStatus",
        "tags": ["Invoices"],
        "summary": "Change status or record payment",
        "description": "Changes the status and records the payment. Setting apmokėta stamps paid_at (now, or the given paid_at) and sent_at if missing; reverting to juodraštis clears both. A status change fires the matching webhook. Quotes and delivery notes cannot be paid (409 not_payable). Amounts and lines cannot be edited through the API.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["write"] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InvoicePatch" } } }
        },
        "responses": {
          "200": {
            "description": "Updated invoice with lines.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "$ref": "#/components/schemas/Invoice" } } } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      },
      "delete": {
        "operationId": "deleteInvoice",
        "tags": ["Invoices"],
        "summary": "Delete a draft, quote or delivery note",
        "description": "Deletes an invoice with its lines. Financial documents (saskaita, isankstine, kreditine) can be deleted only as a draft; an issued one returns 409 not_draft and should be PATCHed to atšaukta instead. Quotes and delivery notes can be deleted in any status. The number is never reused.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["write"] }],
        "responses": {
          "200": {
            "description": "Deleted.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data"],
                  "properties": { "data": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "deleted": { "type": "boolean", "const": true } } } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/clients": {
      "get": {
        "operationId": "listClients",
        "tags": ["Clients"],
        "summary": "List clients",
        "description": "Lists the user's clients, newest first.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["read"] }],
        "parameters": [
          { "name": "limit", "in": "query", "description": "Maximum rows to return.", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 100 } }
        ],
        "responses": {
          "200": {
            "description": "Clients.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Client" } } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      },
      "post": {
        "operationId": "createClient",
        "tags": ["Clients"],
        "summary": "Create a client",
        "description": "Creates a client. Only name is required. Use the returned id as client_id when creating invoices.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["write"] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientCreate" } } }
        },
        "responses": {
          "201": {
            "description": "Created client.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "$ref": "#/components/schemas/Client" } } } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/products": {
      "get": {
        "operationId": "listProducts",
        "tags": ["Products"],
        "summary": "List products and services",
        "description": "Lists the user's product and service catalog, ordered by name.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["read"] }],
        "responses": {
          "200": {
            "description": "Catalog.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/expenses": {
      "get": {
        "operationId": "listExpenses",
        "tags": ["Expenses"],
        "summary": "List expenses",
        "description": "Lists the user's expenses, newest document date first, with VAT treatment, cost type and document number.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["read"] }],
        "parameters": [
          { "name": "limit", "in": "query", "description": "Maximum rows to return.", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 100 } }
        ],
        "responses": {
          "200": {
            "description": "Expenses.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Expense" } } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      },
      "post": {
        "operationId": "createExpense",
        "tags": ["Expenses"],
        "summary": "Record an expense",
        "description": "Records a purchase document. amount is the total INCLUDING VAT and vat_amount the VAT part of it. Omitted vat_treatment and cost_type take defaults and stay flagged for review in the app. A row matching an existing expense (document number, or supplier + amount + date) is saved linked as a suspected duplicate, never dropped.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["write"] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExpenseCreate" } } }
        },
        "responses": {
          "201": {
            "description": "Recorded expense.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "$ref": "#/components/schemas/Expense" } } } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/expenses/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/Id" }],
      "patch": {
        "operationId": "updateExpensePayment",
        "tags": ["Expenses"],
        "summary": "Mark an expense paid or clear the mark",
        "description": "Sets or clears the manual payment mark: paid_at and paid_method together, or both null to clear. Only bank is accepted; cash returns 409 cash_needs_kio because a cash payment is proven by a cash payment order (KIO) in the app. Amounts and VAT cannot be edited through the API.",
        "security": [{ "bearerApiKey": [] }, { "oauth2": ["write"] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExpensePatch" } } }
        },
        "responses": {
          "200": {
            "description": "Updated expense.",
            "headers": { "RateLimit": { "$ref": "#/components/headers/RateLimit" }, "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" } },
            "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "$ref": "#/components/schemas/Expense" } } } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PlanRequired" },
          "403": { "$ref": "#/components/responses/ScopeInsufficient" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ily_<key>",
        "description": "API key created at https://app.invly.lt/app/api, sent as `Authorization: Bearer ily_...`. Keys have the read or read+write scope."
      },
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.0 authorization code flow with PKCE (S256) and dynamic client registration at https://www.invly.lt/oauth/register. The access token is an ordinary Invly API key. Metadata: https://www.invly.lt/.well-known/oauth-authorization-server",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://www.invly.lt/app/oauth/authorize",
            "tokenUrl": "https://www.invly.lt/oauth/token",
            "scopes": {
              "read": "Read invoices, clients, products and expenses.",
              "write": "Create invoices, clients and expenses; change invoice status; mark payments."
            }
          }
        }
      }
    },
    "parameters": {
      "Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Resource id (UUID).",
        "schema": { "type": "string", "format": "uuid" }
      }
    },
    "headers": {
      "RateLimit": {
        "description": "Remaining quota in the current window: `\"api-v1\";r=<remaining>;t=<seconds>` (IETF draft-ietf-httpapi-ratelimit-headers).",
        "schema": { "type": "string", "examples": ["\"api-v1\";r=59;t=60"] }
      },
      "RateLimit-Policy": {
        "description": "Quota policy: `\"api-v1\";q=60;w=60`, 60 requests per 60 seconds per key, on responses to an accepted key. Before the key is accepted (health check, 401) it is the per-IP policy `\"api-v1-ip\";q=300;w=60`.",
        "schema": { "type": "string", "examples": ["\"api-v1\";q=60;w=60"] }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (on 429).",
        "schema": { "type": "integer", "examples": [60] }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid JSON (invalid_json) or a field failed validation (validation, invalid_client_id, invalid_bank_account_id); message explains which.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Missing or malformed Authorization header (unauthenticated), or the key is unknown or revoked (invalid_key).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "PlanRequired": {
        "description": "The account's plan does not include the API (plan_required).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ScopeInsufficient": {
        "description": "The key lacks the read or write scope this operation needs (scope_insufficient).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "No such resource for this account (not_found).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Conflict": {
        "description": "The change breaks a bookkeeping rule: not_draft, not_payable or cash_needs_kio.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Too many requests (rate_limit). Wait Retry-After seconds.",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/Retry-After" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ServerError": {
        "description": "Internal error (server_error). Retry; if it persists, contact info@invly.lt.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string", "description": "Machine-readable error code.", "examples": ["validation"] },
              "message": { "type": "string", "description": "Human-readable explanation." }
            }
          }
        }
      },
      "InvoiceStatus": {
        "type": "string",
        "description": "juodraštis = draft, išsiųsta = sent, apmokėta = paid, vėluoja = overdue (derived from due_date, never set directly), atšaukta = cancelled.",
        "enum": ["juodraštis", "išsiųsta", "apmokėta", "vėluoja", "atšaukta"]
      },
      "InvoiceType": {
        "type": "string",
        "description": "saskaita = invoice, isankstine = advance invoice, pasiulymas = quote, kreditine = credit note (amounts stored positive), vaztarastis = delivery note.",
        "enum": ["saskaita", "isankstine", "pasiulymas", "kreditine", "vaztarastis"]
      },
      "VatScheme": {
        "type": "string",
        "description": "standard adds VAT per line; reverse_charge and the margin schemes add none (total = subtotal).",
        "enum": ["standard", "margin_used", "margin_art", "margin_travel", "reverse_charge"]
      },
      "ClientSnapshot": {
        "type": "object",
        "description": "Buyer details frozen on the invoice.",
        "properties": {
          "name": { "type": ["string", "null"] },
          "client_type": { "type": ["string", "null"], "enum": ["business", "individual", null] },
          "first_name": { "type": ["string", "null"] },
          "last_name": { "type": ["string", "null"] },
          "company_code": { "type": ["string", "null"], "description": "Lithuanian company code (įmonės kodas)." },
          "vat_code": { "type": ["string", "null"], "description": "VAT payer code, e.g. LT100001234567." },
          "address": { "type": ["string", "null"] },
          "email": { "type": ["string", "null"] },
          "phone": { "type": ["string", "null"] },
          "country": { "type": ["string", "null"] }
        }
      },
      "InvoiceLine": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "invoice_id": { "type": "string", "format": "uuid" },
          "description": { "type": ["string", "null"] },
          "quantity": { "type": "number" },
          "unit": { "type": ["string", "null"] },
          "unit_price": { "type": "number", "description": "Price per unit excluding VAT, EUR." },
          "vat_rate": { "type": "number", "description": "VAT percent; -1 means a margin-scheme line shown without VAT." },
          "kind": { "type": ["string", "null"], "enum": ["goods", "services", null] },
          "line_total": { "type": "number", "description": "quantity x unit_price, excluding VAT." },
          "sort_order": { "type": "integer" }
        }
      },
      "InvoiceSummary": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "invoice_number": { "type": "string", "examples": ["SF-2026-042"] },
          "status": { "$ref": "#/components/schemas/InvoiceStatus" },
          "issue_date": { "type": "string", "format": "date" },
          "due_date": { "type": ["string", "null"], "format": "date" },
          "subtotal": { "type": "number", "description": "Total excluding VAT, EUR." },
          "vat_amount": { "type": "number" },
          "total": { "type": "number", "description": "Total including VAT, EUR." },
          "invoice_type": { "$ref": "#/components/schemas/InvoiceType" },
          "long_term_supply": { "type": ["boolean", "null"] },
          "client_snapshot": { "$ref": "#/components/schemas/ClientSnapshot" },
          "created_at": { "type": "string", "format": "date-time" },
          "paid_at": { "type": ["string", "null"], "format": "date-time" },
          "public_token": { "type": ["string", "null"], "description": "Token of the public invoice page https://portal.invly.lt/i/<public_token>." }
        }
      },
      "Invoice": {
        "description": "A full invoice row with its lines. Further columns used by the app may appear and are safe to ignore.",
        "allOf": [
          { "$ref": "#/components/schemas/InvoiceSummary" },
          {
            "type": "object",
            "properties": {
              "client_id": { "type": ["string", "null"], "format": "uuid" },
              "notes": { "type": ["string", "null"] },
              "vat_scheme": { "$ref": "#/components/schemas/VatScheme" },
              "discount": { "type": "number" },
              "sent_at": { "type": ["string", "null"], "format": "date-time" },
              "paid_method": { "type": ["string", "null"], "enum": ["bank", "cash", null] },
              "currency": { "type": ["string", "null"], "description": "Null means EUR." },
              "bank_account_id": { "type": ["string", "null"], "format": "uuid" },
              "lines": { "type": "array", "items": { "$ref": "#/components/schemas/InvoiceLine" } }
            }
          }
        ]
      },
      "InvoiceLineInput": {
        "type": "object",
        "required": ["description", "quantity", "unit_price"],
        "properties": {
          "description": { "type": "string", "minLength": 1, "maxLength": 500 },
          "quantity": { "type": "number", "exclusiveMinimum": 0 },
          "unit": { "type": "string", "maxLength": 16, "default": "vnt", "description": "Unit, e.g. vnt (pcs), val (hours)." },
          "unit_price": { "type": "number", "minimum": 0, "description": "Price per unit excluding VAT, EUR." },
          "vat_rate": { "type": "number", "minimum": -1, "maximum": 100, "default": 21, "description": "VAT percent (21, 12, 5 or 0 in Lithuania); -1 for a margin-scheme line." },
          "kind": { "type": ["string", "null"], "enum": ["goods", "services", null], "description": "Goods or services, for the FR0564 report. Guessed from the unit when omitted." }
        }
      },
      "InvoiceCreate": {
        "type": "object",
        "description": "Send either client_id (an existing client) or client_snapshot (buyer details inline).",
        "anyOf": [{ "required": ["client_id"] }, { "required": ["client_snapshot"] }],
        "required": ["lines"],
        "properties": {
          "client_id": { "type": "string", "format": "uuid", "description": "Id of one of the user's clients." },
          "client_snapshot": { "$ref": "#/components/schemas/ClientSnapshot" },
          "lines": { "type": "array", "minItems": 1, "maxItems": 100, "items": { "$ref": "#/components/schemas/InvoiceLineInput" } },
          "invoice_number": { "type": "string", "description": "Omit to take the next number from the user's sequence (e.g. SF-2026-043)." },
          "status": { "type": "string", "enum": ["juodraštis", "išsiųsta", "apmokėta"], "default": "juodraštis", "description": "Any other value is stored as juodraštis." },
          "issue_date": { "type": "string", "format": "date", "description": "Defaults to today." },
          "due_date": { "type": "string", "format": "date", "description": "Defaults to issue date plus the user's payment term." },
          "notes": { "type": "string", "maxLength": 1000 },
          "invoice_type": { "type": "string", "enum": ["saskaita", "isankstine", "pasiulymas", "kreditine"], "default": "saskaita" },
          "vat_scheme": { "$ref": "#/components/schemas/VatScheme" },
          "long_term_supply": { "type": "boolean", "default": false, "description": "Advance invoices only: delivery 12 months or more after the deal (PVMĮ 14 str. 4 d.)." },
          "bank_account_id": { "type": "string", "format": "uuid", "description": "Which of the user's bank accounts to print. Defaults to the main one." },
          "discount": { "type": "number", "const": 0, "description": "Discounts are not supported through the API; omit or send 0." }
        }
      },
      "InvoicePatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "status": { "type": "string", "enum": ["juodraštis", "išsiųsta", "apmokėta", "atšaukta"] },
          "paid_at": { "type": "string", "description": "ISO date or datetime of payment; only with status apmokėta. Defaults to now.", "examples": ["2026-05-20"] },
          "paid_method": { "type": ["string", "null"], "enum": ["bank", "cash", null], "description": "Only with status apmokėta." }
        }
      },
      "Client": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "client_type": { "type": "string", "enum": ["business", "individual"] },
          "email": { "type": ["string", "null"] },
          "phone": { "type": ["string", "null"] },
          "company_code": { "type": ["string", "null"] },
          "vat_code": { "type": ["string", "null"] },
          "address": { "type": ["string", "null"] },
          "iban": { "type": ["string", "null"] },
          "country": { "type": ["string", "null"] },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "ClientCreate": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "minLength": 1, "maxLength": 200, "description": "Company or person name." },
          "client_type": { "type": "string", "enum": ["business", "individual"], "default": "business" },
          "first_name": { "type": "string", "maxLength": 100 },
          "last_name": { "type": "string", "maxLength": 100 },
          "email": { "type": "string", "maxLength": 200 },
          "phone": { "type": "string", "maxLength": 50 },
          "company_code": { "type": "string", "maxLength": 50 },
          "vat_code": { "type": "string", "maxLength": 50 },
          "address": { "type": "string", "maxLength": 500 },
          "iban": { "type": "string", "maxLength": 50 },
          "country": { "type": "string", "maxLength": 100, "default": "Lietuva" },
          "notes": { "type": "string", "maxLength": 1000 }
        }
      },
      "Product": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "description": { "type": ["string", "null"] },
          "price": { "type": "number", "description": "Price excluding VAT." },
          "vat_rate": { "type": "number" },
          "unit": { "type": "string" },
          "kind": { "type": ["string", "null"], "enum": ["goods", "services", null] },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "VatTreatment": {
        "type": "string",
        "enum": ["deductible", "non_deductible", "margin", "reverse_charge"]
      },
      "ReverseChargeKind": {
        "type": "string",
        "enum": ["eu_services", "foreign_services", "eu_goods", "domestic_96"]
      },
      "CostType": {
        "type": "string",
        "enum": ["operating", "cogs", "inventory", "raw_materials", "work_in_progress", "fixed_asset", "finance_cost", "interest", "loan_principal", "supplier_advance"]
      },
      "Expense": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "date": { "type": "string", "format": "date" },
          "category": { "type": ["string", "null"] },
          "supplier": { "type": ["string", "null"] },
          "amount": { "type": "number", "description": "Document total including VAT, EUR." },
          "vat_amount": { "type": "number" },
          "vat_treatment": { "$ref": "#/components/schemas/VatTreatment" },
          "vat_reverse_charge_kind": { "oneOf": [{ "$ref": "#/components/schemas/ReverseChargeKind" }, { "type": "null" }] },
          "cost_type": { "$ref": "#/components/schemas/CostType" },
          "document_number": { "type": ["string", "null"] },
          "description": { "type": ["string", "null"] },
          "paid_at": { "type": ["string", "null"], "format": "date" },
          "paid_method": { "type": ["string", "null"], "enum": ["bank", "cash", null] },
          "suspected_duplicate_of": { "type": ["string", "null"], "format": "uuid", "description": "Set when this row matches an existing expense." },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "ExpenseCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": ["supplier", "date", "amount"],
        "properties": {
          "supplier": { "type": "string", "minLength": 1, "maxLength": 200 },
          "date": { "type": "string", "format": "date", "description": "Document date, YYYY-MM-DD." },
          "amount": { "type": "number", "minimum": 0, "maximum": 99999999.99, "description": "Document total INCLUDING VAT." },
          "vat_amount": { "type": "number", "minimum": 0, "default": 0, "description": "VAT part of amount; must not exceed it. 0 for reverse charge." },
          "vat_treatment": { "$ref": "#/components/schemas/VatTreatment" },
          "vat_reverse_charge_kind": { "$ref": "#/components/schemas/ReverseChargeKind", "description": "Required when vat_treatment is reverse_charge, not allowed otherwise." },
          "cost_type": { "$ref": "#/components/schemas/CostType" },
          "document_number": { "type": "string", "maxLength": 100 },
          "description": { "type": "string", "maxLength": 1000 },
          "paid_at": { "type": ["string", "null"], "format": "date", "description": "Payment date; send together with paid_method." },
          "paid_method": { "type": ["string", "null"], "enum": ["bank", "cash", null], "description": "Only bank is accepted; cash returns 409 cash_needs_kio." }
        }
      },
      "ExpensePatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "paid_at": { "type": ["string", "null"], "format": "date", "description": "Payment date, or null with paid_method null to clear the mark." },
          "paid_method": { "type": ["string", "null"], "enum": ["bank", "cash", null], "description": "Only bank is accepted; cash returns 409 cash_needs_kio." }
        }
      }
    }
  }
}
