Developers
API reference
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.
Base URL https://quarterwise.co.uk. All endpoints speak JSON over HTTPS. This page is also served as plain markdown at /developers/docs.md and as an OpenAPI 3.1 document at /api/v1/openapi.json, both generated from the same source, for AI agents, code generators and anyone who prefers text. New to the API? Start with the overview.
Authentication
- Create a key in the app under Account, then API access (/app/account/api). API access is part of the Pro plan.
- Keys start with qw_live_ and are shown once, at creation. Only a SHA-256 hash is stored, so a lost key cannot be recovered; create a new one instead.
- Send the key on every request as a bearer token: Authorization: Bearer qw_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
- Revoking a key in the app stops it working immediately.
Conventions
- Amounts are whole pence as integers. £450.00 is 45000. Floats are rejected.
- Dates are YYYY-MM-DD.
- Request bodies are JSON, UTF-8, with a Content-Type: application/json header.
- Successful responses include an X-RateLimit-Remaining header with the requests left in the current minute.
Rate limits
- 120 requests per minute per key. Over that, requests return 429 rate_limited until the minute rolls over.
- Writes are capped at 5,000 transactions per rolling 24 hours across the account's books. The cap exists to stop a runaway script filling the ledger; a request that would cross it returns 429 daily_cap.
What the API does not do
- No 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.
- Sources are read-only. Properties and self-employment businesses are set up in the app; the API cannot create, rename or delete them.
- No receipt upload, no tax calculations and no HMRC data. The API covers the transaction ledger and the reference data needed to write to it.
- Transactions posted without a category are not categorised automatically. They land in the app's review queue for the account holder to sort.
Errors
Every failure, on every endpoint, returns one JSON shape:
{
"error": {
"code": "invalid_key",
"message": "That API key is not valid, or it has been revoked."
}
}These errors can occur on any endpoint:
| Status | Code | When |
|---|---|---|
| 401 | missing_key | No Authorization: Bearer header was sent. |
| 401 | invalid_key | The key is malformed, unknown or revoked. |
| 403 | upgrade_required | The key belongs to an account whose plan does not include API access. |
| 404 | not_found | The API is not enabled in this environment. |
| 429 | rate_limited | More than 120 requests in a minute on this key. |
| 500 | server_error | Something went wrong on our side. Safe to retry. |
Endpoints
GET /api/v1/me
Who this key belongs to, and what it can do
Returns the account the key is attached to, its plan, and the capabilities of the API. Useful as a connection test.
Example request
curl https://quarterwise.co.uk/api/v1/me \ -H "Authorization: Bearer qw_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
Example response (200)
{
"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."
}GET /api/v1/sources
List the account’s properties and businesses
Every income source on the account: UK properties, overseas properties and self-employment businesses. The id is what you pass to the transactions endpoints.
sharePercent 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.
Example request
curl https://quarterwise.co.uk/api/v1/sources \ -H "Authorization: Bearer qw_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
Example response (200)
{
"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
}
]
}GET /api/v1/categories
Valid category keys, per source type
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.
With 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 | In | Type | Required | Description |
|---|---|---|---|---|
sourceType | query | string | no | One of uk-property, foreign-property, self-employment. Omit for all three. |
Example request
curl https://quarterwise.co.uk/api/v1/categories \ -H "Authorization: Bearer qw_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
Example response (200)
{
"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"
}
]
}
}Endpoint errors
| Status | Code | When |
|---|---|---|
| 422 | invalid_source_type | sourceType is present but not one of the three valid values. |
GET /api/v1/sources/{id}/transactions
List transactions for a source
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.
category is null for entries that have not been categorised yet, and description and externalRef are null when they were not provided.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | A source id from GET /api/v1/sources. |
from | query | string | no | Earliest date to include, YYYY-MM-DD, inclusive. |
to | query | string | no | Latest date to include, YYYY-MM-DD, inclusive. |
cursor | query | string | no | The nextCursor value from the previous page. |
limit | query | integer | no | Page size, default 50, maximum 200. |
Example request
curl "https://quarterwise.co.uk/api/v1/sources/6a1f9b3e-4c2d-4e8f-b1a7-5d9c0e2f4a6b/transactions?from=2026-04-06&limit=2" \ -H "Authorization: Bearer qw_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
Example response (200)
{
"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"
}nextCursor is null when there are no further pages.
Endpoint errors
| Status | Code | When |
|---|---|---|
| 404 | source_not_found | The id does not belong to a source on this account. |
POST /api/v1/sources/{id}/transactions
Add transactions to a source
Creates ledger entries. The body is either a single transaction object or an array of up to 500.
Idempotent 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.
If 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.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | A source id from GET /api/v1/sources. |
date | body | string | yes | Transaction date, YYYY-MM-DD. |
type | body | string | yes | income or expense. |
amountPence | body | integer | yes | Whole pence, positive. £450.00 is 45000. |
category | body | string | no | A category key valid for the source type. Omit to leave the entry for the review queue. |
description | body | string | no | Free text, up to 500 characters. |
externalRef | body | string | no | Your own id for the entry, up to 180 characters. Re-posting the same ref returns the existing entry. |
Example request
curl -X POST https://quarterwise.co.uk/api/v1/sources/6a1f9b3e-4c2d-4e8f-b1a7-5d9c0e2f4a6b/transactions \
-H "Authorization: Bearer qw_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d" \
-H "Content-Type: application/json" \
-d '[
{"date":"2026-05-01","type":"income","amountPence":95000,
"category":"periodAmount","description":"May rent","externalRef":"may-rent-2026"},
{"date":"2026-05-14","type":"expense","amountPence":8450,
"category":"repairsAndMaintenance","description":"Boiler service","externalRef":"boiler-2026-05"}
]'Example response (201)
{
"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": []
}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.
Endpoint errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. |
| 404 | source_not_found | The id does not belong to a source on this account. |
| 422 | empty_body | The array is empty. |
| 422 | too_many | More than 500 transactions in one request. |
| 422 | invalid_transaction | A field fails validation. The response includes an issues array listing the failing paths and messages. |
| 422 | invalid_category | A category key is not valid for this source type. The response includes validCategories listing the accepted keys. |
| 429 | daily_cap | The request would exceed 5,000 transactions written in 24 hours. |
Keys are managed in the app under Account, then API access. Questions or a missing endpoint you need? Get in touch.