{
  "openapi": "3.1.0",
  "info": {
    "title": "The IQ Suite Public API",
    "version": "v1",
    "summary": "Programmatic access to ReconcileIQ reconciliation, CodeIQ transaction coding, and IQ Books bookkeeping.",
    "description": "The IQ Suite public API (`/api/v1`) gives third-party applications API-key access to three product surfaces:\n\n- **ReconcileIQ** — submit strictly-formatted bank and book datasets and get a reconciliation result back (async + poll).\n- **CodeIQ** — run the real CodeIQ coding pipeline over your own transactions and chart of accounts (async + poll).\n- **IQ Books** — read and write a member organisation's ledger: accounts, contacts, bank accounts and transactions, invoices, receipts and payments, and manual journals.\n\nAll authenticated reads and writes use an API key (`Authorization: Bearer iqk_...` or `X-API-Key: iqk_...`). API-key minting is a first-party web-app operation protected by the web session (JWT), not by an API key.\n\nReconciliation and coding jobs are asynchronous: submit returns `202` with a job id and a poll URL; poll the GET endpoint until `status` is `completed` or `failed`. ReconcileIQ and CodeIQ consume account credits; IQ Books does not. Money in IQ Books is exchanged as decimal major units (e.g. `1234.56`) with an explicit currency. Every error response uses one standard envelope (`{ \"error\": { \"code\", \"message\", \"details?\" }, \"request_id\" }`) and every response carries an `X-Request-Id` header — with no exceptions. A syntactically malformed JSON request body returns HTTP `400` `invalid_json` in that envelope (checked before authentication, so even unauthenticated broken bodies get the clean shape); a well-formed body that fails schema validation returns the product's `400`/`422` code. The first-party `/api/v1/keys` endpoints authenticate with a web-session JWT (not an API key) and on failure return the same standard envelope: `401` `unauthorized` (missing token) or `401` `invalid_token` (invalid/expired). The maximum accepted request body is 25 MB (`413 payload_too_large`).",
    "contact": {
      "name": "The IQ Suite",
      "url": "https://bankreconciler.app/developers"
    }
  },
  "servers": [
    {
      "url": "https://api.bankreconciler.app",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Health",
      "description": "Service health and caller identity."
    },
    {
      "name": "API Keys",
      "description": "First-party (JWT-authenticated) API key management."
    },
    {
      "name": "ReconcileIQ",
      "description": "Bank reconciliation jobs (async + poll, credit-metered)."
    },
    {
      "name": "CodeIQ",
      "description": "Transaction coding jobs (async + poll, credit-metered)."
    },
    {
      "name": "IQ Books — Organisations",
      "description": "Organisations and tax codes."
    },
    {
      "name": "IQ Books — Accounts",
      "description": "Chart of accounts."
    },
    {
      "name": "IQ Books — Contacts",
      "description": "Customers and suppliers."
    },
    {
      "name": "IQ Books — Bank",
      "description": "Bank accounts and bank transactions."
    },
    {
      "name": "IQ Books — Invoices",
      "description": "Sales and purchase invoices."
    },
    {
      "name": "IQ Books — Settlements",
      "description": "Customer receipts and supplier payments."
    },
    {
      "name": "IQ Books — Journals",
      "description": "Manual double-entry journals."
    }
  ],
  "security": [
    {
      "ApiKeyBearer": []
    },
    {
      "ApiKeyHeader": []
    }
  ],
  "paths": {
    "/api/v1/health": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Service health",
        "description": "Public, unauthenticated liveness check.",
        "operationId": "getHealth",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "version",
                    "time"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "examples": [
                        "ok"
                      ]
                    },
                    "version": {
                      "type": "string",
                      "examples": [
                        "v1"
                      ]
                    },
                    "time": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "status": "ok",
                  "version": "v1",
                  "time": "2026-05-17T16:30:43.845Z"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Authenticated caller identity and credit balance",
        "description": "Returns the user id behind the presented API key, the key's scopes, and the current monthly + bonus credit balance.",
        "operationId": "getMe",
        "responses": {
          "200": {
            "description": "Caller identity.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "user_id",
                    "scopes",
                    "credits"
                  ],
                  "properties": {
                    "user_id": {
                      "type": "integer",
                      "examples": [
                        1
                      ]
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Scope"
                      }
                    },
                    "credits": {
                      "type": "object",
                      "required": [
                        "remainingMonthly",
                        "bonusCredits"
                      ],
                      "properties": {
                        "remainingMonthly": {
                          "type": "integer"
                        },
                        "bonusCredits": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "user_id": 1,
                  "scopes": [
                    "codeiq:read",
                    "reconcileiq:read"
                  ],
                  "credits": {
                    "remainingMonthly": 982,
                    "bonusCredits": 94947002
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/keys": {
      "post": {
        "tags": [
          "API Keys"
        ],
        "summary": "Mint an API key (first-party, JWT-authenticated)",
        "description": "Creates a new API key for the logged-in web user. The plaintext key is returned exactly once and is never recoverable afterwards. This endpoint is authenticated by the first-party web session (JWT bearer token), NOT by an API key. On authentication failure it returns the standard error envelope (`401 unauthorized` if the token is missing, `401 invalid_token` if it is invalid or expired), with a `request_id`.",
        "operationId": "createApiKey",
        "security": [
          {
            "FirstPartyJWT": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "scopes"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "scopes": {
                    "type": "array",
                    "minItems": 1,
                    "description": "Unique list of scopes. Duplicates are rejected.",
                    "items": {
                      "$ref": "#/components/schemas/Scope"
                    }
                  },
                  "allow_overage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, this key is permitted to incur overage charges (still also requires the request and the subscription to allow overage)."
                  }
                }
              },
              "example": {
                "name": "Production integration",
                "scopes": [
                  "reconcileiq:write",
                  "reconcileiq:read",
                  "codeiq:write",
                  "codeiq:read"
                ],
                "allow_overage": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created. `key` is shown only here.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "name",
                    "prefix",
                    "scopes",
                    "allow_overage",
                    "key"
                  ],
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "prefix": {
                      "type": "string",
                      "description": "12-char lookup handle (also the middle segment of the key)."
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Scope"
                      }
                    },
                    "allow_overage": {
                      "type": "boolean"
                    },
                    "key": {
                      "type": "string",
                      "description": "Full plaintext key, format iqk_<prefix>_<secret>. Shown ONCE."
                    }
                  }
                },
                "example": {
                  "id": 7,
                  "name": "Production integration",
                  "prefix": "Ab3kZ9mQ2xWp",
                  "scopes": [
                    "reconcileiq:write",
                    "reconcileiq:read"
                  ],
                  "allow_overage": false,
                  "key": "iqk_Ab3kZ9mQ2xWp_s3cr3tValueBase64UrlEncodedNeverStoredAgain"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/FirstPartyUnauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "List the caller's API keys (first-party, JWT-authenticated)",
        "description": "Lists all keys owned by the logged-in web user, newest first. Secrets are never returned. JWT-authenticated, not API-key-authenticated.",
        "operationId": "listApiKeys",
        "security": [
          {
            "FirstPartyJWT": []
          }
        ],
        "responses": {
          "200": {
            "description": "Keys owned by the caller.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "keys"
                  ],
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "prefix": {
                            "type": "string"
                          },
                          "scopes": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/Scope"
                            }
                          },
                          "allow_overage": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "last_used_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "revoked_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "keys": [
                    {
                      "id": 7,
                      "name": "Production integration",
                      "prefix": "Ab3kZ9mQ2xWp",
                      "scopes": [
                        "reconcileiq:write",
                        "reconcileiq:read"
                      ],
                      "allow_overage": false,
                      "created_at": "2026-05-01T10:00:00.000Z",
                      "last_used_at": "2026-05-17T16:30:00.000Z",
                      "revoked_at": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/FirstPartyUnauthorized"
          }
        }
      }
    },
    "/api/v1/keys/{id}": {
      "delete": {
        "tags": [
          "API Keys"
        ],
        "summary": "Revoke an API key (first-party, JWT-authenticated)",
        "description": "Soft-revokes a key owned by the caller (sets revoked_at). Revoked or non-owned keys return 404. JWT-authenticated.",
        "operationId": "revokeApiKey",
        "security": [
          {
            "FirstPartyJWT": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Key revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "name": {
                          "type": "string"
                        },
                        "prefix": {
                          "type": "string"
                        },
                        "revoked_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "revoked": {
                    "id": 7,
                    "name": "Production integration",
                    "prefix": "Ab3kZ9mQ2xWp",
                    "revoked_at": "2026-05-17T16:31:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/FirstPartyUnauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/reconciliations": {
      "post": {
        "tags": [
          "ReconcileIQ"
        ],
        "summary": "Submit a reconciliation",
        "description": "Submits a bank dataset and a book dataset for reconciliation. The job is queued and processed asynchronously; poll the returned `poll_url`.\n\n**Strict canonical format.** No column mapping or auto-detection is performed. Each row of both `bank` and `book` must be exactly `{ date, amount, description }` with no extra keys:\n- `date`: string, exact ISO 8601 `YYYY-MM-DD`, must be a real calendar date.\n- `amount`: a finite JSON number, signed (negatives allowed).\n- `description`: string, 1 to 500 characters.\n\nNo extra top-level keys are allowed (only `bank`, `book`, `options`). Any structural deviation is rejected with HTTP **422 `invalid_dataset_format`**, listing the offending dataset, row index, and field.\n\n**Credits.** A reconciliation costs credits equal to the number of bank rows, and is charged ONLY if the run completes and is verified (`result.verification.is_verified === true`). If the dataset is obviously unaffordable at submit time, the request is rejected synchronously with 402. Requires scope `reconcileiq:write`.",
        "operationId": "createReconciliation",
        "security": [
          {
            "ApiKeyBearer": [
              "reconcileiq:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "reconcileiq:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReconciliationRequest"
              },
              "example": {
                "bank": [
                  {
                    "date": "2026-01-03",
                    "amount": -42.5,
                    "description": "TESCO STORES 3294"
                  },
                  {
                    "date": "2026-01-05",
                    "amount": 1200.0,
                    "description": "CLIENT PAYMENT INV-1001"
                  }
                ],
                "book": [
                  {
                    "date": "2026-01-03",
                    "amount": -42.5,
                    "description": "Tesco groceries"
                  },
                  {
                    "date": "2026-01-06",
                    "amount": 1200.0,
                    "description": "Invoice 1001 receipt"
                  }
                ],
                "options": {
                  "allowedDateDifferenceInDays": 3,
                  "includeMatched": true,
                  "allowOverage": false
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Reconciliation queued.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "reconciliation_id",
                    "status",
                    "poll_url"
                  ],
                  "properties": {
                    "reconciliation_id": {
                      "type": "string",
                      "examples": [
                        "rec_8Hq2Lm9Xk4PwZ0aBcD1eF"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued"
                      ]
                    },
                    "poll_url": {
                      "type": "string",
                      "examples": [
                        "/api/v1/reconciliations/rec_8Hq2Lm9Xk4PwZ0aBcD1eF"
                      ]
                    }
                  }
                },
                "example": {
                  "reconciliation_id": "rec_8Hq2Lm9Xk4PwZ0aBcD1eF",
                  "status": "queued",
                  "poll_url": "/api/v1/reconciliations/rec_8Hq2Lm9Xk4PwZ0aBcD1eF"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/InvalidDatasetFormat"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Busy"
          }
        }
      }
    },
    "/api/v1/reconciliations/{id}": {
      "get": {
        "tags": [
          "ReconcileIQ"
        ],
        "summary": "Poll a reconciliation",
        "description": "Fetches reconciliation status and, once finished, the result. `status` is one of `queued`, `processing`, `completed`, `failed`. The `result` object is present only when `status` is `completed`; the `error` object only when `status` is `failed`. **A reconciliation that ran but could not be balance-verified is intended, documented behaviour, not an error**: it still completes with `status: completed`, `result.summary.verified: false`, `result.verification.is_verified: false` with a human-readable `result.verification.reason`, and `credits_charged: 0` (top-level and inside `result`) — nothing is billed. A verified run carries `credits_charged: <n>` (n = bank rows). Always branch on `verification.is_verified` / `credits_charged`, never on `status` alone. Visible only to the owning user; requires scope `reconcileiq:read` OR being the exact key that created it. Non-owned or unknown ids return 404 (no existence leak).",
        "operationId": "getReconciliation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reconciliation state.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReconciliationState"
                },
                "examples": {
                  "processing": {
                    "summary": "Still running",
                    "value": {
                      "reconciliation_id": "rec_8Hq2Lm9Xk4PwZ0aBcD1eF",
                      "status": "processing",
                      "created_at": "2026-05-17T16:30:00.000Z"
                    }
                  },
                  "completed": {
                    "summary": "Completed and verified",
                    "value": {
                      "reconciliation_id": "rec_8Hq2Lm9Xk4PwZ0aBcD1eF",
                      "status": "completed",
                      "created_at": "2026-05-17T16:30:00.000Z",
                      "completed_at": "2026-05-17T16:30:08.000Z",
                      "result": {
                        "summary": {
                          "bank_rows": 2,
                          "book_rows": 2,
                          "matched_count": 1,
                          "missing_from_books_count": 1,
                          "remove_from_books_count": 1,
                          "verified": true
                        },
                        "credits_charged": 3,
                        "missing_from_books": [
                          {
                            "date": "2026-01-05",
                            "amount": 1200.0,
                            "description": "CLIENT PAYMENT INV-1001"
                          }
                        ],
                        "remove_from_books": [
                          {
                            "date": "2026-01-06",
                            "amount": 1200.0,
                            "description": "Invoice 1001 receipt"
                          }
                        ],
                        "verification": {
                          "is_verified": true,
                          "expected_net_discrepancy": 0,
                          "actual_net_effect": 0,
                          "reason": "Verified: the unreconciled items fully account for the net balance discrepancy between the two datasets."
                        },
                        "matched": [
                          {
                            "bank": {
                              "date": "2026-01-03",
                              "amount": -42.5,
                              "description": "TESCO STORES 3294"
                            },
                            "book": {
                              "date": "2026-01-03",
                              "amount": -42.5,
                              "description": "Tesco groceries"
                            },
                            "date_difference": 0
                          }
                        ]
                      },
                      "credits_charged": 3
                    }
                  },
                  "completed_unverified": {
                    "summary": "Completed but NOT balance-verified (intended behaviour — not charged)",
                    "value": {
                      "reconciliation_id": "rec_w8KLdV_ZErlgQSU0lZUfcQ",
                      "status": "completed",
                      "created_at": "2026-05-17T17:41:32.437Z",
                      "completed_at": "2026-05-17T17:41:32.510Z",
                      "credits_charged": 0,
                      "result": {
                        "summary": {
                          "bank_rows": 20,
                          "book_rows": 20,
                          "matched_count": 20,
                          "missing_from_books_count": 0,
                          "remove_from_books_count": 0,
                          "verified": false
                        },
                        "credits_charged": 0,
                        "missing_from_books": [],
                        "remove_from_books": [],
                        "verification": {
                          "is_verified": false,
                          "expected_net_discrepancy": 0.018,
                          "actual_net_effect": 0,
                          "reason": "Not verified: an unexplained net discrepancy of 0.02 remains. The expected net discrepancy (0.018) does not match the net effect of the unreconciled items (0), so the result could not be balance-verified and this run was not charged."
                        }
                      }
                    }
                  },
                  "failed": {
                    "summary": "Failed (insufficient credits at charge time)",
                    "value": {
                      "reconciliation_id": "rec_8Hq2Lm9Xk4PwZ0aBcD1eF",
                      "status": "failed",
                      "created_at": "2026-05-17T16:30:00.000Z",
                      "completed_at": "2026-05-17T16:30:08.000Z",
                      "error": {
                        "code": "insufficient_credits",
                        "message": "Insufficient credits to charge for this reconciliation",
                        "details": {
                          "required": 1264,
                          "available": 300
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/codeiq/coding-jobs": {
      "post": {
        "tags": [
          "CodeIQ"
        ],
        "summary": "Submit a coding job",
        "description": "Runs the real CodeIQ coding pipeline over caller-supplied transactions against a caller-supplied chart of accounts. Async; poll `poll_url`.\n\n**Strict format** (HTTP **422 `invalid_request_format`** on any deviation, with section/row/field detail):\n- `transactions[]`: `{ date: 'YYYY-MM-DD' (real calendar date), description: 1..500, amount: signed finite number (money out negative), merchant?: <=200, reference?: <=100 }`.\n- `chart_of_accounts[]`: `{ code: 1..50, name: 1..200, type: one of INCOME, EXPENSE, ASSET, LIABILITY, EQUITY (case-insensitive; REVENUE is accepted as an alias of INCOME) }`.\n- `vat_codes[]` (optional): `{ code: 1..50, name: 1..200, rate: 0..100 }`. If omitted, the default UK universal set (ST/RR/ZR/EX/NV) is used.\n- `options` (optional): `{ allowOverage?: boolean (default false), businessContext?: string <=2000 }`.\n\nVAT codes returned are universal codes: `NV`, `ST`, `RR`, `EX`, `ZR`.\n\n**Credits.** Cost equals the number of transactions, charged only on a successful completed run. Unaffordable at submit time returns 402 synchronously. Requires scope `codeiq:write`.\n\n**Data minimisation.** The transient working session and the submitted transactions are deleted after the result is extracted; only the coded result is retained for polling.",
        "operationId": "createCodingJob",
        "security": [
          {
            "ApiKeyBearer": [
              "codeiq:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "codeiq:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CodingJobRequest"
              },
              "example": {
                "transactions": [
                  {
                    "date": "2026-01-03",
                    "description": "TESCO STORES 3294",
                    "amount": -42.5
                  },
                  {
                    "date": "2026-01-04",
                    "description": "SHELL FUEL LONDON",
                    "amount": -68.0,
                    "merchant": "Shell"
                  }
                ],
                "chart_of_accounts": [
                  {
                    "code": "5000",
                    "name": "Cost of Goods Sold",
                    "type": "EXPENSE"
                  },
                  {
                    "code": "7300",
                    "name": "Motor Vehicle Expenses",
                    "type": "EXPENSE"
                  },
                  {
                    "code": "4000",
                    "name": "Sales",
                    "type": "INCOME"
                  }
                ],
                "vat_codes": [
                  {
                    "code": "ST",
                    "name": "Standard Rate (20%)",
                    "rate": 20
                  },
                  {
                    "code": "NV",
                    "name": "No VAT",
                    "rate": 0
                  }
                ],
                "options": {
                  "allowOverage": false,
                  "businessContext": "Small UK retailer"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Coding job queued.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "coding_job_id",
                    "status",
                    "poll_url"
                  ],
                  "properties": {
                    "coding_job_id": {
                      "type": "string",
                      "examples": [
                        "cod_4Tn1Ks8Wm2Qx5aZbCd9eF"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued"
                      ]
                    },
                    "poll_url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "coding_job_id": "cod_4Tn1Ks8Wm2Qx5aZbCd9eF",
                  "status": "queued",
                  "poll_url": "/api/v1/codeiq/coding-jobs/cod_4Tn1Ks8Wm2Qx5aZbCd9eF"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Busy"
          }
        }
      }
    },
    "/api/v1/codeiq/coding-jobs/{id}": {
      "get": {
        "tags": [
          "CodeIQ"
        ],
        "summary": "Poll a coding job",
        "description": "Fetches coding job status and, once finished, the coded result. `status` is one of `queued`, `processing`, `completed`, `failed`. Result transactions are returned in the exact submitted order. `suggested_account_type` may be null. Visible only to the owning user; requires scope `codeiq:read` OR being the exact key that created it. Non-owned or unknown ids return 404.",
        "operationId": "getCodingJob",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Coding job state.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodingJobState"
                },
                "examples": {
                  "completed": {
                    "summary": "Completed",
                    "value": {
                      "coding_job_id": "cod_4Tn1Ks8Wm2Qx5aZbCd9eF",
                      "status": "completed",
                      "created_at": "2026-05-17T16:30:00.000Z",
                      "completed_at": "2026-05-17T16:31:20.000Z",
                      "result": {
                        "summary": {
                          "transaction_count": 2,
                          "coded_count": 2,
                          "by_method": {
                            "universal_pattern": 1,
                            "semantic": 1
                          }
                        },
                        "transactions": [
                          {
                            "input": {
                              "date": "2026-01-03",
                              "description": "TESCO STORES 3294",
                              "amount": -42.5
                            },
                            "suggested_account_code": "5000",
                            "suggested_account_name": "Cost of Goods Sold",
                            "suggested_account_type": "Expense",
                            "vat_code": "ST",
                            "vat_rate": 20,
                            "net_amount": -35.42,
                            "vat_amount": -7.08,
                            "confidence": 0.93,
                            "coding_method": "universal_pattern"
                          },
                          {
                            "input": {
                              "date": "2026-01-04",
                              "description": "SHELL FUEL LONDON",
                              "amount": -68.0,
                              "merchant": "Shell"
                            },
                            "suggested_account_code": "7300",
                            "suggested_account_name": "Motor Vehicle Expenses",
                            "suggested_account_type": null,
                            "vat_code": "ST",
                            "vat_rate": 20,
                            "net_amount": -56.67,
                            "vat_amount": -11.33,
                            "confidence": 0.81,
                            "coding_method": "semantic"
                          }
                        ]
                      }
                    }
                  },
                  "failed": {
                    "summary": "Failed",
                    "value": {
                      "coding_job_id": "cod_4Tn1Ks8Wm2Qx5aZbCd9eF",
                      "status": "failed",
                      "created_at": "2026-05-17T16:30:00.000Z",
                      "completed_at": "2026-05-17T16:31:00.000Z",
                      "error": {
                        "code": "coding_failed",
                        "message": "Coding pipeline failed",
                        "details": "pipeline failed"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations": {
      "get": {
        "tags": [
          "IQ Books — Organisations"
        ],
        "summary": "List organisations",
        "description": "Lists organisations the caller is an accepted member of. Requires scope `iqbooks:read`. Note: this collection response is `{ data: [...] }` with no pagination block.",
        "operationId": "listOrganisations",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Organisations.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Organisation"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}": {
      "get": {
        "tags": [
          "IQ Books — Organisations"
        ],
        "summary": "Get an organisation",
        "description": "Returns a single organisation. Membership is enforced: a non-member or unknown organisation returns 404 (no existence leak). Requires scope `iqbooks:read`.",
        "operationId": "getOrganisation",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Organisation"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/tax-codes": {
      "get": {
        "tags": [
          "IQ Books — Organisations"
        ],
        "summary": "List tax / VAT codes",
        "description": "Returns the canonical universal VAT code set the IQ Books ledger recognises (ST, RR, ZR, EX, NV) plus the organisation's VAT registration status and scheme. Membership enforced. Requires scope `iqbooks:read`.",
        "operationId": "listTaxCodes",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Tax codes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "rate": {
                            "type": "number"
                          },
                          "rate_fraction": {
                            "type": [
                              "number",
                              "null"
                            ]
                          }
                        }
                      }
                    },
                    "vat_registered": {
                      "type": "boolean"
                    },
                    "vat_scheme": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "code": "ST",
                      "name": "Standard Rate",
                      "rate": 20.0,
                      "rate_fraction": 0.2
                    },
                    {
                      "code": "RR",
                      "name": "Reduced Rate",
                      "rate": 5.0,
                      "rate_fraction": 0.05
                    },
                    {
                      "code": "ZR",
                      "name": "Zero Rate",
                      "rate": 0.0,
                      "rate_fraction": 0.0
                    },
                    {
                      "code": "EX",
                      "name": "Exempt",
                      "rate": 0.0,
                      "rate_fraction": 0.0
                    },
                    {
                      "code": "NV",
                      "name": "No VAT",
                      "rate": 0.0,
                      "rate_fraction": null
                    }
                  ],
                  "vat_registered": true,
                  "vat_scheme": "standard"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/accounts": {
      "get": {
        "tags": [
          "IQ Books — Accounts"
        ],
        "summary": "List accounts",
        "description": "Lists the organisation's chart of accounts. `{ data: [...] }` (no pagination block). Requires scope `iqbooks:read`.",
        "operationId": "listAccounts",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Account"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Accounts"
        ],
        "summary": "Create an account",
        "description": "Creates a chart-of-accounts entry via the native account service. Requires scope `iqbooks:write`. Set role='vat_control' on a LIABILITY account to establish the organisation's VAT Control account, which is a prerequisite for any VAT-bearing posting (bank confirm, invoice issue, journal).",
        "operationId": "createAccount",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "name",
                  "account_type"
                ],
                "additionalProperties": false,
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "account_type": {
                    "type": "string",
                    "enum": [
                      "ASSET",
                      "LIABILITY",
                      "EQUITY",
                      "REVENUE",
                      "EXPENSE",
                      "SUSPENSE"
                    ]
                  },
                  "role": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50,
                    "description": "Optional system role for this account. Set role='vat_control' to designate the organisation's VAT Control account. An organisation with no vat_control account cannot confirm VAT-bearing bank transactions, issue VAT invoices, or post VAT journals (those return 422 unprocessable_entity until one exists). Create one vat_control LIABILITY account per organisation before any VAT posting."
                  }
                }
              },
              "example": {
                "code": "7400",
                "name": "Travel",
                "account_type": "EXPENSE"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/accounts/{id}": {
      "get": {
        "tags": [
          "IQ Books — Accounts"
        ],
        "summary": "Get an account",
        "operationId": "getAccount",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "IQ Books — Accounts"
        ],
        "summary": "Update an account (master data only)",
        "description": "Renames an account and/or changes its code. Master-data only; posts no journal. Requires a write role (owner/admin/bookkeeper) and scope `iqbooks:write`. A duplicate code returns 409.",
        "operationId": "patchAccount",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "minProperties": 1,
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20
                  }
                }
              },
              "example": {
                "name": "Travel and Subsistence"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/accounts/{id}/archive": {
      "post": {
        "tags": [
          "IQ Books — Accounts"
        ],
        "summary": "Archive an account",
        "description": "Soft-archives the account (idempotent). The row is retained so historical journal lines keep resolving. Requires a write role and scope `iqbooks:write`.",
        "operationId": "archiveAccount",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Archived.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/customers": {
      "get": {
        "tags": [
          "IQ Books — Contacts"
        ],
        "summary": "List customers",
        "description": "Paginated list of customers. Supports `?search`, `?include_archived=true`, and `?limit`/`?offset`. Requires scope `iqbooks:read`.",
        "operationId": "listCustomers",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_archived",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactList"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Contacts"
        ],
        "summary": "Create a customer",
        "operationId": "createCustomer",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactCreate"
              },
              "example": {
                "name": "Acme Ltd",
                "email": "ap@acme.example",
                "default_payment_terms_days": 30,
                "default_currency_code": "GBP"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/ContactSingle"
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/customers/{id}": {
      "get": {
        "tags": [
          "IQ Books — Contacts"
        ],
        "summary": "Get a customer",
        "operationId": "getCustomer",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactSingle"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "IQ Books — Contacts"
        ],
        "summary": "Update a customer",
        "operationId": "patchCustomer",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactPatch"
              },
              "example": {
                "email": "newbilling@acme.example"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactSingle"
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/suppliers": {
      "get": {
        "tags": [
          "IQ Books — Contacts"
        ],
        "summary": "List suppliers",
        "description": "Paginated list of suppliers. Supports `?search`, `?include_archived=true`, `?limit`/`?offset`. Requires scope `iqbooks:read`.",
        "operationId": "listSuppliers",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_archived",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactList"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Contacts"
        ],
        "summary": "Create a supplier",
        "operationId": "createSupplier",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactCreate"
              },
              "example": {
                "name": "Office Supplies Co"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/ContactSingle"
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/suppliers/{id}": {
      "get": {
        "tags": [
          "IQ Books — Contacts"
        ],
        "summary": "Get a supplier",
        "operationId": "getSupplier",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactSingle"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "IQ Books — Contacts"
        ],
        "summary": "Update a supplier",
        "operationId": "patchSupplier",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactSingle"
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/bank-accounts": {
      "get": {
        "tags": [
          "IQ Books — Bank"
        ],
        "summary": "List bank accounts",
        "description": "Lists non-archived bank accounts. `{ data: [...] }` (no pagination block). Requires scope `iqbooks:read`.",
        "operationId": "listBankAccounts",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Bank accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BankAccount"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Bank"
        ],
        "summary": "Create a bank account",
        "operationId": "createBankAccount",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "display_name"
                ],
                "additionalProperties": false,
                "properties": {
                  "display_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "account_code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20
                  },
                  "account_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "account_id": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "currency_code": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "sort_code": {
                    "type": "string",
                    "maxLength": 20
                  },
                  "account_number_last4": {
                    "type": "string",
                    "maxLength": 34
                  },
                  "iban_last4": {
                    "type": "string",
                    "maxLength": 34
                  }
                }
              },
              "example": {
                "display_name": "Business Current",
                "currency_code": "GBP",
                "sort_code": "20-00-00",
                "account_number_last4": "1234"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bank account created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankAccount"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/bank-accounts/{id}": {
      "get": {
        "tags": [
          "IQ Books — Bank"
        ],
        "summary": "Get a bank account",
        "operationId": "getBankAccount",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Bank account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankAccount"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/bank-transactions": {
      "get": {
        "tags": [
          "IQ Books — Bank"
        ],
        "summary": "List bank transactions",
        "description": "Paginated list of bank transactions. Filters: `?status`, `?bank_account_id`, `?date_from`, `?date_to` (YYYY-MM-DD), plus `?limit`/`?offset`. Requires scope `iqbooks:read`.",
        "operationId": "listBankTransactions",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "e.g. pending, confirmed, ignored."
          },
          {
            "name": "bank_account_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bank transactions.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/BankTransaction"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Bank"
        ],
        "summary": "Create a bank transaction",
        "description": "Creates a draft bank transaction. `amount` is a positive decimal; the direction (`money_in`/`money_out`) carries the sign. No journal is posted until confirmed. Requires scope `iqbooks:write`.",
        "operationId": "createBankTransaction",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "bank_account_id",
                  "transaction_date",
                  "description",
                  "direction",
                  "amount"
                ],
                "additionalProperties": false,
                "properties": {
                  "bank_account_id": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "transaction_date": {
                    "type": "string",
                    "format": "date",
                    "description": "YYYY-MM-DD"
                  },
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  },
                  "direction": {
                    "type": "string",
                    "enum": [
                      "money_in",
                      "money_out"
                    ]
                  },
                  "amount": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Positive decimal major units (sign comes from direction)."
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "category_account_id": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "vat_amount": {
                    "type": "number",
                    "minimum": 0
                  },
                  "vat_code": {
                    "type": "string",
                    "maxLength": 20
                  },
                  "reference": {
                    "type": "string",
                    "maxLength": 255
                  }
                }
              },
              "example": {
                "bank_account_id": 12,
                "transaction_date": "2026-01-10",
                "description": "Card payment - Shell",
                "direction": "money_out",
                "amount": 68.0,
                "category_account_id": 44,
                "vat_amount": 11.33,
                "vat_code": "ST"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bank transaction created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankTransaction"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/bank-transactions/{id}": {
      "get": {
        "tags": [
          "IQ Books — Bank"
        ],
        "summary": "Get a bank transaction",
        "operationId": "getBankTransaction",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Bank transaction.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankTransaction"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "IQ Books — Bank"
        ],
        "summary": "Categorise and/or confirm a bank transaction",
        "description": "Updates draft fields (category, VAT, description) and/or, with `confirm: true`, posts the double-entry journal via the native confirm path (period-lock and balance enforced there). At least one updatable field or `confirm: true` must be supplied. Requires scope `iqbooks:write`. Requires a vat_control-role account to exist in the organisation when the posting carries VAT; otherwise 422 unprocessable_entity (\"VAT Control account is required ...\").",
        "operationId": "patchBankTransaction",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "category_account_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "null explicitly clears the category."
                  },
                  "vat_amount": {
                    "type": "number",
                    "minimum": 0
                  },
                  "vat_code": {
                    "type": "string",
                    "maxLength": 20
                  },
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  },
                  "confirm": {
                    "type": "boolean",
                    "description": "true posts the journal."
                  }
                }
              },
              "example": {
                "category_account_id": 44,
                "vat_code": "ST",
                "confirm": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated / confirmed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankTransaction"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/sales-invoices": {
      "get": {
        "tags": [
          "IQ Books — Invoices"
        ],
        "summary": "List sales invoices",
        "description": "Paginated list. Filters: `?status`, `?contact_id`, `?document_type`, `?search`, `?date_from`, `?date_to`, `?limit`/`?offset`. Requires scope `iqbooks:read`.",
        "operationId": "listSalesInvoices",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contact_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "document_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "invoice",
                "credit_note",
                "quote"
              ]
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/InvoiceList"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Invoices"
        ],
        "summary": "Create a sales invoice",
        "description": "Creates a sales invoice. By default the invoice is issued, posting the AR + VAT-control double-entry journal via the native validated path. Pass `issue: false` to leave it as a draft. `document_type: 'quote'` is never auto-issued. Requires scope `iqbooks:write`. Requires a vat_control-role account to exist in the organisation when the posting carries VAT; otherwise 422 unprocessable_entity (\"VAT Control account is required ...\").",
        "operationId": "createSalesInvoice",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              },
              "example": {
                "contact_id": 8,
                "invoice_date": "2026-01-15",
                "due_date": "2026-02-14",
                "lines": [
                  {
                    "account_id": 21,
                    "description": "Consulting",
                    "net_amount": 1000.0,
                    "vat_code": "ST",
                    "vat_amount": 200.0
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/InvoiceSingle"
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/sales-invoices/{id}": {
      "get": {
        "tags": [
          "IQ Books — Invoices"
        ],
        "summary": "Get a sales invoice",
        "operationId": "getSalesInvoice",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/InvoiceSingle"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/sales-invoices/{id}/void": {
      "post": {
        "tags": [
          "IQ Books — Invoices"
        ],
        "summary": "Void a sales invoice",
        "description": "Voids the invoice via the native service and returns the updated invoice. Requires scope `iqbooks:write`.",
        "operationId": "voidSalesInvoice",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/InvoiceSingle"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/purchase-invoices": {
      "get": {
        "tags": [
          "IQ Books — Invoices"
        ],
        "summary": "List purchase invoices",
        "description": "Paginated list. Same filters as sales invoices. Requires scope `iqbooks:read`.",
        "operationId": "listPurchaseInvoices",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contact_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "document_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "invoice",
                "credit_note",
                "quote"
              ]
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/InvoiceList"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Invoices"
        ],
        "summary": "Create a purchase invoice",
        "description": "Creates a purchase invoice. Issued by default (posts the AP + VAT-control journal). Pass `issue: false` for a draft. Requires scope `iqbooks:write`. Requires a vat_control-role account to exist in the organisation when the posting carries VAT; otherwise 422 unprocessable_entity (\"VAT Control account is required ...\").",
        "operationId": "createPurchaseInvoice",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              },
              "example": {
                "contact_id": 15,
                "invoice_date": "2026-01-12",
                "lines": [
                  {
                    "account_id": 60,
                    "description": "Stationery",
                    "net_amount": 50.0,
                    "vat_code": "ST",
                    "vat_amount": 10.0
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/InvoiceSingle"
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/purchase-invoices/{id}": {
      "get": {
        "tags": [
          "IQ Books — Invoices"
        ],
        "summary": "Get a purchase invoice",
        "operationId": "getPurchaseInvoice",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/InvoiceSingle"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/purchase-invoices/{id}/void": {
      "post": {
        "tags": [
          "IQ Books — Invoices"
        ],
        "summary": "Void a purchase invoice",
        "operationId": "voidPurchaseInvoice",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/InvoiceSingle"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/customer-receipts": {
      "get": {
        "tags": [
          "IQ Books — Settlements"
        ],
        "summary": "List customer receipts",
        "description": "Paginated list. Filters: `?contact_id`, `?search`, `?date_from`, `?date_to`, `?limit`/`?offset`. Requires scope `iqbooks:read`.",
        "operationId": "listCustomerReceipts",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "contact_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/SettlementList"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Settlements"
        ],
        "summary": "Record a customer receipt",
        "description": "Records a customer receipt and posts the settlement double-entry journal (bank to AR, plus FX line where applicable) via the native transactional service. Optional `allocations` apply the receipt to open sales invoices. Requires scope `iqbooks:write`.",
        "operationId": "createCustomerReceipt",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SettlementCreate"
              },
              "example": {
                "contact_id": 8,
                "bank_account_id": 12,
                "amount": 1200.0,
                "date": "2026-01-20",
                "reference": "BACS",
                "allocations": [
                  {
                    "invoice_id": 101,
                    "amount": 1200.0
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/SettlementSingle"
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/supplier-payments": {
      "get": {
        "tags": [
          "IQ Books — Settlements"
        ],
        "summary": "List supplier payments",
        "description": "Paginated list. Filters: `?contact_id`, `?search`, `?date_from`, `?date_to`, `?limit`/`?offset`. Requires scope `iqbooks:read`.",
        "operationId": "listSupplierPayments",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "contact_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/SettlementList"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Settlements"
        ],
        "summary": "Record a supplier payment",
        "description": "Records a supplier payment and posts the settlement journal (bank to AP, plus FX where applicable). Optional `allocations` apply the payment to open purchase invoices. Requires scope `iqbooks:write`.",
        "operationId": "createSupplierPayment",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SettlementCreate"
              },
              "example": {
                "contact_id": 15,
                "bank_account_id": 12,
                "amount": 60.0,
                "date": "2026-01-22",
                "allocations": [
                  {
                    "invoice_id": 220,
                    "amount": 60.0
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/SettlementSingle"
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidRequestFormat"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/journals": {
      "get": {
        "tags": [
          "IQ Books — Journals"
        ],
        "summary": "List journals",
        "description": "Entry-level paginated list of journal entries. Filters: `?search`, `?date_from`, `?date_to`, `?source_system`, `?status`, `?account_id`, `?limit`/`?offset`. Requires scope `iqbooks:read`.",
        "operationId": "listJournals",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "source_system",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Journal entries.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/JournalSummary"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "IQ Books — Journals"
        ],
        "summary": "Post a manual journal",
        "description": "Posts a balanced manual double-entry journal via the native adjustment service. At least 2 lines; each line has exactly one positive value (`debit` OR `credit`); total debits must equal total credits. An unbalanced journal is rejected with HTTP **422 `unprocessable_entity`**. Requires scope `iqbooks:write`. Requires a vat_control-role account to exist in the organisation when the posting carries VAT; otherwise 422 unprocessable_entity (\"VAT Control account is required ...\").",
        "operationId": "createJournal",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:write"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JournalCreate"
              },
              "example": {
                "entry_date": "2026-01-31",
                "description": "Depreciation - January",
                "lines": [
                  {
                    "account_id": 70,
                    "debit": 250.0,
                    "memo": "Depreciation expense"
                  },
                  {
                    "account_id": 18,
                    "credit": 250.0,
                    "memo": "Accumulated depreciation"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/JournalSingle"
          },
          "400": {
            "$ref": "#/components/responses/InvalidJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/api/v1/iqbooks/organisations/{organisationId}/journals/{id}": {
      "get": {
        "tags": [
          "IQ Books — Journals"
        ],
        "summary": "Get a journal entry",
        "operationId": "getJournal",
        "security": [
          {
            "ApiKeyBearer": [
              "iqbooks:read"
            ]
          },
          {
            "ApiKeyHeader": [
              "iqbooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/JournalSingle"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "iqk_<prefix>_<secret>",
        "description": "API key presented as `Authorization: Bearer iqk_...`. Scopes are enforced per operation."
      },
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key presented as `X-API-Key: iqk_...`. Equivalent to the Bearer scheme."
      },
      "FirstPartyJWT": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "First-party web-session JWT. Used ONLY by the `/api/v1/keys` management endpoints. On failure these endpoints return the standard error envelope (`401 unauthorized` for a missing token, `401 invalid_token` for an invalid/expired one), with a `request_id`."
      }
    },
    "headers": {
      "XRequestId": {
        "description": "Correlation id, also logged server-side. Present on every response.",
        "schema": {
          "type": "string"
        },
        "example": "aac12d9848a2"
      },
      "RateLimitLimit": {
        "description": "Requests permitted per window.",
        "schema": {
          "type": "integer"
        },
        "example": 120
      },
      "RateLimitPolicy": {
        "description": "Rate limit policy descriptor, e.g. \"120;w=60\" (limit;window-seconds).",
        "schema": {
          "type": "string"
        },
        "example": "120;w=60"
      },
      "RateLimitRemaining": {
        "description": "Requests remaining in the current window.",
        "schema": {
          "type": "integer"
        },
        "example": 118
      },
      "RateLimitReset": {
        "description": "Seconds until the window resets.",
        "schema": {
          "type": "integer"
        },
        "example": 60
      }
    },
    "parameters": {
      "OrganisationId": {
        "name": "organisationId",
        "in": "path",
        "required": true,
        "description": "IQ Books organisation id. The caller must be an accepted member; otherwise 404 (no existence leak).",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "ResourceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size, integer 1..200 (default 50). Out-of-range values return 422 invalid_request_format.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "required": false,
        "description": "Zero-based row offset (default 0).",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      }
    },
    "schemas": {
      "Scope": {
        "type": "string",
        "enum": [
          "reconcileiq:read",
          "reconcileiq:write",
          "codeiq:read",
          "codeiq:write",
          "iqbooks:read",
          "iqbooks:write"
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error",
          "request_id"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine code (e.g. validation_error, invalid_dataset_format, invalid_request_format, unauthorized, forbidden, not_found, conflict, unprocessable_entity, insufficient_credits, busy, rate_limited, internal_error, coding_failed, reconciliation_failed)."
              },
              "message": {
                "type": "string"
              },
              "details": {
                "description": "Optional, code-specific. May be an object, array, or string."
              }
            }
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Matches the X-Request-Id response header."
          }
        },
        "examples": [
          {
            "error": {
              "code": "not_found",
              "message": "Reconciliation not found"
            },
            "request_id": "e62473baa9ee"
          }
        ]
      },
      "ValidationErrorEnvelope": {
        "type": "object",
        "required": [
          "error",
          "request_id"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "validation_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "properties": {
                  "issues": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "path": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "DatasetFormatErrorEnvelope": {
        "type": "object",
        "required": [
          "error",
          "request_id"
        ],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_dataset_format"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "properties": {
                  "issues": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "path": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string"
                        },
                        "dataset": {
                          "type": "string",
                          "description": "bank or book."
                        },
                        "row_index": {
                          "type": "integer"
                        },
                        "field": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "RequestFormatErrorEnvelope": {
        "type": "object",
        "required": [
          "error",
          "request_id"
        ],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request_format"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "properties": {
                  "issues": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "path": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string"
                        },
                        "section": {
                          "type": "string",
                          "description": "e.g. transactions, chart_of_accounts, vat_codes."
                        },
                        "row_index": {
                          "type": "integer"
                        },
                        "field": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "InsufficientCreditsEnvelope": {
        "type": "object",
        "required": [
          "error",
          "request_id"
        ],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "insufficient_credits"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "properties": {
                  "required": {
                    "type": "integer"
                  },
                  "available": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PaginatedEnvelope": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {}
          },
          "pagination": {
            "type": "object",
            "required": [
              "limit",
              "offset",
              "count"
            ],
            "properties": {
              "limit": {
                "type": "integer"
              },
              "offset": {
                "type": "integer"
              },
              "count": {
                "type": "integer",
                "description": "Number of rows in this page (data.length)."
              },
              "total": {
                "type": "integer",
                "description": "Total matching rows when the native service reports it; may be absent."
              }
            }
          }
        }
      },
      "CanonicalRow": {
        "type": "object",
        "required": [
          "date",
          "amount",
          "description"
        ],
        "additionalProperties": false,
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "description": "Exact ISO 8601 YYYY-MM-DD; must be a real calendar date."
          },
          "amount": {
            "type": "number",
            "description": "Signed finite number (negatives allowed)."
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        }
      },
      "ReconciliationRequest": {
        "type": "object",
        "required": [
          "bank",
          "book"
        ],
        "additionalProperties": false,
        "properties": {
          "bank": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50000,
            "items": {
              "$ref": "#/components/schemas/CanonicalRow"
            }
          },
          "book": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50000,
            "items": {
              "$ref": "#/components/schemas/CanonicalRow"
            }
          },
          "options": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "allowedDateDifferenceInDays": {
                "type": "integer",
                "minimum": 0,
                "maximum": 30,
                "default": 3
              },
              "includeMatched": {
                "type": "boolean",
                "default": false
              },
              "allowOverage": {
                "type": "boolean",
                "default": false
              }
            }
          }
        }
      },
      "ReconciliationResult": {
        "type": "object",
        "properties": {
          "summary": {
            "type": "object",
            "properties": {
              "bank_rows": {
                "type": "integer"
              },
              "book_rows": {
                "type": "integer"
              },
              "matched_count": {
                "type": "integer"
              },
              "missing_from_books_count": {
                "type": "integer"
              },
              "remove_from_books_count": {
                "type": "integer"
              },
              "verified": {
                "type": "boolean"
              }
            }
          },
          "credits_charged": {
            "type": "integer",
            "minimum": 0,
            "description": "Credits charged for this run. 0 unless the run completed AND was balance-verified (an unverifiable reconciliation is never charged). Mirrored at the top level of the GET response too."
          },
          "missing_from_books": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NormalisedRow"
            }
          },
          "remove_from_books": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NormalisedRow"
            }
          },
          "verification": {
            "type": "object",
            "properties": {
              "is_verified": {
                "type": "boolean"
              },
              "expected_net_discrepancy": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Net discrepancy implied by the two datasets (total bank minus total book). null if the engine could not compute a balance check."
              },
              "actual_net_effect": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Net effect of the unreconciled items (sum of missing_from_books minus sum of remove_from_books). null if not computable."
              },
              "reason": {
                "type": "string",
                "description": "Stable human-readable explanation of the verification outcome (verified, or why it could not be balance-verified and therefore was not charged). Always present on a completed run."
              }
            }
          },
          "matched": {
            "type": "array",
            "description": "Present only when options.includeMatched was true (capped at 500 pairs).",
            "items": {
              "type": "object",
              "properties": {
                "bank": {
                  "$ref": "#/components/schemas/NormalisedRow"
                },
                "book": {
                  "$ref": "#/components/schemas/NormalisedRow"
                },
                "date_difference": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          }
        },
        "description": "Completed reconciliation result. The verification contract is always present and machine-readable: summary.verified, top-level credits_charged, and verification.{is_verified,expected_net_discrepancy,actual_net_effect,reason}. A run that completes but cannot be balance-verified is intended behaviour (status stays 'completed') and is not charged (credits_charged = 0). Branch on verification.is_verified / credits_charged, not on status alone."
      },
      "NormalisedRow": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string"
          },
          "amount": {
            "description": "Parsed number when parseable, otherwise the original string."
          },
          "description": {
            "type": "string"
          }
        }
      },
      "ReconciliationState": {
        "type": "object",
        "required": [
          "reconciliation_id",
          "status",
          "created_at"
        ],
        "properties": {
          "reconciliation_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "completed",
              "failed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "description": "Present once terminal."
          },
          "credits_charged": {
            "type": "integer",
            "minimum": 0,
            "description": "Present once the run is completed. Credits charged for this reconciliation: 0 if it could not be balance-verified (intended behaviour, never charged), or n = bank rows if verified. Mirrors result.credits_charged."
          },
          "result": {
            "$ref": "#/components/schemas/ReconciliationResult"
          },
          "error": {
            "type": "object",
            "description": "Present only when status is failed.",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {}
            }
          }
        }
      },
      "CodingJobRequest": {
        "type": "object",
        "required": [
          "transactions",
          "chart_of_accounts"
        ],
        "additionalProperties": false,
        "properties": {
          "transactions": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20000,
            "items": {
              "type": "object",
              "required": [
                "date",
                "description",
                "amount"
              ],
              "additionalProperties": false,
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date",
                  "description": "Exact YYYY-MM-DD, real calendar date."
                },
                "description": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 500
                },
                "amount": {
                  "type": "number",
                  "description": "Signed finite number; money out is negative."
                },
                "merchant": {
                  "type": "string",
                  "maxLength": 200
                },
                "reference": {
                  "type": "string",
                  "maxLength": 100
                }
              }
            }
          },
          "chart_of_accounts": {
            "type": "array",
            "minItems": 1,
            "maxItems": 2000,
            "items": {
              "type": "object",
              "required": [
                "code",
                "name",
                "type"
              ],
              "additionalProperties": false,
              "properties": {
                "code": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 50
                },
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "type": {
                  "type": "string",
                  "description": "One of INCOME, EXPENSE, ASSET, LIABILITY, EQUITY (case-insensitive). REVENUE is accepted as an alias of INCOME."
                }
              }
            }
          },
          "vat_codes": {
            "type": "array",
            "minItems": 1,
            "description": "Optional. If omitted, the default UK universal set (ST/RR/ZR/EX/NV) is used.",
            "items": {
              "type": "object",
              "required": [
                "code",
                "name",
                "rate"
              ],
              "additionalProperties": false,
              "properties": {
                "code": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 50
                },
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "rate": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                }
              }
            }
          },
          "options": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "allowOverage": {
                "type": "boolean",
                "default": false
              },
              "businessContext": {
                "type": "string",
                "maxLength": 2000
              }
            }
          }
        }
      },
      "CodedTransaction": {
        "type": "object",
        "properties": {
          "input": {
            "type": "object",
            "description": "Echo of the submitted row (merchant/reference only if supplied).",
            "properties": {
              "date": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "merchant": {
                "type": "string"
              },
              "reference": {
                "type": "string"
              }
            }
          },
          "suggested_account_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "suggested_account_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "suggested_account_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "May be null even when an account is suggested."
          },
          "vat_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Universal VAT code: NV, ST, RR, EX, ZR."
          },
          "vat_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "net_amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "vat_amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "confidence": {
            "type": [
              "number",
              "null"
            ]
          },
          "coding_method": {
            "type": [
              "string",
              "null"
            ],
            "description": "Non-exhaustive; e.g. transfer_detection, invoice_payment, historical, universal_pattern, mcc_category_match, semantic, user_learning, riq_enhancement. Treat as an opaque string."
          }
        }
      },
      "CodingJobResult": {
        "type": "object",
        "properties": {
          "summary": {
            "type": "object",
            "properties": {
              "transaction_count": {
                "type": "integer"
              },
              "coded_count": {
                "type": "integer"
              },
              "by_method": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              }
            }
          },
          "transactions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CodedTransaction"
            }
          }
        }
      },
      "CodingJobState": {
        "type": "object",
        "required": [
          "coding_job_id",
          "status",
          "created_at"
        ],
        "properties": {
          "coding_job_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "completed",
              "failed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "$ref": "#/components/schemas/CodingJobResult"
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {}
            }
          }
        }
      },
      "Organisation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "trading_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "legal_form": {
            "type": [
              "string",
              "null"
            ]
          },
          "country_code": {
            "type": "string"
          },
          "base_currency": {
            "type": "string"
          },
          "vat_registered": {
            "type": "boolean"
          },
          "vat_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "vat_scheme": {
            "type": [
              "string",
              "null"
            ]
          },
          "accounting_basis": {
            "type": [
              "string",
              "null"
            ]
          },
          "fiscal_year_end_month": {
            "type": [
              "integer",
              "null"
            ]
          },
          "fiscal_year_end_day": {
            "type": [
              "integer",
              "null"
            ]
          },
          "registration_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "period_locked_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "role": {
            "type": [
              "string",
              "null"
            ],
            "description": "Caller's membership role."
          },
          "account_count": {
            "type": "integer"
          },
          "posted_entries": {
            "type": "integer"
          },
          "bank_account_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "organisation_id": {
            "type": "integer"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "account_type": {
            "type": "string",
            "description": "ASSET, LIABILITY, EQUITY, REVENUE, EXPENSE, SUSPENSE (native uppercase)."
          },
          "role": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_system": {
            "type": "boolean"
          },
          "is_bank_account": {
            "type": "boolean"
          },
          "archived": {
            "type": "boolean"
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ContactCreate": {
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "email": {
            "type": "string",
            "maxLength": 255
          },
          "phone": {
            "type": "string",
            "maxLength": 60
          },
          "address_line1": {
            "type": "string",
            "maxLength": 255
          },
          "address_line2": {
            "type": "string",
            "maxLength": 255
          },
          "address_city": {
            "type": "string",
            "maxLength": 120
          },
          "address_postcode": {
            "type": "string",
            "maxLength": 20
          },
          "address_country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          },
          "vat_number": {
            "type": "string",
            "maxLength": 50
          },
          "default_payment_terms_days": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365
          },
          "default_currency_code": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3
          },
          "notes": {
            "type": "string",
            "maxLength": 5000
          }
        }
      },
      "ContactPatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "email": {
            "type": "string",
            "maxLength": 255
          },
          "phone": {
            "type": "string",
            "maxLength": 60
          },
          "address_line1": {
            "type": "string",
            "maxLength": 255
          },
          "address_line2": {
            "type": "string",
            "maxLength": 255
          },
          "address_city": {
            "type": "string",
            "maxLength": 120
          },
          "address_postcode": {
            "type": "string",
            "maxLength": 20
          },
          "address_country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          },
          "vat_number": {
            "type": "string",
            "maxLength": 50
          },
          "default_payment_terms_days": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365
          },
          "default_currency_code": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3
          },
          "notes": {
            "type": "string",
            "maxLength": 5000
          }
        }
      },
      "Contact": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "organisation_id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "address_line1": {
            "type": [
              "string",
              "null"
            ]
          },
          "address_line2": {
            "type": [
              "string",
              "null"
            ]
          },
          "address_city": {
            "type": [
              "string",
              "null"
            ]
          },
          "address_postcode": {
            "type": [
              "string",
              "null"
            ]
          },
          "address_country": {
            "type": [
              "string",
              "null"
            ]
          },
          "vat_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "default_payment_terms_days": {
            "type": [
              "integer",
              "null"
            ]
          },
          "default_currency_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "outstanding_amount": {
            "type": "number",
            "description": "Decimal major units, present on list responses that include it."
          },
          "invoice_count": {
            "type": "integer"
          },
          "archived": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BankAccount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "organisation_id": {
            "type": "integer"
          },
          "account_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Linked ledger account id."
          },
          "display_name": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "sort_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "account_number_last4": {
            "type": [
              "string",
              "null"
            ]
          },
          "iban_last4": {
            "type": [
              "string",
              "null"
            ]
          },
          "feed_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "ledger_account_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "ledger_account_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BankTransaction": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "organisation_id": {
            "type": "integer"
          },
          "bank_account_id": {
            "type": "integer"
          },
          "bank_account_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "journal_entry_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Set once confirmed."
          },
          "transaction_date": {
            "type": "string",
            "format": "date"
          },
          "description": {
            "type": "string"
          },
          "direction": {
            "type": "string",
            "enum": [
              "money_in",
              "money_out"
            ]
          },
          "amount": {
            "type": "number",
            "description": "Decimal major units, positive."
          },
          "currency": {
            "type": "string"
          },
          "category_account_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "category_account_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "category_account_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "category_account_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "vat_amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "vat_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "description": "Lifecycle status. A newly created manual transaction is 'draft'; once confirmed it is 'confirmed'. May also be 'ignored'. Branch on 'draft' (not 'pending') for un-confirmed rows."
          },
          "entry_route": {
            "type": [
              "string",
              "null"
            ]
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "InvoiceLineCreate": {
        "type": "object",
        "required": [
          "account_id",
          "net_amount"
        ],
        "additionalProperties": false,
        "properties": {
          "description": {
            "type": "string",
            "maxLength": 512
          },
          "account_id": {
            "type": "integer",
            "minimum": 1,
            "description": "Income account (sales) or expense account (purchase)."
          },
          "quantity": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "unit_price": {
            "type": "number"
          },
          "net_amount": {
            "type": "number"
          },
          "vat_code": {
            "type": "string",
            "maxLength": 20
          },
          "vat_rate": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Fraction (e.g. 0.2 for 20%)."
          },
          "vat_amount": {
            "type": "number",
            "minimum": 0
          }
        }
      },
      "InvoiceCreate": {
        "type": "object",
        "required": [
          "contact_id",
          "lines"
        ],
        "additionalProperties": false,
        "properties": {
          "contact_id": {
            "type": "integer",
            "minimum": 1,
            "description": "Customer id (sales) or supplier id (purchase)."
          },
          "invoice_number": {
            "type": "string",
            "maxLength": 60
          },
          "invoice_date": {
            "type": "string",
            "format": "date"
          },
          "due_date": {
            "type": "string",
            "format": "date"
          },
          "currency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3
          },
          "description": {
            "type": "string",
            "maxLength": 512
          },
          "document_type": {
            "type": "string",
            "enum": [
              "invoice",
              "credit_note",
              "quote"
            ]
          },
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/InvoiceLineCreate"
            }
          },
          "issue": {
            "type": "boolean",
            "description": "Default true (posts the journal). false leaves a draft. Ignored (never issued) for quotes."
          }
        }
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "organisation_id": {
            "type": "integer"
          },
          "contact_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "contact_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "invoice_number": {
            "type": "string"
          },
          "invoice_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "document_type": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "net_amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "vat_amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "gross_amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "outstanding_amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "journal_entry_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "lines": {
            "type": "array",
            "description": "Present on single-invoice responses.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "line_number": {
                  "type": "integer"
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "account_id": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "account_code": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "account_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "quantity": {
                  "type": "number"
                },
                "unit_price": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "net_amount": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "vat_code": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "vat_rate": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "vat_amount": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "gross_amount": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "SettlementCreate": {
        "type": "object",
        "required": [
          "contact_id",
          "bank_account_id",
          "amount"
        ],
        "additionalProperties": false,
        "properties": {
          "contact_id": {
            "type": "integer",
            "minimum": 1
          },
          "bank_account_id": {
            "type": "integer",
            "minimum": 1
          },
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "currency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3
          },
          "reference": {
            "type": "string",
            "maxLength": 120
          },
          "description": {
            "type": "string",
            "maxLength": 512
          },
          "allocations": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "invoice_id",
                "amount"
              ],
              "additionalProperties": false,
              "properties": {
                "invoice_id": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "Sales invoice id (receipts) or purchase invoice id (payments)."
                },
                "amount": {
                  "type": "number",
                  "exclusiveMinimum": 0
                }
              }
            }
          }
        }
      },
      "Settlement": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "organisation_id": {
            "type": "integer"
          },
          "contact_id": {
            "type": "integer"
          },
          "contact_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "bank_account_id": {
            "type": "integer"
          },
          "bank_account_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "journal_entry_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "currency": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "allocations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "invoice_id": {
                  "type": "integer"
                },
                "invoice_number": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "amount": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "JournalLineCreate": {
        "type": "object",
        "required": [
          "account_id"
        ],
        "additionalProperties": false,
        "properties": {
          "account_id": {
            "type": "integer",
            "minimum": 1
          },
          "debit": {
            "type": "number",
            "minimum": 0
          },
          "credit": {
            "type": "number",
            "minimum": 0
          },
          "memo": {
            "type": "string",
            "maxLength": 512
          },
          "vat_code": {
            "type": "string",
            "maxLength": 20
          },
          "vat_amount": {
            "type": "number"
          }
        },
        "description": "Each line must have exactly one positive value: debit OR credit."
      },
      "JournalCreate": {
        "type": "object",
        "required": [
          "lines"
        ],
        "additionalProperties": false,
        "properties": {
          "entry_date": {
            "type": "string",
            "format": "date"
          },
          "description": {
            "type": "string",
            "maxLength": 512
          },
          "lines": {
            "type": "array",
            "minItems": 2,
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/JournalLineCreate"
            }
          }
        }
      },
      "JournalSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "je_number": {
            "type": [
              "integer",
              "null"
            ]
          },
          "entry_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "source_system": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "primary_vat_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_vat_amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "posted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "JournalDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "je_number": {
            "type": [
              "integer",
              "null"
            ]
          },
          "organisation_id": {
            "type": "integer"
          },
          "entry_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "source_system": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "posted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "line_number": {
                  "type": "integer"
                },
                "account_id": {
                  "type": "integer"
                },
                "account_code": {
                  "type": "string"
                },
                "account_name": {
                  "type": "string"
                },
                "account_type": {
                  "type": "string"
                },
                "debit": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "credit": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "memo": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "vat_code": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "vat_rate": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "vat_amount": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, invalid, or revoked API key.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "unauthorized",
                "message": "Missing API key"
              },
              "request_id": "9451f95c2916"
            }
          }
        }
      },
      "FirstPartyUnauthorized": {
        "description": "First-party web-session (JWT) auth failure on a /api/v1/keys endpoint. Standard error envelope, identical in shape to API-key auth failures: `401 unauthorized` when the token is missing, `401 invalid_token` when it is invalid or expired.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "missing_token": {
                "summary": "No web-session token",
                "value": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Authentication required"
                  },
                  "request_id": "12075d91e15d"
                }
              },
              "invalid_token": {
                "summary": "Invalid or expired token",
                "value": {
                  "error": {
                    "code": "invalid_token",
                    "message": "Invalid or expired token"
                  },
                  "request_id": "733cf38711ba"
                }
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "Authenticated but the key lacks the required scope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "forbidden",
                "message": "Missing required scope: reconcileiq:write",
                "details": {
                  "required_scope": "reconcileiq:write"
                }
              },
              "request_id": "7739e3f9f342"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found, or not visible to the caller (no existence leak).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "not_found",
                "message": "Reconciliation not found"
              },
              "request_id": "e62473baa9ee"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflict with current resource state (e.g. duplicate code, already voided).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "conflict",
                "message": "An account with that code already exists"
              },
              "request_id": "1a2b3c4d5e6f"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Structurally invalid request (Zod). Distinct from the 422 format-contract gate.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "validation_error",
                "message": "Request validation failed",
                "details": {
                  "issues": [
                    {
                      "path": "scopes",
                      "message": "Required",
                      "code": "invalid_type"
                    }
                  ]
                }
              },
              "request_id": "abcd1234ef56"
            }
          }
        }
      },
      "InvalidJson": {
        "description": "The request body is not syntactically valid JSON. Returned in the standard envelope with an X-Request-Id, before authentication runs.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "invalid_json",
                "message": "Request body is not valid JSON",
                "details": {
                  "reason": "Expected property name or '}' in JSON at position 1"
                }
              },
              "request_id": "3f36820790a3"
            }
          }
        }
      },
      "InvalidDatasetFormat": {
        "description": "ReconcileIQ canonical dataset contract violation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/DatasetFormatErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "invalid_dataset_format",
                "message": "Dataset does not conform to the required canonical format. Submit datasets already in the required schema: rows of { date: \"YYYY-MM-DD\", amount: <signed number>, description: <1..500 chars> }. No mapping or auto-detection is performed.",
                "details": {
                  "issues": [
                    {
                      "path": "bank.2.amount",
                      "message": "amount must be a number",
                      "code": "invalid_type",
                      "dataset": "bank",
                      "row_index": 2,
                      "field": "amount"
                    }
                  ]
                }
              },
              "request_id": "9f8e7d6c5b4a"
            }
          }
        }
      },
      "InvalidRequestFormat": {
        "description": "CodeIQ / IQ Books strict-format contract violation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RequestFormatErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "invalid_request_format",
                "message": "Request does not conform to the required format.",
                "details": {
                  "issues": [
                    {
                      "path": "chart_of_accounts.0.type",
                      "message": "type must be one of INCOME, EXPENSE, ASSET, LIABILITY, EQUITY",
                      "code": "custom",
                      "section": "chart_of_accounts",
                      "row_index": 0,
                      "field": "type"
                    }
                  ]
                }
              },
              "request_id": "112233445566"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "Semantically rejected (e.g. an unbalanced journal: total debits != total credits).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "unprocessable_entity",
                "message": "Journal does not balance: total debits must equal total credits"
              },
              "request_id": "778899aabbcc"
            }
          }
        }
      },
      "InsufficientCredits": {
        "description": "Not enough credits to run/charge the job.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/InsufficientCreditsEnvelope"
            },
            "example": {
              "error": {
                "code": "insufficient_credits",
                "message": "Insufficient credits for this reconciliation",
                "details": {
                  "required": 1264,
                  "available": 300
                }
              },
              "request_id": "ddeeff001122"
            }
          }
        }
      },
      "Busy": {
        "description": "The serial job queue for this product is full; retry shortly.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "busy",
                "message": "Reconciliation queue is full, retry shortly",
                "details": {
                  "max_queue_length": 25
                }
              },
              "request_id": "334455667788"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Per-key rate limit exceeded (120 requests / 60s window).",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "Too many requests, slow down",
                "details": {
                  "window_ms": 60000,
                  "max": 120
                }
              },
              "request_id": "99aabbccddee"
            }
          }
        }
      },
      "ContactList": {
        "description": "Paginated contacts.",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/PaginatedEnvelope"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "ContactSingle": {
        "description": "A single contact.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "data"
              ],
              "properties": {
                "data": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          }
        }
      },
      "InvoiceList": {
        "description": "Paginated invoices.",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/PaginatedEnvelope"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "InvoiceSingle": {
        "description": "A single invoice (with lines).",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "data"
              ],
              "properties": {
                "data": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          }
        }
      },
      "SettlementList": {
        "description": "Paginated receipts/payments.",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/PaginatedEnvelope"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Settlement"
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "SettlementSingle": {
        "description": "A single receipt/payment (with allocations).",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "data"
              ],
              "properties": {
                "data": {
                  "$ref": "#/components/schemas/Settlement"
                }
              }
            }
          }
        }
      },
      "JournalSingle": {
        "description": "A single journal entry with lines.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "data"
              ],
              "properties": {
                "data": {
                  "$ref": "#/components/schemas/JournalDetail"
                }
              }
            }
          }
        }
      }
    }
  }
}
