# Quarterwise 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.

All endpoints live under https://quarterwise.co.uk/api/v1 and speak JSON over HTTPS.

Base URL: `https://quarterwise.co.uk`

## 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:

```json
{
  "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

```bash
curl https://quarterwise.co.uk/api/v1/me \
  -H "Authorization: Bearer qw_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
```

#### Example response (200)

```json
{
  "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

```bash
curl https://quarterwise.co.uk/api/v1/sources \
  -H "Authorization: Bearer qw_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
```

#### Example response (200)

```json
{
  "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

```bash
curl https://quarterwise.co.uk/api/v1/categories \
  -H "Authorization: Bearer qw_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
```

#### Example response (200)

```json
{
  "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

```bash
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)

```json
{
  "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

```bash
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)

```json
{
  "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. |

---

HTML version: https://quarterwise.co.uk/developers/docs · Overview: https://quarterwise.co.uk/developers · Keys: https://quarterwise.co.uk/app/account/api
