Lekker Accounting API
A REST API for everything you can do in Lekker Accounting: clients, products, invoices,
quotes, purchase orders, payments and expenses. All requests and responses are JSON. Amount
fields are returned as decimal strings (e.g. "1500.00")
to avoid floating point surprises; dates are YYYY-MM-DD
and timestamps are ISO-8601 UTC.
Base URL
https://lekkeraccounting.com/api/v1
Prefer machine-readable docs? Import the OpenAPI 3.1 document into Postman, Insomnia or your code generator.
Authentication
The API uses personal access tokens. Create one in the app under Settings → Claude — choose a name, whether the token can write, and an optional expiry. The token is shown once; store it securely. Send it on every request as a bearer token:
curl 'https://lekkeraccounting.com/api/v1/me' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Abilities. Tokens carry abilities:
read allows all GET endpoints;
write is additionally required for
POST, PUT and DELETE. A read-only token calling a write endpoint gets a 403.
Revocation. Revoking a token in Settings → Claude immediately ends its access. Expired tokens are rejected with 401.
Workspaces & permissions
Every token is pinned to the workspace it was created in. All data you read or write through that token belongs to that workspace — switching your active workspace in the app does not affect existing tokens. If you belong to several businesses, create one token per workspace.
Tokens act with your role's permissions
(admin, accountant or viewer). For example, listing clients needs manage_clients;
a viewer-role user gets 403 regardless of token abilities. Each resource below lists the permission it requires.
Use GET /me to see the workspace,
abilities and permissions a token has.
Pagination, sorting & filtering
List endpoints are paginated: ?page=2&per_page=50
(max 100 per page, default 25). Responses carry Laravel's standard
data / links / meta envelope.
Sort with ?sort=<key>&direction=asc|desc
— each resource documents its sort keys. Filters (search, status, date ranges) are plain query
parameters listed per endpoint.
Soft deletes & restore
DELETE never destroys data — records move to the trash (also visible in the app under Trash)
and get a deleted_at timestamp.
Restore one with POST /<resource>/{id}/restore.
Deleted records are hidden from lists by default. Add
?trashed=with to include them or
?trashed=only to list the trash itself.
Errors
Errors are JSON with a human-readable message; validation errors add per-field detail:
{
"message": "The client id field is required. (and 1 more error)",
"errors": {
"client_id": ["The client id field is required."],
"items": ["The items field is required."]
}
}
| Status | Meaning |
|---|---|
| 401 | Missing, invalid, expired or revoked token. |
| 402 | The workspace's subscription is unpaid — API access is paused until it's settled. |
| 403 | The token lacks the required ability, your role lacks the permission, or a plan limit blocks the action. |
| 404 | Not found — including records that belong to another workspace. |
| 422 | Validation failed; see errors. |
| 429 | Rate limit exceeded — retry after the Retry-After seconds. |
Rate limits
Requests are limited to 60 per minute per user.
The X-RateLimit-Limit and
X-RateLimit-Remaining headers show
where you stand; exceeding the limit returns 429.
Me
Token introspection. Confirms the token works and shows who it acts as, which workspace it is pinned to, its abilities and the user's permissions.
/v1/me
Inspect the current token
Returns the authenticated user, the workspace the token operates on, token metadata and the user's permission names. Workspace includes invoice_allowance: monthly slots remaining, extra invoices granted by an admin (never expire, used after the monthly allowance), and when the monthly count resets.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/me' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"user": {
"id": 12,
"name": "Thandi Mokoena",
"email": "thandi@example.com"
},
"workspace": {
"id": "4d81…",
"name": "Mokoena Consulting",
"invoice_allowance": {
"unlimited": false,
"monthly_allowance": 50,
"used_this_month": 12,
"monthly_remaining": 38,
"bonus_remaining": 500,
"total_remaining": 538,
"resets_on": "2026-09-01"
}
},
"token": {
"name": "Zapier integration",
"abilities": [
"read",
"write"
],
"last_used_at": "2026-07-16T08:12:00Z",
"expires_at": null
},
"permissions": [
"manage_clients",
"manage_invoices",
"manage_quotes"
]
}
}
Clients
The people and businesses you invoice. Requires the `manage_clients` permission.
/v1/clients
requires: read
List clients
Paginated list, newest first by default.
Query parameters
page
integer |
Page number (default 1). |
per_page
integer |
Results per page, 1–100 (default 25). |
sort
string |
Sort key. One of: `name`, `company`, `email`, `created_at`. |
direction
string |
`asc` or `desc` (default `desc`). |
trashed
string |
Soft-delete visibility: `with` includes deleted records, `only` returns deleted records exclusively. |
search
string |
Matches first name, last name, company name or email. |
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/clients' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 7,
"first_name": "Thandi",
"last_name": "Mokoena",
"company_name": "Mokoena Consulting",
"display_name": "Mokoena Consulting",
"email": "accounts@mokoena.example",
"contact": "0821234567",
"region_code": "27",
"vat_no": "4123456789",
"website": null,
"address": "12 Long Street",
"city": "Cape Town",
"state": "Western Cape",
"postal_code": "8001",
"country_code": "ZA",
"note": null,
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
],
"links": {
"first": "…",
"last": "…",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"per_page": 25,
"total": 1,
"…": "…"
}
}
/v1/clients/{client}
requires: read
Get a client
Returns a single client by id.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/clients/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 7,
"first_name": "Thandi",
"last_name": "Mokoena",
"company_name": "Mokoena Consulting",
"display_name": "Mokoena Consulting",
"email": "accounts@mokoena.example",
"contact": "0821234567",
"region_code": "27",
"vat_no": "4123456789",
"website": null,
"address": "12 Long Street",
"city": "Cape Town",
"state": "Western Cape",
"postal_code": "8001",
"country_code": "ZA",
"note": null,
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/clients
requires: write
Create a client
Creates a client. Returns 201 with the created record; validation failures return 422 with per-field errors.
Body parameters
first_name
string
required
|
Contact first name. |
last_name
string
required
|
Contact last name. |
company_name
string
|
Company name — used as the display name when present. |
email
string
|
Billing email address. |
contact
string
|
Local phone number, without the country prefix. |
region_code
string
|
Country dialling code (international calling prefix), digits only, e.g. `27` for South Africa. |
vat_no
string
|
VAT registration number. |
website
string
|
Website URL. |
address
string
|
Street address. |
city
string
|
City. |
state
string
|
Province / region. |
postal_code
string
|
Postal code. |
country_code
string
|
ISO 3166-1 alpha-2 country code, e.g. `ZA` or `NA`. |
note
string
|
Internal note (max 1000 characters). |
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/clients' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"first_name":"Example","last_name":"Example"}'
Example response 201
{
"data": {
"id": 7,
"first_name": "Thandi",
"last_name": "Mokoena",
"company_name": "Mokoena Consulting",
"display_name": "Mokoena Consulting",
"email": "accounts@mokoena.example",
"contact": "0821234567",
"region_code": "27",
"vat_no": "4123456789",
"website": null,
"address": "12 Long Street",
"city": "Cape Town",
"state": "Western Cape",
"postal_code": "8001",
"country_code": "ZA",
"note": null,
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/clients/{client}
requires: write
Update a client
Full update — send every field you want kept, exactly like create.
Body parameters
first_name
string
required
|
Contact first name. |
last_name
string
required
|
Contact last name. |
company_name
string
|
Company name — used as the display name when present. |
email
string
|
Billing email address. |
contact
string
|
Local phone number, without the country prefix. |
region_code
string
|
Country dialling code (international calling prefix), digits only, e.g. `27` for South Africa. |
vat_no
string
|
VAT registration number. |
website
string
|
Website URL. |
address
string
|
Street address. |
city
string
|
City. |
state
string
|
Province / region. |
postal_code
string
|
Postal code. |
country_code
string
|
ISO 3166-1 alpha-2 country code, e.g. `ZA` or `NA`. |
note
string
|
Internal note (max 1000 characters). |
Example request
curl -X PUT 'https://lekkeraccounting.com/api/v1/clients/42' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"first_name":"Example","last_name":"Example"}'
Example response 200
{
"data": {
"id": 7,
"first_name": "Thandi",
"last_name": "Mokoena",
"company_name": "Mokoena Consulting",
"display_name": "Mokoena Consulting",
"email": "accounts@mokoena.example",
"contact": "0821234567",
"region_code": "27",
"vat_no": "4123456789",
"website": null,
"address": "12 Long Street",
"city": "Cape Town",
"state": "Western Cape",
"postal_code": "8001",
"country_code": "ZA",
"note": null,
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/clients/{client}
requires: write
Delete a client
Soft delete — the record moves to the trash and can be restored. Returns 204 with no body.
Example request
curl -X DELETE 'https://lekkeraccounting.com/api/v1/clients/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 204
No body.
/v1/clients/{id}/restore
requires: write
Restore a client
Restores a soft-deleted record from the trash. 404 if the id is not currently deleted.
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/clients/42/restore' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 7,
"first_name": "Thandi",
"last_name": "Mokoena",
"company_name": "Mokoena Consulting",
"display_name": "Mokoena Consulting",
"email": "accounts@mokoena.example",
"contact": "0821234567",
"region_code": "27",
"vat_no": "4123456789",
"website": null,
"address": "12 Long Street",
"city": "Cape Town",
"state": "Western Cape",
"postal_code": "8001",
"country_code": "ZA",
"note": null,
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
Products
Products and services used as invoice and quote line items. Requires the `manage_products` permission.
/v1/products
requires: read
List products
Paginated list, newest first by default.
Query parameters
page
integer |
Page number (default 1). |
per_page
integer |
Results per page, 1–100 (default 25). |
sort
string |
Sort key. One of: `name`, `code`, `unit_price`, `created_at`. |
direction
string |
`asc` or `desc` (default `desc`). |
trashed
string |
Soft-delete visibility: `with` includes deleted records, `only` returns deleted records exclusively. |
search
string |
Matches name or code. |
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/products' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 3,
"name": "Monthly bookkeeping",
"code": "BOOK-M",
"unit_price": "1500.00",
"tax_rate": "15.00",
"description": "Bookkeeping retainer, billed monthly.",
"category": {
"id": 1,
"name": "Services"
},
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
],
"links": {
"first": "…",
"last": "…",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"per_page": 25,
"total": 1,
"…": "…"
}
}
/v1/products/{product}
requires: read
Get a product
Returns a single product by id.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/products/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 3,
"name": "Monthly bookkeeping",
"code": "BOOK-M",
"unit_price": "1500.00",
"tax_rate": "15.00",
"description": "Bookkeeping retainer, billed monthly.",
"category": {
"id": 1,
"name": "Services"
},
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/products
requires: write
Create a product
Creates a product. Returns 201 with the created record; validation failures return 422 with per-field errors.
Body parameters
name
string
required
|
Product or service name. |
code
string
|
Your internal SKU / code. |
category
string
|
Category name or id. Unknown names create the category on the fly. |
unit_price
number
required
|
Unit price (≥ 0). |
tax_rate
number
|
Default tax rate percentage, 0–100. |
description
string
|
Description (max 1000 characters). |
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/products' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"name":"Example","unit_price":100}'
Example response 201
{
"data": {
"id": 3,
"name": "Monthly bookkeeping",
"code": "BOOK-M",
"unit_price": "1500.00",
"tax_rate": "15.00",
"description": "Bookkeeping retainer, billed monthly.",
"category": {
"id": 1,
"name": "Services"
},
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/products/{product}
requires: write
Update a product
Full update — send every field you want kept, exactly like create.
Body parameters
name
string
required
|
Product or service name. |
code
string
|
Your internal SKU / code. |
category
string
|
Category name or id. Unknown names create the category on the fly. |
unit_price
number
required
|
Unit price (≥ 0). |
tax_rate
number
|
Default tax rate percentage, 0–100. |
description
string
|
Description (max 1000 characters). |
Example request
curl -X PUT 'https://lekkeraccounting.com/api/v1/products/42' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"name":"Example","unit_price":100}'
Example response 200
{
"data": {
"id": 3,
"name": "Monthly bookkeeping",
"code": "BOOK-M",
"unit_price": "1500.00",
"tax_rate": "15.00",
"description": "Bookkeeping retainer, billed monthly.",
"category": {
"id": 1,
"name": "Services"
},
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/products/{product}
requires: write
Delete a product
Soft delete — the record moves to the trash and can be restored. Returns 204 with no body.
Example request
curl -X DELETE 'https://lekkeraccounting.com/api/v1/products/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 204
No body.
/v1/products/{id}/restore
requires: write
Restore a product
Restores a soft-deleted record from the trash. 404 if the id is not currently deleted.
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/products/42/restore' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 3,
"name": "Monthly bookkeeping",
"code": "BOOK-M",
"unit_price": "1500.00",
"tax_rate": "15.00",
"description": "Bookkeeping retainer, billed monthly.",
"category": {
"id": 1,
"name": "Services"
},
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
Invoices
Invoices with server-computed totals. Line totals, per-line tax and the discount are always recalculated from the submitted items — you never send amounts. Requires the `manage_invoices` permission. Creating an invoice counts toward your plan's monthly allowance first, then any extra invoices granted by an admin; hitting the limit returns 403. The list response includes invoice_allowance beside data (remaining monthly slots, extra invoices, reset date).
/v1/invoices
requires: read
List invoices
Paginated list, newest first by default.
Query parameters
page
integer |
Page number (default 1). |
per_page
integer |
Results per page, 1–100 (default 25). |
sort
string |
Sort key. One of: `invoice_date`, `due_date`, `final_amount`, `status`, `created_at`. |
direction
string |
`asc` or `desc` (default `desc`). |
trashed
string |
Soft-delete visibility: `with` includes deleted records, `only` returns deleted records exclusively. |
search
string |
Matches the invoice number or the client's name. |
status
integer |
Filter by status.
0 Draft · 1 Unpaid · 2 Paid · 3 Partially Paid · 4 Overdue
|
client_id
integer |
Filter by client. |
date_from
date |
Invoice date on or after (YYYY-MM-DD). |
date_to
date |
Invoice date on or before (YYYY-MM-DD). |
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/invoices' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 42,
"invoice_number": "108",
"status": 1,
"status_label": "Unpaid",
"invoice_date": "2026-07-01",
"due_date": "2026-07-31",
"amount": "3000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "3450.00",
"note": null,
"term": "Payment due within 30 days.",
"needs_review": false,
"recurring_frequency": "none",
"recurring_day": null,
"recurring_month": null,
"recurring_status": false,
"local_currency_amount": null,
"exchange_rate": null,
"custom_fields": [
{
"key": "reg_no",
"label": "Reg No",
"type": "text",
"value": "CA 123-456"
}
],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"name": "South African Rand",
"code": "ZAR",
"symbol": "R",
"is_custom": false
},
"items": [
{
"id": 90,
"product_id": 3,
"name": "Monthly bookkeeping",
"description": null,
"quantity": "2.00",
"price": "1500.00",
"tax_rate": "15.00",
"total": "3000.00"
}
],
"payments": [],
"paid_amount": "0.00",
"remaining_amount": "3450.00",
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
],
"links": {
"first": "…",
"last": "…",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"per_page": 25,
"total": 1,
"…": "…"
}
}
/v1/invoices/{invoice}
requires: read
Get a invoice
Returns a single invoice by id.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/invoices/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 42,
"invoice_number": "108",
"status": 1,
"status_label": "Unpaid",
"invoice_date": "2026-07-01",
"due_date": "2026-07-31",
"amount": "3000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "3450.00",
"note": null,
"term": "Payment due within 30 days.",
"needs_review": false,
"recurring_frequency": "none",
"recurring_day": null,
"recurring_month": null,
"recurring_status": false,
"local_currency_amount": null,
"exchange_rate": null,
"custom_fields": [
{
"key": "reg_no",
"label": "Reg No",
"type": "text",
"value": "CA 123-456"
}
],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"name": "South African Rand",
"code": "ZAR",
"symbol": "R",
"is_custom": false
},
"items": [
{
"id": 90,
"product_id": 3,
"name": "Monthly bookkeeping",
"description": null,
"quantity": "2.00",
"price": "1500.00",
"tax_rate": "15.00",
"total": "3000.00"
}
],
"payments": [],
"paid_amount": "0.00",
"remaining_amount": "3450.00",
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/invoices
requires: write
Create a invoice
Creates a invoice. Returns 201 with the created record; validation failures return 422 with per-field errors.
Body parameters
invoice_number
string
|
Custom invoice number (create only). Omitted or already taken → the next number in your sequence is assigned. |
client_id
integer
required
|
Client id. |
currency_id
integer
required
|
Currency id — must be enabled for your workspace (see GET /currencies). |
banking_detail_id
integer
|
Banking detail shown on the PDF. Defaults to your workspace default. |
invoice_date
date
required
|
Invoice date (YYYY-MM-DD). |
due_date
date
required
|
Due date — on or after the invoice date. |
discount_type
integer
required
|
Discount type.
0 None · 1 Fixed amount · 2 Percentage
|
discount
number
|
Discount value — an amount when fixed, a percentage when percentage. |
status
integer
required
|
Invoice status.
0 Draft · 1 Unpaid · 2 Paid · 3 Partially Paid · 4 Overdue
|
note
string
|
Note shown on the invoice. |
term
string
|
Payment terms shown on the invoice. |
recurring_frequency
string
|
Recurring schedule. On create, a monthly or yearly value spawns a standalone recurring template from this invoice; the template (managed under Recurring Invoices) drives future drafts, and the invoice itself stays a plain document. Ignored on update.
none None · monthly Monthly · yearly Yearly
|
recurring_day
integer
|
Day of month (1-31) the spawned template generates on. Only used on create together with recurring_frequency. |
recurring_month
integer
|
Month (1-12) for yearly recurrence. Only used on create together with recurring_frequency. |
recurring_status
boolean
|
Legacy flag, ignored: recurring schedules now live on standalone templates. Always false on invoices returned by the API. |
local_currency_amount
number
|
Amount received in your default currency. Required when marking a foreign-currency invoice as paid; the exchange rate is derived from it. |
custom_fields
object
|
Values for the workspace's invoice custom fields (Settings → Custom Fields), keyed by field key — e.g. `{"reg_no": "CA 123-456"}`. Each value is validated against the field's type and options; required custom fields must be supplied. Responses return the stored entries as `[{key, label, type, value}]`. |
items
array
required
|
Line items. Totals, tax and the final amount are computed server-side from these rows. Each item: `name` (string, required), `quantity` (number ≥ 0.01, required), `price` (number ≥ 0, required), `tax_rate` (0–100, optional), `description` (optional), `product_id` (optional). |
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/invoices' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"client_id":1,"currency_id":1,"invoice_date":"2026-07-01","due_date":"2026-07-01","discount_type":0,"status":0,"items":[{"name":"Line item","quantity":1,"price":100,"tax_rate":15}]}'
Example response 201
{
"data": {
"id": 42,
"invoice_number": "108",
"status": 1,
"status_label": "Unpaid",
"invoice_date": "2026-07-01",
"due_date": "2026-07-31",
"amount": "3000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "3450.00",
"note": null,
"term": "Payment due within 30 days.",
"needs_review": false,
"recurring_frequency": "none",
"recurring_day": null,
"recurring_month": null,
"recurring_status": false,
"local_currency_amount": null,
"exchange_rate": null,
"custom_fields": [
{
"key": "reg_no",
"label": "Reg No",
"type": "text",
"value": "CA 123-456"
}
],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"name": "South African Rand",
"code": "ZAR",
"symbol": "R",
"is_custom": false
},
"items": [
{
"id": 90,
"product_id": 3,
"name": "Monthly bookkeeping",
"description": null,
"quantity": "2.00",
"price": "1500.00",
"tax_rate": "15.00",
"total": "3000.00"
}
],
"payments": [],
"paid_amount": "0.00",
"remaining_amount": "3450.00",
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/invoices/{invoice}
requires: write
Update a invoice
Full update — send every field you want kept, exactly like create.
Body parameters
client_id
integer
required
|
Client id. |
currency_id
integer
required
|
Currency id — must be enabled for your workspace (see GET /currencies). |
banking_detail_id
integer
|
Banking detail shown on the PDF. Defaults to your workspace default. |
invoice_date
date
required
|
Invoice date (YYYY-MM-DD). |
due_date
date
required
|
Due date — on or after the invoice date. |
discount_type
integer
required
|
Discount type.
0 None · 1 Fixed amount · 2 Percentage
|
discount
number
|
Discount value — an amount when fixed, a percentage when percentage. |
status
integer
required
|
Invoice status.
0 Draft · 1 Unpaid · 2 Paid · 3 Partially Paid · 4 Overdue
|
note
string
|
Note shown on the invoice. |
term
string
|
Payment terms shown on the invoice. |
recurring_frequency
string
|
Recurring schedule. On create, a monthly or yearly value spawns a standalone recurring template from this invoice; the template (managed under Recurring Invoices) drives future drafts, and the invoice itself stays a plain document. Ignored on update.
none None · monthly Monthly · yearly Yearly
|
recurring_day
integer
|
Day of month (1-31) the spawned template generates on. Only used on create together with recurring_frequency. |
recurring_month
integer
|
Month (1-12) for yearly recurrence. Only used on create together with recurring_frequency. |
recurring_status
boolean
|
Legacy flag, ignored: recurring schedules now live on standalone templates. Always false on invoices returned by the API. |
local_currency_amount
number
|
Amount received in your default currency. Required when marking a foreign-currency invoice as paid; the exchange rate is derived from it. |
custom_fields
object
|
Values for the workspace's invoice custom fields (Settings → Custom Fields), keyed by field key — e.g. `{"reg_no": "CA 123-456"}`. Each value is validated against the field's type and options; required custom fields must be supplied. Responses return the stored entries as `[{key, label, type, value}]`. |
items
array
required
|
Line items. Totals, tax and the final amount are computed server-side from these rows. Each item: `name` (string, required), `quantity` (number ≥ 0.01, required), `price` (number ≥ 0, required), `tax_rate` (0–100, optional), `description` (optional), `product_id` (optional). |
Example request
curl -X PUT 'https://lekkeraccounting.com/api/v1/invoices/42' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"client_id":1,"currency_id":1,"invoice_date":"2026-07-01","due_date":"2026-07-01","discount_type":0,"status":0,"items":[{"name":"Line item","quantity":1,"price":100,"tax_rate":15}]}'
Example response 200
{
"data": {
"id": 42,
"invoice_number": "108",
"status": 1,
"status_label": "Unpaid",
"invoice_date": "2026-07-01",
"due_date": "2026-07-31",
"amount": "3000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "3450.00",
"note": null,
"term": "Payment due within 30 days.",
"needs_review": false,
"recurring_frequency": "none",
"recurring_day": null,
"recurring_month": null,
"recurring_status": false,
"local_currency_amount": null,
"exchange_rate": null,
"custom_fields": [
{
"key": "reg_no",
"label": "Reg No",
"type": "text",
"value": "CA 123-456"
}
],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"name": "South African Rand",
"code": "ZAR",
"symbol": "R",
"is_custom": false
},
"items": [
{
"id": 90,
"product_id": 3,
"name": "Monthly bookkeeping",
"description": null,
"quantity": "2.00",
"price": "1500.00",
"tax_rate": "15.00",
"total": "3000.00"
}
],
"payments": [],
"paid_amount": "0.00",
"remaining_amount": "3450.00",
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/invoices/{invoice}
requires: write
Delete a invoice
Soft delete — the record moves to the trash and can be restored. Returns 204 with no body.
Example request
curl -X DELETE 'https://lekkeraccounting.com/api/v1/invoices/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 204
No body.
/v1/invoices/{id}/restore
requires: write
Restore a invoice
Restores a soft-deleted record from the trash. 404 if the id is not currently deleted.
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/invoices/42/restore' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 42,
"invoice_number": "108",
"status": 1,
"status_label": "Unpaid",
"invoice_date": "2026-07-01",
"due_date": "2026-07-31",
"amount": "3000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "3450.00",
"note": null,
"term": "Payment due within 30 days.",
"needs_review": false,
"recurring_frequency": "none",
"recurring_day": null,
"recurring_month": null,
"recurring_status": false,
"local_currency_amount": null,
"exchange_rate": null,
"custom_fields": [
{
"key": "reg_no",
"label": "Reg No",
"type": "text",
"value": "CA 123-456"
}
],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"name": "South African Rand",
"code": "ZAR",
"symbol": "R",
"is_custom": false
},
"items": [
{
"id": 90,
"product_id": 3,
"name": "Monthly bookkeeping",
"description": null,
"quantity": "2.00",
"price": "1500.00",
"tax_rate": "15.00",
"total": "3000.00"
}
],
"payments": [],
"paid_amount": "0.00",
"remaining_amount": "3450.00",
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
Quotes
Quotes / estimates with the same server-computed totals as invoices. Requires the `manage_quotes` permission.
/v1/quotes
requires: read
List quotes
Paginated list, newest first by default.
Query parameters
page
integer |
Page number (default 1). |
per_page
integer |
Results per page, 1–100 (default 25). |
sort
string |
Sort key. One of: `quote_date`, `due_date`, `final_amount`, `status`, `created_at`. |
direction
string |
`asc` or `desc` (default `desc`). |
trashed
string |
Soft-delete visibility: `with` includes deleted records, `only` returns deleted records exclusively. |
search
string |
Matches the quote number or the client's name. |
status
integer |
Filter by status.
0 Draft · 1 Converted · 2 Pending
|
client_id
integer |
Filter by client. |
date_from
date |
Quote date on or after. |
date_to
date |
Quote date on or before. |
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/quotes' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 17,
"quote_number": "23",
"status": 0,
"status_label": "Draft",
"quote_date": "2026-07-01",
"due_date": "2026-07-15",
"amount": "5000.00",
"discount_type": 2,
"discount": "10.00",
"final_amount": "5250.00",
"note": null,
"term": null,
"custom_fields": [],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"items": [
{
"id": 31,
"product_id": null,
"name": "Website redesign",
"description": null,
"quantity": "1.00",
"price": "5000.00",
"tax_rate": "15.00",
"total": "5000.00"
}
],
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
],
"links": {
"first": "…",
"last": "…",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"per_page": 25,
"total": 1,
"…": "…"
}
}
/v1/quotes/{quote}
requires: read
Get a quote
Returns a single quote by id.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/quotes/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 17,
"quote_number": "23",
"status": 0,
"status_label": "Draft",
"quote_date": "2026-07-01",
"due_date": "2026-07-15",
"amount": "5000.00",
"discount_type": 2,
"discount": "10.00",
"final_amount": "5250.00",
"note": null,
"term": null,
"custom_fields": [],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"items": [
{
"id": 31,
"product_id": null,
"name": "Website redesign",
"description": null,
"quantity": "1.00",
"price": "5000.00",
"tax_rate": "15.00",
"total": "5000.00"
}
],
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/quotes
requires: write
Create a quote
Creates a quote. Returns 201 with the created record; validation failures return 422 with per-field errors.
Body parameters
quote_number
string
|
Custom quote number. Omitted on create (or already taken) → the next number in your sequence is assigned. On update, omitted keeps the current number. |
client_id
integer
required
|
Client id. |
currency_id
integer
required
|
Currency id — must be enabled for your workspace. |
banking_detail_id
integer
|
Banking detail shown on the PDF. Defaults to your workspace default. |
quote_date
date
required
|
Quote date (YYYY-MM-DD). |
due_date
date
required
|
Valid-until date — on or after the quote date. |
discount_type
integer
required
|
Discount type.
0 None · 1 Fixed amount · 2 Percentage
|
discount
number
|
Discount value. |
status
integer
required
|
Quote status.
0 Draft · 1 Converted · 2 Pending
|
note
string
|
Note shown on the quote. |
term
string
|
Terms shown on the quote. |
custom_fields
object
|
Values for the workspace's quote custom fields (Settings → Custom Fields), keyed by field key — e.g. `{"reg_no": "CA 123-456"}`. Quote fields are managed separately from invoice fields. Each value is validated against the field's type and options; required custom fields must be supplied. Responses return the stored entries as `[{key, label, type, value}]`. |
items
array
required
|
Line items. Totals, tax and the final amount are computed server-side from these rows. Each item: `name` (string, required), `quantity` (number ≥ 0.01, required), `price` (number ≥ 0, required), `tax_rate` (0–100, optional), `description` (optional), `product_id` (optional). |
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/quotes' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"client_id":1,"currency_id":1,"quote_date":"2026-07-01","due_date":"2026-07-01","discount_type":0,"status":0,"items":[{"name":"Line item","quantity":1,"price":100,"tax_rate":15}]}'
Example response 201
{
"data": {
"id": 17,
"quote_number": "23",
"status": 0,
"status_label": "Draft",
"quote_date": "2026-07-01",
"due_date": "2026-07-15",
"amount": "5000.00",
"discount_type": 2,
"discount": "10.00",
"final_amount": "5250.00",
"note": null,
"term": null,
"custom_fields": [],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"items": [
{
"id": 31,
"product_id": null,
"name": "Website redesign",
"description": null,
"quantity": "1.00",
"price": "5000.00",
"tax_rate": "15.00",
"total": "5000.00"
}
],
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/quotes/{quote}
requires: write
Update a quote
Full update — send every field you want kept, exactly like create.
Body parameters
quote_number
string
|
Custom quote number. Omitted on create (or already taken) → the next number in your sequence is assigned. On update, omitted keeps the current number. |
client_id
integer
required
|
Client id. |
currency_id
integer
required
|
Currency id — must be enabled for your workspace. |
banking_detail_id
integer
|
Banking detail shown on the PDF. Defaults to your workspace default. |
quote_date
date
required
|
Quote date (YYYY-MM-DD). |
due_date
date
required
|
Valid-until date — on or after the quote date. |
discount_type
integer
required
|
Discount type.
0 None · 1 Fixed amount · 2 Percentage
|
discount
number
|
Discount value. |
status
integer
required
|
Quote status.
0 Draft · 1 Converted · 2 Pending
|
note
string
|
Note shown on the quote. |
term
string
|
Terms shown on the quote. |
custom_fields
object
|
Values for the workspace's quote custom fields (Settings → Custom Fields), keyed by field key — e.g. `{"reg_no": "CA 123-456"}`. Quote fields are managed separately from invoice fields. Each value is validated against the field's type and options; required custom fields must be supplied. Responses return the stored entries as `[{key, label, type, value}]`. |
items
array
required
|
Line items. Totals, tax and the final amount are computed server-side from these rows. Each item: `name` (string, required), `quantity` (number ≥ 0.01, required), `price` (number ≥ 0, required), `tax_rate` (0–100, optional), `description` (optional), `product_id` (optional). |
Example request
curl -X PUT 'https://lekkeraccounting.com/api/v1/quotes/42' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"client_id":1,"currency_id":1,"quote_date":"2026-07-01","due_date":"2026-07-01","discount_type":0,"status":0,"items":[{"name":"Line item","quantity":1,"price":100,"tax_rate":15}]}'
Example response 200
{
"data": {
"id": 17,
"quote_number": "23",
"status": 0,
"status_label": "Draft",
"quote_date": "2026-07-01",
"due_date": "2026-07-15",
"amount": "5000.00",
"discount_type": 2,
"discount": "10.00",
"final_amount": "5250.00",
"note": null,
"term": null,
"custom_fields": [],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"items": [
{
"id": 31,
"product_id": null,
"name": "Website redesign",
"description": null,
"quantity": "1.00",
"price": "5000.00",
"tax_rate": "15.00",
"total": "5000.00"
}
],
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
/v1/quotes/{quote}
requires: write
Delete a quote
Soft delete — the record moves to the trash and can be restored. Returns 204 with no body.
Example request
curl -X DELETE 'https://lekkeraccounting.com/api/v1/quotes/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 204
No body.
/v1/quotes/{id}/restore
requires: write
Restore a quote
Restores a soft-deleted record from the trash. 404 if the id is not currently deleted.
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/quotes/42/restore' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 17,
"quote_number": "23",
"status": 0,
"status_label": "Draft",
"quote_date": "2026-07-01",
"due_date": "2026-07-15",
"amount": "5000.00",
"discount_type": 2,
"discount": "10.00",
"final_amount": "5250.00",
"note": null,
"term": null,
"custom_fields": [],
"client": {
"id": 7,
"display_name": "Mokoena Consulting",
"…": "…"
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"items": [
{
"id": 31,
"product_id": null,
"name": "Website redesign",
"description": null,
"quantity": "1.00",
"price": "5000.00",
"tax_rate": "15.00",
"total": "5000.00"
}
],
"created_at": "2026-07-01T09:30:00Z",
"updated_at": "2026-07-01T09:30:00Z",
"deleted_at": null
}
}
Purchase Orders
Purchase orders sent to suppliers, with the same server-computed totals as invoices. Requires the `manage_expenses` permission.
/v1/purchase-orders
requires: read
List purchase-orders
Paginated list, newest first by default.
Query parameters
page
integer |
Page number (default 1). |
per_page
integer |
Results per page, 1–100 (default 25). |
sort
string |
Sort key. One of: `order_date`, `expected_date`, `final_amount`, `status`, `created_at`. |
direction
string |
`asc` or `desc` (default `desc`). |
trashed
string |
Soft-delete visibility: `with` includes deleted records, `only` returns deleted records exclusively. |
search
string |
Matches the PO number, supplier name or reference. |
status
integer |
Filter by status.
0 Draft · 1 Sent · 2 Confirmed · 3 Received · 4 Billed · 5 Cancelled
|
date_from
date |
Order date on or after. |
date_to
date |
Order date on or before. |
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/purchase-orders' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 5,
"po_number": "4",
"status": 1,
"status_label": "Sent",
"supplier_name": "Acme Supplies",
"supplier_email": "orders@acme.example",
"supplier_phone": null,
"supplier_vat_number": null,
"supplier_address": null,
"delivery_address": null,
"reference": "Stationery Q3",
"order_date": "2026-07-10",
"expected_date": "2026-07-24",
"amount": "1000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "1150.00",
"note": null,
"term": null,
"expense_id": null,
"sent_at": "2026-07-10T11:00:00Z",
"currency": {
"id": 2,
"code": "NAD",
"…": "…"
},
"items": [
{
"id": 11,
"product_id": null,
"name": "Printer paper",
"description": null,
"quantity": "10.00",
"price": "50.00",
"tax_rate": "15.00",
"total": "500.00"
}
],
"created_at": "2026-07-10T09:30:00Z",
"updated_at": "2026-07-10T11:00:00Z",
"deleted_at": null
}
],
"links": {
"first": "…",
"last": "…",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"per_page": 25,
"total": 1,
"…": "…"
}
}
/v1/purchase-orders/{purchase_order}
requires: read
Get a purchase order
Returns a single purchase order by id.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/purchase-orders/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 5,
"po_number": "4",
"status": 1,
"status_label": "Sent",
"supplier_name": "Acme Supplies",
"supplier_email": "orders@acme.example",
"supplier_phone": null,
"supplier_vat_number": null,
"supplier_address": null,
"delivery_address": null,
"reference": "Stationery Q3",
"order_date": "2026-07-10",
"expected_date": "2026-07-24",
"amount": "1000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "1150.00",
"note": null,
"term": null,
"expense_id": null,
"sent_at": "2026-07-10T11:00:00Z",
"currency": {
"id": 2,
"code": "NAD",
"…": "…"
},
"items": [
{
"id": 11,
"product_id": null,
"name": "Printer paper",
"description": null,
"quantity": "10.00",
"price": "50.00",
"tax_rate": "15.00",
"total": "500.00"
}
],
"created_at": "2026-07-10T09:30:00Z",
"updated_at": "2026-07-10T11:00:00Z",
"deleted_at": null
}
}
/v1/purchase-orders
requires: write
Create a purchase order
Creates a purchase order. Returns 201 with the created record; validation failures return 422 with per-field errors.
Body parameters
supplier_name
string
required
|
Supplier name. |
supplier_email
string
|
Supplier email. |
supplier_phone
string
|
Supplier phone. |
supplier_vat_number
string
|
Supplier VAT number. |
supplier_address
string
|
Supplier address. |
delivery_address
string
|
Delivery address. |
reference
string
|
Your reference for this order. |
currency_id
integer
required
|
Currency id — must be enabled for your workspace. |
order_date
date
required
|
Order date (YYYY-MM-DD). |
expected_date
date
|
Expected delivery date — on or after the order date. |
discount_type
integer
required
|
Discount type.
0 None · 1 Fixed amount · 2 Percentage
|
discount
number
|
Discount value. |
status
integer
required
|
Order status. Billed orders are managed by converting to an expense in the app.
0 Draft · 1 Sent · 2 Confirmed · 3 Received · 5 Cancelled
|
note
string
|
Note shown on the order. |
term
string
|
Terms shown on the order. |
items
array
required
|
Line items. Totals, tax and the final amount are computed server-side from these rows. Each item: `name` (string, required), `quantity` (number ≥ 0.01, required), `price` (number ≥ 0, required), `tax_rate` (0–100, optional), `description` (optional), `product_id` (optional). |
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/purchase-orders' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"supplier_name":"Example","currency_id":1,"order_date":"2026-07-01","discount_type":0,"status":0,"items":[{"name":"Line item","quantity":1,"price":100,"tax_rate":15}]}'
Example response 201
{
"data": {
"id": 5,
"po_number": "4",
"status": 1,
"status_label": "Sent",
"supplier_name": "Acme Supplies",
"supplier_email": "orders@acme.example",
"supplier_phone": null,
"supplier_vat_number": null,
"supplier_address": null,
"delivery_address": null,
"reference": "Stationery Q3",
"order_date": "2026-07-10",
"expected_date": "2026-07-24",
"amount": "1000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "1150.00",
"note": null,
"term": null,
"expense_id": null,
"sent_at": "2026-07-10T11:00:00Z",
"currency": {
"id": 2,
"code": "NAD",
"…": "…"
},
"items": [
{
"id": 11,
"product_id": null,
"name": "Printer paper",
"description": null,
"quantity": "10.00",
"price": "50.00",
"tax_rate": "15.00",
"total": "500.00"
}
],
"created_at": "2026-07-10T09:30:00Z",
"updated_at": "2026-07-10T11:00:00Z",
"deleted_at": null
}
}
/v1/purchase-orders/{purchase_order}
requires: write
Update a purchase order
Full update — send every field you want kept, exactly like create.
Body parameters
supplier_name
string
required
|
Supplier name. |
supplier_email
string
|
Supplier email. |
supplier_phone
string
|
Supplier phone. |
supplier_vat_number
string
|
Supplier VAT number. |
supplier_address
string
|
Supplier address. |
delivery_address
string
|
Delivery address. |
reference
string
|
Your reference for this order. |
currency_id
integer
required
|
Currency id — must be enabled for your workspace. |
order_date
date
required
|
Order date (YYYY-MM-DD). |
expected_date
date
|
Expected delivery date — on or after the order date. |
discount_type
integer
required
|
Discount type.
0 None · 1 Fixed amount · 2 Percentage
|
discount
number
|
Discount value. |
status
integer
required
|
Order status. Billed orders are managed by converting to an expense in the app.
0 Draft · 1 Sent · 2 Confirmed · 3 Received · 5 Cancelled
|
note
string
|
Note shown on the order. |
term
string
|
Terms shown on the order. |
items
array
required
|
Line items. Totals, tax and the final amount are computed server-side from these rows. Each item: `name` (string, required), `quantity` (number ≥ 0.01, required), `price` (number ≥ 0, required), `tax_rate` (0–100, optional), `description` (optional), `product_id` (optional). |
Example request
curl -X PUT 'https://lekkeraccounting.com/api/v1/purchase-orders/42' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"supplier_name":"Example","currency_id":1,"order_date":"2026-07-01","discount_type":0,"status":0,"items":[{"name":"Line item","quantity":1,"price":100,"tax_rate":15}]}'
Example response 200
{
"data": {
"id": 5,
"po_number": "4",
"status": 1,
"status_label": "Sent",
"supplier_name": "Acme Supplies",
"supplier_email": "orders@acme.example",
"supplier_phone": null,
"supplier_vat_number": null,
"supplier_address": null,
"delivery_address": null,
"reference": "Stationery Q3",
"order_date": "2026-07-10",
"expected_date": "2026-07-24",
"amount": "1000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "1150.00",
"note": null,
"term": null,
"expense_id": null,
"sent_at": "2026-07-10T11:00:00Z",
"currency": {
"id": 2,
"code": "NAD",
"…": "…"
},
"items": [
{
"id": 11,
"product_id": null,
"name": "Printer paper",
"description": null,
"quantity": "10.00",
"price": "50.00",
"tax_rate": "15.00",
"total": "500.00"
}
],
"created_at": "2026-07-10T09:30:00Z",
"updated_at": "2026-07-10T11:00:00Z",
"deleted_at": null
}
}
/v1/purchase-orders/{purchase_order}
requires: write
Delete a purchase order
Soft delete — the record moves to the trash and can be restored. Returns 204 with no body.
Example request
curl -X DELETE 'https://lekkeraccounting.com/api/v1/purchase-orders/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 204
No body.
/v1/purchase-orders/{id}/restore
requires: write
Restore a purchase order
Restores a soft-deleted record from the trash. 404 if the id is not currently deleted.
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/purchase-orders/42/restore' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 5,
"po_number": "4",
"status": 1,
"status_label": "Sent",
"supplier_name": "Acme Supplies",
"supplier_email": "orders@acme.example",
"supplier_phone": null,
"supplier_vat_number": null,
"supplier_address": null,
"delivery_address": null,
"reference": "Stationery Q3",
"order_date": "2026-07-10",
"expected_date": "2026-07-24",
"amount": "1000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "1150.00",
"note": null,
"term": null,
"expense_id": null,
"sent_at": "2026-07-10T11:00:00Z",
"currency": {
"id": 2,
"code": "NAD",
"…": "…"
},
"items": [
{
"id": 11,
"product_id": null,
"name": "Printer paper",
"description": null,
"quantity": "10.00",
"price": "50.00",
"tax_rate": "15.00",
"total": "500.00"
}
],
"created_at": "2026-07-10T09:30:00Z",
"updated_at": "2026-07-10T11:00:00Z",
"deleted_at": null
}
}
/v1/purchase-orders/{purchase_order}/status
requires: write
Transition status
Moves the order through its lifecycle (draft → sent → confirmed → received / cancelled). Marking as sent stamps sent_at once. Billed orders can no longer change (422).
Body parameters
status
integer
required
|
The new status.
0 Draft · 1 Sent · 2 Confirmed · 3 Received · 5 Cancelled
|
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/purchase-orders/42/status' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"status":0}'
Example response 200
{
"data": {
"id": 5,
"po_number": "4",
"status": 1,
"status_label": "Sent",
"supplier_name": "Acme Supplies",
"supplier_email": "orders@acme.example",
"supplier_phone": null,
"supplier_vat_number": null,
"supplier_address": null,
"delivery_address": null,
"reference": "Stationery Q3",
"order_date": "2026-07-10",
"expected_date": "2026-07-24",
"amount": "1000.00",
"discount_type": 0,
"discount": "0.00",
"final_amount": "1150.00",
"note": null,
"term": null,
"expense_id": null,
"sent_at": "2026-07-10T11:00:00Z",
"currency": {
"id": 2,
"code": "NAD",
"…": "…"
},
"items": [
{
"id": 11,
"product_id": null,
"name": "Printer paper",
"description": null,
"quantity": "10.00",
"price": "50.00",
"tax_rate": "15.00",
"total": "500.00"
}
],
"created_at": "2026-07-10T09:30:00Z",
"updated_at": "2026-07-10T11:00:00Z",
"deleted_at": null
}
}
Payments
Payments recorded against invoices. New payments start unapproved; approving one recalculates the invoice's paid / partially-paid / overdue status. Requires the `manage_payments` permission.
/v1/payments
requires: read
List payments
Paginated list, newest first by default.
Query parameters
page
integer |
Page number (default 1). |
per_page
integer |
Results per page, 1–100 (default 25). |
sort
string |
Sort key. One of: `payment_date`, `amount`, `created_at`. |
direction
string |
`asc` or `desc` (default `desc`). |
trashed
string |
Soft-delete visibility: `with` includes deleted records, `only` returns deleted records exclusively. |
invoice_id
integer |
Filter by invoice. |
is_approved
boolean |
Filter by approval state. |
date_from
date |
Payment date on or after. |
date_to
date |
Payment date on or before. |
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/payments' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 88,
"invoice_id": 42,
"amount": "3450.00",
"payment_mode": "bank_transfer",
"payment_date": "2026-07-15",
"notes": "EFT ref 99123",
"is_approved": false,
"invoice": {
"id": 42,
"invoice_number": "108"
},
"created_at": "2026-07-15T14:00:00Z",
"updated_at": "2026-07-15T14:00:00Z",
"deleted_at": null
}
],
"links": {
"first": "…",
"last": "…",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"per_page": 25,
"total": 1,
"…": "…"
}
}
/v1/payments/{payment}
requires: read
Get a payment
Returns a single payment by id.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/payments/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 88,
"invoice_id": 42,
"amount": "3450.00",
"payment_mode": "bank_transfer",
"payment_date": "2026-07-15",
"notes": "EFT ref 99123",
"is_approved": false,
"invoice": {
"id": 42,
"invoice_number": "108"
},
"created_at": "2026-07-15T14:00:00Z",
"updated_at": "2026-07-15T14:00:00Z",
"deleted_at": null
}
}
/v1/payments
requires: write
Create a payment
Creates a payment. Returns 201 with the created record; validation failures return 422 with per-field errors.
Body parameters
invoice_id
integer
required
|
The invoice this payment is against. |
amount
number
required
|
Payment amount — may not exceed the invoice's remaining balance. |
payment_mode
string
required
|
How the payment was made.
cash Cash · bank_transfer Bank transfer · card Card · cheque Cheque · other Other
|
payment_date
date
required
|
Payment date (YYYY-MM-DD). |
notes
string
|
Note (max 1000 characters). |
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/payments' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"invoice_id":1,"amount":100,"payment_mode":"cash","payment_date":"2026-07-01"}'
Example response 201
{
"data": {
"id": 88,
"invoice_id": 42,
"amount": "3450.00",
"payment_mode": "bank_transfer",
"payment_date": "2026-07-15",
"notes": "EFT ref 99123",
"is_approved": false,
"invoice": {
"id": 42,
"invoice_number": "108"
},
"created_at": "2026-07-15T14:00:00Z",
"updated_at": "2026-07-15T14:00:00Z",
"deleted_at": null
}
}
/v1/payments/{payment}
requires: write
Update a payment
Full update — send every field you want kept, exactly like create.
Body parameters
invoice_id
integer
required
|
The invoice this payment is against. |
amount
number
required
|
Payment amount — may not exceed the invoice's remaining balance. |
payment_mode
string
required
|
How the payment was made.
cash Cash · bank_transfer Bank transfer · card Card · cheque Cheque · other Other
|
payment_date
date
required
|
Payment date (YYYY-MM-DD). |
notes
string
|
Note (max 1000 characters). |
Example request
curl -X PUT 'https://lekkeraccounting.com/api/v1/payments/42' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"invoice_id":1,"amount":100,"payment_mode":"cash","payment_date":"2026-07-01"}'
Example response 200
{
"data": {
"id": 88,
"invoice_id": 42,
"amount": "3450.00",
"payment_mode": "bank_transfer",
"payment_date": "2026-07-15",
"notes": "EFT ref 99123",
"is_approved": false,
"invoice": {
"id": 42,
"invoice_number": "108"
},
"created_at": "2026-07-15T14:00:00Z",
"updated_at": "2026-07-15T14:00:00Z",
"deleted_at": null
}
}
/v1/payments/{payment}
requires: write
Delete a payment
Soft delete — the record moves to the trash and can be restored. Returns 204 with no body.
Example request
curl -X DELETE 'https://lekkeraccounting.com/api/v1/payments/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 204
No body.
/v1/payments/{id}/restore
requires: write
Restore a payment
Restores a soft-deleted record from the trash. 404 if the id is not currently deleted.
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/payments/42/restore' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 88,
"invoice_id": 42,
"amount": "3450.00",
"payment_mode": "bank_transfer",
"payment_date": "2026-07-15",
"notes": "EFT ref 99123",
"is_approved": false,
"invoice": {
"id": 42,
"invoice_number": "108"
},
"created_at": "2026-07-15T14:00:00Z",
"updated_at": "2026-07-15T14:00:00Z",
"deleted_at": null
}
}
/v1/payments/{payment}/approve
requires: write
Approve a payment
Marks the payment as approved and recalculates the invoice status.
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/payments/42/approve' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 88,
"invoice_id": 42,
"amount": "3450.00",
"payment_mode": "bank_transfer",
"payment_date": "2026-07-15",
"notes": "EFT ref 99123",
"is_approved": true,
"invoice": {
"id": 42,
"invoice_number": "108"
},
"created_at": "2026-07-15T14:00:00Z",
"updated_at": "2026-07-15T14:00:00Z",
"deleted_at": null
}
}
/v1/payments/{payment}/reject
requires: write
Reject a payment
Marks the payment as not approved and recalculates the invoice status.
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/payments/42/reject' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 88,
"invoice_id": 42,
"amount": "3450.00",
"payment_mode": "bank_transfer",
"payment_date": "2026-07-15",
"notes": "EFT ref 99123",
"is_approved": false,
"invoice": {
"id": 42,
"invoice_number": "108"
},
"created_at": "2026-07-15T14:00:00Z",
"updated_at": "2026-07-15T14:00:00Z",
"deleted_at": null
}
}
Expenses
Business expenses. A single scanned expense response includes its extracted invoice items and each item's category; when no items exist, the expense-level category applies to the whole invoice. API-created expenses are Manual-source; scanned invoices and bank-statement imports are created in the app. Requires the `manage_expenses` permission.
/v1/expenses
requires: read
List expenses
Paginated list, newest first by default.
Query parameters
page
integer |
Page number (default 1). |
per_page
integer |
Results per page, 1–100 (default 25). |
sort
string |
Sort key. One of: `expense_date`, `total_amount`, `vendor_name`, `created_at`. |
direction
string |
`asc` or `desc` (default `desc`). |
trashed
string |
Soft-delete visibility: `with` includes deleted records, `only` returns deleted records exclusively. |
search
string |
Matches vendor name, description or reference. |
status
integer |
Filter by status.
0 Draft · 1 Pending Review · 2 Approved · 3 Rejected
|
source
integer |
Filter by source.
0 Manual · 1 Invoice Scan · 2 Bank Statement
|
expense_category_id
integer |
Filter by whole-expense or invoice-item category. |
date_from
date |
Expense date on or after. |
date_to
date |
Expense date on or before. |
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/expenses' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 231,
"expense_number": "58",
"vendor_name": "CTM Office Park",
"vendor_tax_number": null,
"amount": "2000.00",
"tax_amount": "300.00",
"total_amount": "2300.00",
"expense_date": "2026-07-03",
"description": "July office rent",
"reference": "RENT-JUL",
"source": 0,
"source_label": "Manual",
"status": 2,
"status_label": "Approved",
"has_invoice": false,
"invoice_required": true,
"category": {
"id": 4,
"name": "Rent",
"color": "#f97316",
"is_active": true
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"created_at": "2026-07-03T08:00:00Z",
"updated_at": "2026-07-03T08:00:00Z",
"deleted_at": null
}
],
"links": {
"first": "…",
"last": "…",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"per_page": 25,
"total": 1,
"…": "…"
}
}
/v1/expenses/{expense}
requires: read
Get a expense
Returns a single expense by id.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/expenses/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 231,
"expense_number": "58",
"vendor_name": "CTM Office Park",
"vendor_tax_number": null,
"amount": "2000.00",
"tax_amount": "300.00",
"total_amount": "2300.00",
"expense_date": "2026-07-03",
"description": "July office rent",
"reference": "RENT-JUL",
"source": 0,
"source_label": "Manual",
"status": 2,
"status_label": "Approved",
"has_invoice": false,
"invoice_required": true,
"category": {
"id": 4,
"name": "Rent",
"color": "#f97316",
"is_active": true
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"created_at": "2026-07-03T08:00:00Z",
"updated_at": "2026-07-03T08:00:00Z",
"deleted_at": null
}
}
/v1/expenses
requires: write
Create a expense
Creates a expense. Returns 201 with the created record; validation failures return 422 with per-field errors.
Body parameters
vendor_name
string
|
Who was paid. |
vendor_tax_number
string
|
Vendor VAT / tax number. |
amount
number
required
|
Amount excluding tax. |
tax_amount
number
required
|
Tax portion (0 if none). |
total_amount
number
required
|
Total including tax. |
expense_date
date
required
|
Expense date (YYYY-MM-DD). |
description
string
|
What the expense was for. |
reference
string
|
Receipt / statement reference. |
expense_category_id
integer
|
Category id (see GET /expense-categories). |
currency_id
integer
|
Currency id. |
invoice_required
boolean
|
Whether a supplier invoice must still be attached. |
status
integer
|
Status — defaults to Pending Review on create.
0 Draft · 1 Pending Review · 2 Approved · 3 Rejected
|
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/expenses' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"amount":100,"tax_amount":100,"total_amount":100,"expense_date":"2026-07-01"}'
Example response 201
{
"data": {
"id": 231,
"expense_number": "58",
"vendor_name": "CTM Office Park",
"vendor_tax_number": null,
"amount": "2000.00",
"tax_amount": "300.00",
"total_amount": "2300.00",
"expense_date": "2026-07-03",
"description": "July office rent",
"reference": "RENT-JUL",
"source": 0,
"source_label": "Manual",
"status": 2,
"status_label": "Approved",
"has_invoice": false,
"invoice_required": true,
"category": {
"id": 4,
"name": "Rent",
"color": "#f97316",
"is_active": true
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"created_at": "2026-07-03T08:00:00Z",
"updated_at": "2026-07-03T08:00:00Z",
"deleted_at": null
}
}
/v1/expenses/{expense}
requires: write
Update a expense
Full update — send every field you want kept, exactly like create.
Body parameters
vendor_name
string
|
Who was paid. |
vendor_tax_number
string
|
Vendor VAT / tax number. |
amount
number
required
|
Amount excluding tax. |
tax_amount
number
required
|
Tax portion (0 if none). |
total_amount
number
required
|
Total including tax. |
expense_date
date
required
|
Expense date (YYYY-MM-DD). |
description
string
|
What the expense was for. |
reference
string
|
Receipt / statement reference. |
expense_category_id
integer
|
Category id (see GET /expense-categories). |
currency_id
integer
|
Currency id. |
invoice_required
boolean
|
Whether a supplier invoice must still be attached. |
status
integer
|
Status — defaults to Pending Review on create.
0 Draft · 1 Pending Review · 2 Approved · 3 Rejected
|
Example request
curl -X PUT 'https://lekkeraccounting.com/api/v1/expenses/42' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"amount":100,"tax_amount":100,"total_amount":100,"expense_date":"2026-07-01"}'
Example response 200
{
"data": {
"id": 231,
"expense_number": "58",
"vendor_name": "CTM Office Park",
"vendor_tax_number": null,
"amount": "2000.00",
"tax_amount": "300.00",
"total_amount": "2300.00",
"expense_date": "2026-07-03",
"description": "July office rent",
"reference": "RENT-JUL",
"source": 0,
"source_label": "Manual",
"status": 2,
"status_label": "Approved",
"has_invoice": false,
"invoice_required": true,
"category": {
"id": 4,
"name": "Rent",
"color": "#f97316",
"is_active": true
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"created_at": "2026-07-03T08:00:00Z",
"updated_at": "2026-07-03T08:00:00Z",
"deleted_at": null
}
}
/v1/expenses/{expense}
requires: write
Delete a expense
Soft delete — the record moves to the trash and can be restored. Returns 204 with no body.
Example request
curl -X DELETE 'https://lekkeraccounting.com/api/v1/expenses/42' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 204
No body.
/v1/expenses/{id}/restore
requires: write
Restore a expense
Restores a soft-deleted record from the trash. 404 if the id is not currently deleted.
Example request
curl -X POST 'https://lekkeraccounting.com/api/v1/expenses/42/restore' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": {
"id": 231,
"expense_number": "58",
"vendor_name": "CTM Office Park",
"vendor_tax_number": null,
"amount": "2000.00",
"tax_amount": "300.00",
"total_amount": "2300.00",
"expense_date": "2026-07-03",
"description": "July office rent",
"reference": "RENT-JUL",
"source": 0,
"source_label": "Manual",
"status": 2,
"status_label": "Approved",
"has_invoice": false,
"invoice_required": true,
"category": {
"id": 4,
"name": "Rent",
"color": "#f97316",
"is_active": true
},
"currency": {
"id": 1,
"code": "ZAR",
"…": "…"
},
"created_at": "2026-07-03T08:00:00Z",
"updated_at": "2026-07-03T08:00:00Z",
"deleted_at": null
}
}
Reference Data
Read-only lookups for the ids used elsewhere in the API.
/v1/currencies
requires: read
List currencies
Currencies enabled for your workspace — valid values for every currency_id field.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/currencies' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 1,
"name": "South African Rand",
"code": "ZAR",
"symbol": "R",
"is_custom": false
},
{
"id": 2,
"name": "Namibian Dollar",
"code": "NAD",
"symbol": "N$",
"is_custom": false
}
]
}
/v1/expense-categories
requires: read
List expense categories
Active expense categories — valid values for expense_category_id.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/expense-categories' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 4,
"name": "Rent",
"color": "#f97316",
"is_active": true
}
]
}
/v1/banking-details
requires: read
List banking details
Your workspace's bank accounts — valid values for banking_detail_id on invoices and quotes. Requires the `manage_invoices` permission.
Example request
curl -X GET 'https://lekkeraccounting.com/api/v1/banking-details' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/json'
Example response 200
{
"data": [
{
"id": 1,
"name": "FNB Cheque",
"bank_name": "FNB",
"account_holder": "Mokoena Consulting",
"account_number": "62012345678",
"branch_code": "250655",
"swift_code": null,
"iban": null,
"is_default": true
}
]
}