Expenses
Record and read business expenses over the API. The read shape is a field-by-field allowlist over the stored record, so receipts, OCR output, draft flags, recurrence links, and audit metadata are never returned.
Expense endpoints require a secret key (ibp_sk_). expenses:read and
expenses:write are outside the publishable-key allowlist, so a publishable
key can never hold them.
The expense model
Properties
- Name
id- Type
- string
- Description
Unique identifier. Server-generated for expenses created through the API.
- Name
amount- Type
- number
- Description
Expense amount, rounded to 2 decimal places.
- Name
currency- Type
- string
- Description
ISO 4217 code, uppercase.
- Name
category / subcategory- Type
- string
- Description
Canonical category id (see expense categories) and a free-text subcategory.
subcategoryis full shape only.
- Name
date- Type
- string
- Description
Expense date, returned as an ISO 8601 timestamp. Stored internally as epoch seconds, so a date with no time component reads back at
00:00:00.000Z.
- Name
description / notes- Type
- string
- Description
Free text.
notesis full shape only.
- Name
tags- Type
- array
- Description
Free-form tags,
[]when none. Full shape only.
- Name
supplier- Type
- object
- Description
{ id, name }, either of which may benull.nullwhen no supplier is set. Full shape only.
- Name
payment- Type
- object
- Description
method,status,due_date,reference. Always present, withnullmembers when unset. Full shape only.
- Name
tax- Type
- object
- Description
{ amount, rate }, ornullwhen no tax block is stored. Full shape only.
- Name
status- Type
- string
- Description
Lifecycle status. Expenses created through the API are always
submitted. Dashboard expenses awaiting approval aredraft; automatic gateway-fee expenses arerecorded.
- Name
source- Type
- string
- Description
Provenance.
apifor expenses created through this endpoint,gateway_feefor the automatic payment-fee entries, other values for dashboard and recurring expenses.
- Name
editable- Type
- boolean
- Description
Whether the expense can be edited in the dashboard. Automatic gateway-fee expenses are
false.
- Name
line_items / metadata- Type
- array / object
- Description
Gateway-fee expenses only, full shape only. See Gateway-fee expenses.
- Name
created_at / updated_at- Type
- string
- Description
ISO 8601 timestamps.
created_atis full shape only.
The list endpoint returns a lighter shape: id, amount, currency,
category, date, status, source, editable, supplier_name,
description, and updated_at. Note that the supplier arrives as a flat
supplier_name string, not the supplier object. Retrieve an expense by id
for the full shape.
List all expenses
Retrieve a cursor-paginated list of expenses. Soft-deleted expenses are excluded; gateway-fee expenses are included.
Required scope: expenses:read
Query parameters
- Name
limit- Type
- integer
- Description
Page size (default 50, max 100). A non-numeric value falls back to 50.
- Name
cursor- Type
- string
- Description
Opaque cursor from
pagination.next_cursor.
- Name
category- Type
- string
- Description
Exact match on the canonical category id. Labels are not resolved here, unlike on create.
- Name
status- Type
- string
- Description
Exact match on the lifecycle status.
- Name
supplier_id- Type
- string
- Description
Exact match on the supplier id.
- Name
date_from / date_to- Type
- string
- Description
Inclusive range on the expense date. Accepts epoch seconds or ISO 8601. An unparseable value returns
400naming the field.
- Name
updated_after- Type
- string
- Description
ISO 8601. Returns expenses whose
updated_atis greater than or equal to this value, ordered byupdated_atdescending. An unparseable value returns400.
- Name
order_by- Type
- string
- Description
date(default, descending) orupdated_at(descending). Any other value returns400.
Allowed filter combinations
A date range can only be applied when the results are ordered by date, so the parameters that force the updated_at order are rejected rather than silently dropped. Exactly one of these three shapes is valid:
date_fromand/ordate_to, ordered bydate. This is the default order, soorder_bymay be omitted or set todate.updated_after, with no date range. Results come back ordered byupdated_atdescending.order_by=updated_at, with no date range.
Each shape composes with at most one of category, status, or supplier_id.
These return 400:
updated_aftertogether withdate_fromordate_to, codeconflicting_filters.order_by=updated_attogether withdate_fromordate_to, codeconflicting_filters.- Two or more of
category,status, andsupplier_id, codeinvalid_value, withfieldnaming the second offender. - Any
order_byvalue other thandateorupdated_at, codeinvalid_value.
Pagination
Pass pagination.next_cursor back as cursor. A cursor that is malformed, or that points at a document that no longer exists, returns 400 with code invalid_cursor. Treat that as an error and restart the list without a cursor; it is not the end of the list.
Soft-deleted expenses are removed after the page window is read, so a page can contain fewer than limit items. Use has_more and next_cursor to decide whether to continue, not the item count.
For delta sync, pass the highest updated_at you have seen as updated_after. The comparison is inclusive, so the last record of the previous sync is returned again.
Request
curl -G https://au.api.ibakepro.com/api/v2/expenses \
-H "Authorization: Bearer {secret_key}" \
-d category=ingredients \
-d date_from=2026-06-01 \
-d limit=10
Response
{
"data": [
{
"id": "Yb4Nq7TdWm2Fs9Ke1Rzu",
"amount": 124.50,
"currency": "AUD",
"category": "ingredients",
"date": "2026-06-01T00:00:00.000Z",
"status": "submitted",
"source": "api",
"editable": true,
"supplier_name": "Southbank Flour Co",
"description": "Bulk flour",
"updated_at": "2026-06-01T10:30:00.000Z"
}
],
"pagination": {
"limit": 10,
"has_more": false,
"next_cursor": null,
"prev_cursor": null
}
}
Response (400)
{
"error": {
"type": "validation_error",
"code": "conflicting_filters",
"message": "updated_after cannot be combined with date_from. Use updated_after for delta sync, or date_from for a date range.",
"field": "date_from"
}
}
Create an expense
Create an expense.
Required scope: expenses:write
Required attributes
- Name
amount- Type
- number
- Description
Must be a finite number greater than 0. Zero, a negative amount, and a non-numeric value all return
400. Stored rounded to 2 decimal places.
- Name
date- Type
- string
- Description
ISO 8601, or a string of epoch seconds. Must resolve to a positive epoch value, so dates at or before 1970-01-01T00:00:00Z are rejected.
- Name
category- Type
- string
- Description
A category id or its label, matched case-insensitively (see expense categories). The server stores the canonical id. An unknown value returns
400.
Optional attributes
- Name
currency- Type
- string
- Description
Three letters, uppercased before validation. Anything else returns
400. Omit to use the account currency; if that cannot be resolved the request returns500.
- Name
subcategory / description / notes- Type
- string
- Description
Free text.
- Name
tags- Type
- array
- Description
Free-form tags. A non-array value is stored as
[].
- Name
supplier- Type
- object
- Description
{ id, name }. Any other key is discarded.
- Name
payment- Type
- object
- Description
{ method, status, due_date, reference }. Only these four keys are accepted.
- Name
tax- Type
- object
- Description
{ amount, rate }.amountis rounded to 2 decimal places. Only these two keys are accepted.
All string values are trimmed and stripped of HTML before storage, except description and notes, which keep their markup.
Server-controlled fields
The request body cannot set provenance or lifecycle. These are forced on every API create and any value you send for them is discarded:
sourceisapi. A gateway-fee expense cannot be forged through this endpoint.statusissubmittedand the draft flag is off. The dashboard approval gate does not apply to API creates.editableistrue,auto_generatedisfalse, and the id is server-generated.line_itemsandmetadatabelong to gateway-fee expenses and are never accepted on create.
Response
201 with the full expense shape.
Idempotency
Send an Idempotency-Key header. A replay of a stored key returns the original response with an Idempotent-Replayed: true header. Reusing a key with a different body, or on a different route, returns 422 (idempotency_key_reused), and a retry while the first request is still running returns 409 (request_in_flight). Keys expire 24 hours after first use.
Request
curl -X POST https://au.api.ibakepro.com/api/v2/expenses \
-H "Authorization: Bearer {secret_key}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: exp-0001" \
-d '{
"amount": 124.50,
"date": "2026-06-01T00:00:00Z",
"category": "ingredients",
"description": "Bulk flour",
"supplier": { "name": "Southbank Flour Co" },
"tax": { "amount": 11.32, "rate": 0.1 }
}'
Response (201)
{
"id": "Yb4Nq7TdWm2Fs9Ke1Rzu",
"amount": 124.50,
"currency": "AUD",
"category": "ingredients",
"subcategory": null,
"description": "Bulk flour",
"notes": null,
"tags": [],
"date": "2026-06-01T00:00:00.000Z",
"status": "submitted",
"source": "api",
"editable": true,
"supplier": { "id": null, "name": "Southbank Flour Co" },
"payment": { "method": null, "status": null, "due_date": null, "reference": null },
"tax": { "amount": 11.32, "rate": 0.1 },
"created_at": "2026-06-01T10:30:00.000Z",
"updated_at": "2026-06-01T10:30:00.000Z"
}
Retrieve an expense
Retrieve a single expense by id, in the full shape. Returns 404 if the expense does not exist, is soft-deleted, or belongs to another account.
Required scope: expenses:read
Request
curl https://au.api.ibakepro.com/api/v2/expenses/Yb4Nq7TdWm2Fs9Ke1Rzu \
-H "Authorization: Bearer {secret_key}"
Gateway-fee expenses
When a payment gateway deducts a processing fee, the account records it automatically as one expense per day per account. They appear in list and retrieve responses so your expense total reconciles with the dashboard, and they cannot be created, edited, or deleted over the API.
They are recognisable by source: "gateway_fee" and editable: false. Their id is gateway_fees_ followed by the date (gateway_fees_2026-06-01), the category is bank_fees, the status is recorded, the supplier name is Payment gateways, and payment.status is deducted_at_source.
Retrieving one by id adds two fields that no other expense carries:
- Name
line_items- Type
- array
- Description
One entry per fee-bearing transaction:
order_id,order_label,gateway,amount.
- Name
metadata- Type
- object
- Description
transaction_countandgateways, the distinct gateway names in the breakdown.
Neither field is present in the list shape, so read the expense by id when you need the per-transaction breakdown.