{
  "openapi": "3.1.0",
  "info": {
    "title": "Quarterwise API",
    "version": "1.0.0",
    "description": "The Quarterwise API lets you push property and self-employment income and expenses into your Quarterwise books from your own scripts, spreadsheets or systems, and read back what is recorded. Filing stays in the app: you review the figures and send each quarterly update and the tax return yourself in the browser.\n\nAll endpoints live under https://quarterwise.co.uk/api/v1 and speak JSON over HTTPS.\n\nRate limits: 120 requests per minute per key; 5,000 transactions written per rolling 24 hours.\n\nNo HMRC submission. Nothing here files anything. A tax return carries a legal declaration that the figures are correct and complete, so quarterly updates and the tax return are always confirmed and sent by the account holder in the app.\n\nSources are read-only. Properties and self-employment businesses are set up in the app; the API cannot create, rename or delete them.\n\nNo receipt upload, no tax calculations and no HMRC data. The API covers the transaction ledger and the reference data needed to write to it.\n\nTransactions posted without a category are not categorised automatically. They land in the app's review queue for the account holder to sort.\n\nFull guide: https://quarterwise.co.uk/developers/docs",
    "contact": {
      "name": "Quarterwise support",
      "email": "support@quarterwise.co.uk",
      "url": "https://quarterwise.co.uk/developers"
    },
    "termsOfService": "https://quarterwise.co.uk/terms"
  },
  "servers": [
    {
      "url": "https://quarterwise.co.uk"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "externalDocs": {
    "description": "API reference",
    "url": "https://quarterwise.co.uk/developers/docs"
  },
  "paths": {
    "/api/v1/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Who this key belongs to, and what it can do",
        "description": "Returns the account the key is attached to, its plan, and the capabilities of the API. Useful as a connection test.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                },
                "example": {
                  "accountId": "0d4f3c1a-8b2e-4d5f-9a6c-2f7e8b9c0d1e",
                  "plan": "pro",
                  "capabilities": {
                    "readBookkeeping": true,
                    "writeBookkeeping": true,
                    "hmrcSubmission": false
                  },
                  "note": "Submitting to HMRC is not available through the API. Quarterly updates and the tax return are confirmed and sent by the account holder in the browser."
                }
              }
            }
          },
          "401": {
            "description": "missing_key: No Authorization: Bearer header was sent. invalid_key: The key is malformed, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_key": {
                    "value": {
                      "error": {
                        "code": "missing_key",
                        "message": "No Authorization: Bearer header was sent."
                      }
                    }
                  },
                  "invalid_key": {
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "The key is malformed, unknown or revoked."
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "upgrade_required: The key belongs to an account whose plan does not include API access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "upgrade_required": {
                    "value": {
                      "error": {
                        "code": "upgrade_required",
                        "message": "The key belongs to an account whose plan does not include API access."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "not_found: The API is not enabled in this environment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "not_found": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "The API is not enabled in this environment."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: More than 120 requests in a minute on this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "More than 120 requests in a minute on this key."
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "server_error: Something went wrong on our side. Safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "server_error": {
                    "value": {
                      "error": {
                        "code": "server_error",
                        "message": "Something went wrong on our side. Safe to retry."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sources": {
      "get": {
        "operationId": "getSources",
        "summary": "List the account’s properties and businesses",
        "description": "Every income source on the account: UK properties, overseas properties and self-employment businesses. The id is what you pass to the transactions endpoints.\n\nsharePercent is the account holder’s share of a jointly held property, as a percentage. transactionCount is the number of ledger entries currently recorded against the source.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sources": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Source"
                      }
                    }
                  }
                },
                "example": {
                  "sources": [
                    {
                      "id": "6a1f9b3e-4c2d-4e8f-b1a7-5d9c0e2f4a6b",
                      "name": "14 Brookfield Road",
                      "type": "uk-property",
                      "countryCode": null,
                      "role": "owner",
                      "sharePercent": 100,
                      "transactionCount": 128
                    },
                    {
                      "id": "8c3e5d7f-2a4b-4c6d-8e0f-1b3d5f7a9c2e",
                      "name": "Flat 2, Sea View",
                      "type": "uk-property",
                      "countryCode": null,
                      "role": "sharer",
                      "sharePercent": 50,
                      "transactionCount": 41
                    },
                    {
                      "id": "2f7c4a9d-1e3b-4d5c-a8f0-6b2e9d4c1a7f",
                      "name": "Villa Rosa",
                      "type": "foreign-property",
                      "countryCode": "ES",
                      "role": "owner",
                      "sharePercent": 100,
                      "transactionCount": 23
                    },
                    {
                      "id": "4b9e2d6f-8a1c-4f3e-b5d7-0c8a6e2f4b9d",
                      "name": "Joinery and carpentry",
                      "type": "self-employment",
                      "countryCode": null,
                      "role": "owner",
                      "sharePercent": 100,
                      "transactionCount": 57
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "missing_key: No Authorization: Bearer header was sent. invalid_key: The key is malformed, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_key": {
                    "value": {
                      "error": {
                        "code": "missing_key",
                        "message": "No Authorization: Bearer header was sent."
                      }
                    }
                  },
                  "invalid_key": {
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "The key is malformed, unknown or revoked."
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "upgrade_required: The key belongs to an account whose plan does not include API access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "upgrade_required": {
                    "value": {
                      "error": {
                        "code": "upgrade_required",
                        "message": "The key belongs to an account whose plan does not include API access."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "not_found: The API is not enabled in this environment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "not_found": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "The API is not enabled in this environment."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: More than 120 requests in a minute on this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "More than 120 requests in a minute on this key."
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "server_error: Something went wrong on our side. Safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "server_error": {
                    "value": {
                      "error": {
                        "code": "server_error",
                        "message": "Something went wrong on our side. Safe to retry."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/categories": {
      "get": {
        "operationId": "getCategories",
        "summary": "Valid category keys, per source type",
        "description": "The category keys accepted when posting transactions. Keys are HMRC’s own field names, and they differ by source type: a key that is valid for a UK property may not be valid for an overseas property or a self-employment business.\n\nWith no sourceType parameter the response covers all three source types, keyed by type. The example below is the real, current response with no parameter.",
        "parameters": [
          {
            "name": "sourceType",
            "in": "query",
            "required": false,
            "description": "One of uk-property, foreign-property, self-employment. Omit for all three.",
            "schema": {
              "type": "string",
              "enum": [
                "uk-property",
                "foreign-property",
                "self-employment"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Category sets keyed by source type.",
                  "properties": {
                    "uk-property": {
                      "$ref": "#/components/schemas/CategorySet"
                    },
                    "foreign-property": {
                      "$ref": "#/components/schemas/CategorySet"
                    },
                    "self-employment": {
                      "$ref": "#/components/schemas/CategorySet"
                    }
                  }
                },
                "example": {
                  "uk-property": {
                    "income": [
                      {
                        "key": "periodAmount",
                        "label": "Rent received"
                      },
                      {
                        "key": "premiumsOfLeaseGrant",
                        "label": "Lease premiums"
                      },
                      {
                        "key": "reversePremiums",
                        "label": "Reverse premiums"
                      },
                      {
                        "key": "otherIncome",
                        "label": "Other property income"
                      },
                      {
                        "key": "rentARoomRentsReceived",
                        "label": "Rent a Room — rents received"
                      },
                      {
                        "key": "taxDeducted",
                        "label": "Tax withheld from rent"
                      }
                    ],
                    "expense": [
                      {
                        "key": "premisesRunningCosts",
                        "label": "Rent, rates, insurance, ground rents"
                      },
                      {
                        "key": "repairsAndMaintenance",
                        "label": "Repairs & maintenance"
                      },
                      {
                        "key": "financialCosts",
                        "label": "Other finance costs"
                      },
                      {
                        "key": "residentialFinancialCost",
                        "label": "Residential mortgage interest"
                      },
                      {
                        "key": "professionalFees",
                        "label": "Legal, management & professional fees"
                      },
                      {
                        "key": "costOfServices",
                        "label": "Cost of services"
                      },
                      {
                        "key": "travelCosts",
                        "label": "Travel"
                      },
                      {
                        "key": "other",
                        "label": "Other allowable expenses"
                      },
                      {
                        "key": "residentialFinancialCostsCarriedForward",
                        "label": "Residential finance costs brought forward"
                      },
                      {
                        "key": "rentARoomAmountClaimed",
                        "label": "Rent a Room — amount claimed"
                      }
                    ]
                  },
                  "foreign-property": {
                    "income": [
                      {
                        "key": "rentIncome",
                        "label": "Rent received (overseas)"
                      },
                      {
                        "key": "premiumsOfLeaseGrant",
                        "label": "Lease premiums"
                      },
                      {
                        "key": "otherPropertyIncome",
                        "label": "Other property income"
                      },
                      {
                        "key": "foreignTaxPaidOrDeducted",
                        "label": "Foreign tax paid on this income"
                      },
                      {
                        "key": "specialWithholdingTaxOrUkTaxPaid",
                        "label": "Special withholding or UK tax paid"
                      }
                    ],
                    "expense": [
                      {
                        "key": "premisesRunningCosts",
                        "label": "Rent, rates, insurance, ground rents"
                      },
                      {
                        "key": "repairsAndMaintenance",
                        "label": "Repairs & maintenance"
                      },
                      {
                        "key": "financialCosts",
                        "label": "Other finance costs"
                      },
                      {
                        "key": "residentialFinancialCost",
                        "label": "Residential mortgage interest"
                      },
                      {
                        "key": "professionalFees",
                        "label": "Legal, management & professional fees"
                      },
                      {
                        "key": "costOfServices",
                        "label": "Cost of services"
                      },
                      {
                        "key": "travelCosts",
                        "label": "Travel"
                      },
                      {
                        "key": "other",
                        "label": "Other allowable expenses"
                      },
                      {
                        "key": "broughtFwdResidentialFinancialCost",
                        "label": "Residential finance costs brought forward"
                      }
                    ]
                  },
                  "self-employment": {
                    "income": [
                      {
                        "key": "turnover",
                        "label": "Sales and business income"
                      },
                      {
                        "key": "other",
                        "label": "Other business income"
                      },
                      {
                        "key": "seTaxDeducted",
                        "label": "Tax withheld from pay (CIS)"
                      }
                    ],
                    "expense": [
                      {
                        "key": "costOfGoods",
                        "label": "Cost of goods or materials"
                      },
                      {
                        "key": "paymentsToSubcontractors",
                        "label": "Payments to subcontractors"
                      },
                      {
                        "key": "wagesAndStaffCosts",
                        "label": "Wages and staff costs"
                      },
                      {
                        "key": "carVanTravelExpenses",
                        "label": "Car, van and travel"
                      },
                      {
                        "key": "premisesRunningCosts",
                        "label": "Rent, rates, power and insurance"
                      },
                      {
                        "key": "maintenanceCosts",
                        "label": "Repairs and maintenance"
                      },
                      {
                        "key": "adminCosts",
                        "label": "Phone, stationery and admin"
                      },
                      {
                        "key": "advertisingCosts",
                        "label": "Advertising and marketing"
                      },
                      {
                        "key": "interestOnBankOtherLoans",
                        "label": "Bank and loan interest"
                      },
                      {
                        "key": "professionalFees",
                        "label": "Accountancy, legal and professional fees"
                      },
                      {
                        "key": "otherExpenses",
                        "label": "Other allowable business expenses"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "missing_key: No Authorization: Bearer header was sent. invalid_key: The key is malformed, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_key": {
                    "value": {
                      "error": {
                        "code": "missing_key",
                        "message": "No Authorization: Bearer header was sent."
                      }
                    }
                  },
                  "invalid_key": {
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "The key is malformed, unknown or revoked."
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "upgrade_required: The key belongs to an account whose plan does not include API access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "upgrade_required": {
                    "value": {
                      "error": {
                        "code": "upgrade_required",
                        "message": "The key belongs to an account whose plan does not include API access."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "not_found: The API is not enabled in this environment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "not_found": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "The API is not enabled in this environment."
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "invalid_source_type: sourceType is present but not one of the three valid values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_source_type": {
                    "value": {
                      "error": {
                        "code": "invalid_source_type",
                        "message": "sourceType is present but not one of the three valid values."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: More than 120 requests in a minute on this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "More than 120 requests in a minute on this key."
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "server_error: Something went wrong on our side. Safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "server_error": {
                    "value": {
                      "error": {
                        "code": "server_error",
                        "message": "Something went wrong on our side. Safe to retry."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sources/{id}/transactions": {
      "get": {
        "operationId": "listTransactions",
        "summary": "List transactions for a source",
        "description": "Ledger entries for one source, newest first. Pagination is cursor-based: when there are more results, nextCursor is set; pass it back as the cursor parameter to fetch the next page. On the last page nextCursor is null.\n\ncategory is null for entries that have not been categorised yet, and description and externalRef are null when they were not provided.\n\nnextCursor is null when there are no further pages.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "A source id from GET /api/v1/sources.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Earliest date to include, YYYY-MM-DD, inclusive.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Latest date to include, YYYY-MM-DD, inclusive.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The nextCursor value from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, default 50, maximum 200.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "nextCursor is null when there are no further pages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionList"
                },
                "example": {
                  "transactions": [
                    {
                      "id": "f3a9c1e5-7b2d-4f6a-8c0e-9d1b3f5a7c9e",
                      "date": "2026-05-14",
                      "type": "expense",
                      "category": "repairsAndMaintenance",
                      "amountPence": 8450,
                      "description": "Boiler service",
                      "externalRef": null
                    },
                    {
                      "id": "e2b8d0f4-6a1c-4e5b-9d7f-8c0a2e4b6d8f",
                      "date": "2026-05-01",
                      "type": "income",
                      "category": "periodAmount",
                      "amountPence": 95000,
                      "description": "May rent",
                      "externalRef": "may-rent-2026"
                    }
                  ],
                  "nextCursor": "e2b8d0f4-6a1c-4e5b-9d7f-8c0a2e4b6d8f"
                }
              }
            }
          },
          "401": {
            "description": "missing_key: No Authorization: Bearer header was sent. invalid_key: The key is malformed, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_key": {
                    "value": {
                      "error": {
                        "code": "missing_key",
                        "message": "No Authorization: Bearer header was sent."
                      }
                    }
                  },
                  "invalid_key": {
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "The key is malformed, unknown or revoked."
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "upgrade_required: The key belongs to an account whose plan does not include API access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "upgrade_required": {
                    "value": {
                      "error": {
                        "code": "upgrade_required",
                        "message": "The key belongs to an account whose plan does not include API access."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "source_not_found: The id does not belong to a source on this account. not_found: The API is not enabled in this environment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "source_not_found": {
                    "value": {
                      "error": {
                        "code": "source_not_found",
                        "message": "The id does not belong to a source on this account."
                      }
                    }
                  },
                  "not_found": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "The API is not enabled in this environment."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: More than 120 requests in a minute on this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "More than 120 requests in a minute on this key."
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "server_error: Something went wrong on our side. Safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "server_error": {
                    "value": {
                      "error": {
                        "code": "server_error",
                        "message": "Something went wrong on our side. Safe to retry."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createTransactions",
        "summary": "Add transactions to a source",
        "description": "Creates ledger entries. The body is either a single transaction object or an array of up to 500.\n\nIdempotent on externalRef: re-posting a ref that already exists in the account’s books returns the existing entry instead of creating a duplicate, so a retried import cannot double-count income. Always set externalRef if your system has its own ids.\n\nIf category is set it must be a valid key for the source’s type (see GET /api/v1/categories) and the entry is recorded as confirmed. If it is omitted the entry goes to the app’s review queue for the account holder to categorise.\n\nStatus is 201 when at least one entry was created, 200 when everything was deduplicated. Entries matched by externalRef appear in existing with their stored ids, and are counted in deduplicated.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "A source id from GET /api/v1/sources.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every entry was matched by externalRef; nothing new was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateResult"
                }
              }
            }
          },
          "201": {
            "description": "Status is 201 when at least one entry was created, 200 when everything was deduplicated. Entries matched by externalRef appear in existing with their stored ids, and are counted in deduplicated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateResult"
                },
                "example": {
                  "created": 2,
                  "deduplicated": 0,
                  "transactions": [
                    {
                      "id": "e2b8d0f4-6a1c-4e5b-9d7f-8c0a2e4b6d8f",
                      "externalRef": "may-rent-2026"
                    },
                    {
                      "id": "f3a9c1e5-7b2d-4f6a-8c0e-9d1b3f5a7c9e",
                      "externalRef": "boiler-2026-05"
                    }
                  ],
                  "existing": []
                }
              }
            }
          },
          "400": {
            "description": "invalid_json: The body is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_json": {
                    "value": {
                      "error": {
                        "code": "invalid_json",
                        "message": "The body is not valid JSON."
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "missing_key: No Authorization: Bearer header was sent. invalid_key: The key is malformed, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_key": {
                    "value": {
                      "error": {
                        "code": "missing_key",
                        "message": "No Authorization: Bearer header was sent."
                      }
                    }
                  },
                  "invalid_key": {
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "The key is malformed, unknown or revoked."
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "upgrade_required: The key belongs to an account whose plan does not include API access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "upgrade_required": {
                    "value": {
                      "error": {
                        "code": "upgrade_required",
                        "message": "The key belongs to an account whose plan does not include API access."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "source_not_found: The id does not belong to a source on this account. not_found: The API is not enabled in this environment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "source_not_found": {
                    "value": {
                      "error": {
                        "code": "source_not_found",
                        "message": "The id does not belong to a source on this account."
                      }
                    }
                  },
                  "not_found": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "The API is not enabled in this environment."
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "empty_body: The array is empty. too_many: More than 500 transactions in one request. invalid_transaction: A field fails validation. The response includes an issues array listing the failing paths and messages. invalid_category: A category key is not valid for this source type. The response includes validCategories listing the accepted keys.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "empty_body": {
                    "value": {
                      "error": {
                        "code": "empty_body",
                        "message": "The array is empty."
                      }
                    }
                  },
                  "too_many": {
                    "value": {
                      "error": {
                        "code": "too_many",
                        "message": "More than 500 transactions in one request."
                      }
                    }
                  },
                  "invalid_transaction": {
                    "value": {
                      "error": {
                        "code": "invalid_transaction",
                        "message": "A field fails validation. The response includes an issues array listing the failing paths and messages."
                      }
                    }
                  },
                  "invalid_category": {
                    "value": {
                      "error": {
                        "code": "invalid_category",
                        "message": "A category key is not valid for this source type. The response includes validCategories listing the accepted keys."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "daily_cap: The request would exceed 5,000 transactions written in 24 hours. rate_limited: More than 120 requests in a minute on this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "daily_cap": {
                    "value": {
                      "error": {
                        "code": "daily_cap",
                        "message": "The request would exceed 5,000 transactions written in 24 hours."
                      }
                    }
                  },
                  "rate_limited": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "More than 120 requests in a minute on this key."
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "server_error: Something went wrong on our side. Safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "server_error": {
                    "value": {
                      "error": {
                        "code": "server_error",
                        "message": "Something went wrong on our side. Safe to retry."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/TransactionInput"
                  },
                  {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "$ref": "#/components/schemas/TransactionInput"
                    }
                  }
                ]
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A key from Account, then API access, in the app (Pro plan)."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code, e.g. invalid_key."
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              }
            }
          }
        },
        "example": {
          "error": {
            "code": "invalid_key",
            "message": "That API key is not valid, or it has been revoked."
          }
        }
      },
      "Me": {
        "type": "object",
        "required": [
          "accountId",
          "plan",
          "capabilities"
        ],
        "properties": {
          "accountId": {
            "type": "string",
            "format": "uuid"
          },
          "plan": {
            "type": "string",
            "enum": [
              "free",
              "pro",
              "expat",
              "firm"
            ]
          },
          "capabilities": {
            "type": "object",
            "properties": {
              "readBookkeeping": {
                "type": "boolean"
              },
              "writeBookkeeping": {
                "type": "boolean"
              },
              "hmrcSubmission": {
                "type": "boolean",
                "description": "Always false: filing is done in the app."
              }
            }
          },
          "note": {
            "type": "string"
          }
        }
      },
      "Source": {
        "type": "object",
        "required": [
          "id",
          "name",
          "type",
          "role",
          "sharePercent",
          "transactionCount"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "uk-property",
              "foreign-property",
              "self-employment"
            ]
          },
          "countryCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 3166-1 alpha-2 for overseas property; null otherwise."
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "sharer"
            ]
          },
          "sharePercent": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "transactionCount": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "Category": {
        "type": "object",
        "required": [
          "key",
          "label"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        }
      },
      "CategorySet": {
        "type": "object",
        "required": [
          "income",
          "expense"
        ],
        "properties": {
          "income": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Category"
            }
          },
          "expense": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Category"
            }
          }
        }
      },
      "Transaction": {
        "type": "object",
        "required": [
          "id",
          "date",
          "type",
          "category",
          "amountPence",
          "description",
          "externalRef"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "type": {
            "type": "string",
            "enum": [
              "income",
              "expense"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "amountPence": {
            "type": "integer",
            "minimum": 1
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "externalRef": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "TransactionInput": {
        "type": "object",
        "required": [
          "date",
          "type",
          "amountPence"
        ],
        "additionalProperties": false,
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD."
          },
          "type": {
            "type": "string",
            "enum": [
              "income",
              "expense"
            ]
          },
          "amountPence": {
            "type": "integer",
            "minimum": 1,
            "description": "Whole pence, positive. £450.00 is 45000."
          },
          "category": {
            "type": "string",
            "description": "A key valid for the source type (GET /api/v1/categories). Omit for the review queue."
          },
          "description": {
            "type": "string",
            "maxLength": 500
          },
          "externalRef": {
            "type": "string",
            "maxLength": 180,
            "description": "Your own id. Re-posting the same ref returns the existing entry."
          }
        }
      },
      "TransactionList": {
        "type": "object",
        "required": [
          "transactions",
          "nextCursor"
        ],
        "properties": {
          "transactions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transaction"
            }
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CreateResult": {
        "type": "object",
        "required": [
          "created",
          "deduplicated",
          "transactions",
          "existing"
        ],
        "properties": {
          "created": {
            "type": "integer"
          },
          "deduplicated": {
            "type": "integer"
          },
          "transactions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "externalRef": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "existing": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "externalRef": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}