Transactions
Manage double-entry ledger transactions.
Transactions are the atomic units of accounting in SpeyBooks. Every financial event ultimately results in one or more transactions.
All endpoints require a valid API key. See Authentication.
Overview
Every transaction in SpeyBooks follows double-entry accounting:
- Each transaction contains two or more lines
- Debits must equal credits
- The sum of all line amounts must equal zero
Amount convention
- Positive amounts = debits
- Negative amounts = credits
This convention is enforced consistently across the API.
The transaction object
{
"id": "txn_52",
"date": "2026-01-31",
"description": "Sales invoice INV-2026-0001",
"reference": "INV-2026-0001",
"sourceType": "invoice",
"sourceId": "inv_42",
"status": "posted",
"isReconciled": false,
"reconciledAt": null,
"createdBy": "usr_1",
"createdByName": "William Murray",
"createdAt": "2026-01-31T10:00:00Z",
"updatedAt": "2026-01-31T10:00:00Z",
"totalDebit": 120000,
"totalCredit": 120000,
"accountCodes": "1100, 4000, 2200",
"lines": [
{
"id": "line_99",
"accountId": "acc_1100",
"accountCode": "1100",
"accountName": "Accounts Receivable",
"contactId": "cont_17",
"amount": 120000,
"vatAmount": null,
"description": "Sales invoice INV-2026-0001"
},
{
"id": "line_100",
"accountId": "acc_4000",
"accountCode": "4000",
"accountName": "Sales",
"contactId": null,
"amount": -100000,
"vatAmount": null,
"description": "Consulting services"
},
{
"id": "line_101",
"accountId": "acc_2200",
"accountCode": "2200",
"accountName": "VAT Output",
"contactId": null,
"amount": -20000,
"vatAmount": null,
"description": "VAT @ 20%"
}
]
}
Transaction attributes
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (txn_*) |
date | string | Transaction date (YYYY-MM-DD) |
description | string | Description (max 500 chars) |
reference | string | External reference |
sourceType | string | manual, invoice, payment, import |
sourceId | string | Source document ID (if applicable) |
status | string | draft or posted |
isReconciled | boolean | Whether reconciled to bank |
reconciledAt | string | Reconciliation timestamp |
createdBy | string | User ID |
createdByName | string | User name |
totalDebit | integer | Total debits (minor units) |
totalCredit | integer | Total credits (minor units) |
accountCodes | string | Comma-separated account codes |
lines | array | Transaction lines |
Transaction line attributes
| Field | Type | Description |
|---|---|---|
id | string | Line identifier (line_*) |
accountId | string | Account ID (acc_*) |
accountCode | string | Account code |
accountName | string | Account name |
contactId | string | Contact reference (optional) |
amount | integer | Minor units (positive = debit) |
vatAmount | integer | VAT portion (optional) |
description | string | Line description |
Create a transaction
Create a manual journal entry.
POST /v1/transactions
curl -X POST https://api.speybooks.com/v1/transactions \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"date": "2026-01-31",
"description": "Office supplies purchase",
"reference": "AMZN-12345",
"status": "posted",
"lines": [
{ "accountId": "acc_5100", "amount": 4167, "description": "Office supplies" },
{ "accountId": "acc_2200", "amount": 833, "description": "VAT @ 20%" },
{ "accountId": "acc_1200", "amount": -5000, "description": "Payment from bank" }
]
}'
Create parameters
| Field | Type | Required | Description |
|---|---|---|---|
date | string | Yes | Transaction date (YYYY-MM-DD) |
description | string | Yes | Description (max 500 chars) |
reference | string | No | External reference (max 100 chars) |
status | string | No | draft or posted (default: posted) |
lines | array | Yes | At least 2 lines required |
Line parameters
| Field | Type | Required | Description |
|---|---|---|---|
accountId | string | Yes | Account ID (acc_*) |
amount | integer | Yes | Minor units (positive = debit, negative = credit) |
description | string | No | Line description |
contactId | string | No | Contact reference |
vatRate | integer | No | VAT percentage (0-100) |
vatTreatment | string | No | none, inclusive, or exclusive |
Response
{
"success": true,
"data": {
"id": "txn_53",
"date": "2026-01-31",
"description": "Office supplies purchase",
"reference": "AMZN-12345",
"status": "posted",
"totalDebit": 5000,
"totalCredit": 5000,
"lines": [
{ "id": "line_102", "accountId": "acc_5100", "accountCode": "5100", "accountName": "Office Supplies", "amount": 4167 },
{ "id": "line_103", "accountId": "acc_2200", "accountCode": "2200", "accountName": "VAT Output", "amount": 833 },
{ "id": "line_104", "accountId": "acc_1200", "accountCode": "1200", "accountName": "Bank Account", "amount": -5000 }
]
}
}
Error: Unbalanced transaction
{
"success": false,
"error": {
"code": "validation_error",
"message": "Transaction must balance (debits must equal credits)",
"field": "lines"
}
}
Retrieve a transaction
GET /v1/transactions/:id
curl https://api.speybooks.com/v1/transactions/txn_52 \
-H "Authorization: Bearer sk_live_your_api_key"
Returns the full transaction object including all lines.
List transactions
GET /v1/transactions
curl "https://api.speybooks.com/v1/transactions?from=2026-01-01&to=2026-01-31&status=posted" \
-H "Authorization: Bearer sk_live_your_api_key"
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
from | string | — | Start date (YYYY-MM-DD) |
to | string | — | End date (YYYY-MM-DD) |
accountId | string | — | Filter by account (acc_*) |
contactId | string | — | Filter by contact (cont_*) |
status | string | — | draft or posted |
reconciled | boolean | — | Filter by reconciliation status |
search | string | — | Search description or reference |
page | integer | 1 | Page number |
limit | integer | 50 | Results per page (max 100) |
Response
{
"success": true,
"data": [
{
"id": "txn_52",
"date": "2026-01-31",
"description": "Sales invoice INV-2026-0001",
"reference": "INV-2026-0001",
"status": "posted",
"isReconciled": false,
"totalDebit": 120000,
"totalCredit": 120000,
"accountCodes": "1100, 4000, 2200"
}
],
"meta": {
"page": 1,
"limit": 50,
"total": 127,
"hasMore": true
}
}
Update a transaction
Only draft transactions can be updated. Updating replaces all lines.
PUT /v1/transactions/:id
curl -X PUT https://api.speybooks.com/v1/transactions/txn_53 \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated description",
"lines": [
{ "accountId": "acc_5100", "amount": 5000, "description": "Office equipment" },
{ "accountId": "acc_2200", "amount": 1000, "description": "VAT @ 20%" },
{ "accountId": "acc_1200", "amount": -6000, "description": "Payment from bank" }
]
}'
Response
{
"success": true,
"data": {
"id": "txn_53",
"updatedAt": "2026-01-31T15:00:00Z"
}
}
Error: Cannot update posted transaction
{
"success": false,
"error": {
"code": "unprocessable_entity",
"message": "Cannot update posted transaction"
}
}
Change transaction status
Post a draft transaction or revert to draft (if not reconciled).
PATCH /v1/transactions/:id/status
curl -X PATCH https://api.speybooks.com/v1/transactions/txn_53/status \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"status": "posted"
}'
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Yes | draft or posted |
Response
{
"success": true,
"data": {
"id": "txn_53",
"status": "posted"
}
}
Reverting is not allowed if the transaction has been reconciled.
Reconcile a transaction
Mark a posted transaction as reconciled to the bank statement.
PATCH /v1/transactions/:id/reconcile
curl -X PATCH https://api.speybooks.com/v1/transactions/txn_52/reconcile \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"reconciled": true
}'
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
reconciled | boolean | Yes | Reconciliation status |
Response
{
"success": true,
"data": {
"id": "txn_52",
"isReconciled": true,
"reconciledAt": "2026-02-01T10:00:00Z"
}
}
Only posted transactions may be reconciled.
Delete a transaction
DELETE /v1/transactions/:id
curl -X DELETE https://api.speybooks.com/v1/transactions/txn_53 \
-H "Authorization: Bearer sk_live_your_api_key"
Deletion rules
- Only draft manual transactions can be deleted
- Posted transactions cannot be deleted
- System-generated transactions cannot be deleted
To remove system transactions, delete the source document instead.
Response
{
"success": true,
"data": {
"deleted": true
}
}
Double-entry examples
Sale with VAT
{
"description": "Sales invoice to customer",
"lines": [
{ "accountId": "acc_1100", "amount": 1200, "description": "Accounts Receivable" },
{ "accountId": "acc_4000", "amount": -1000, "description": "Sales Revenue" },
{ "accountId": "acc_2200", "amount": -200, "description": "VAT Output @ 20%" }
]
}
Payment received
{
"description": "Payment from customer",
"lines": [
{ "accountId": "acc_1200", "amount": 1200, "description": "Bank" },
{ "accountId": "acc_1100", "amount": -1200, "description": "Accounts Receivable" }
]
}
Expense with VAT
{
"description": "Software subscription",
"lines": [
{ "accountId": "acc_5200", "amount": 5000, "description": "Software Costs" },
{ "accountId": "acc_2200", "amount": 1000, "description": "VAT Input @ 20%" },
{ "accountId": "acc_1200", "amount": -6000, "description": "Bank" }
]
}
Common errors
| Code | HTTP | Description |
|---|---|---|
validation_error | 400 | Transaction does not balance (debits ≠ credits) |
validation_error | 400 | Fewer than 2 lines provided |
invalid_id | 400 | Malformed ID (expected txn_*, acc_*, or cont_*) |
not_found | 404 | Transaction does not exist |
unprocessable_entity | 422 | Cannot update/delete posted transaction |
unprocessable_entity | 422 | Cannot delete system-generated transaction |
unprocessable_entity | 422 | Cannot revert reconciled transaction to draft |
Key invariants
- Debits always equal credits
- Transactions are immutable once posted
- Reconciled transactions cannot be reverted
- System transactions derive from source documents
- Ledger history is append-only
All amounts are in minor units. Positive = debit, negative = credit. Sum of all line amounts must equal zero.