States exist for a reason: each one represents who is responsible for the record at that moment.
The states, in detail
draft
The submitter is still working on it. Bezala does not validate required fields while a record is in draft, so you can save a partial entry and come back to it. Drafts are visible only to the submitter.
A draft becomes a real expense when it's submitted — moving it to reviewing. In the API, this happens by POSTing or PUTting the record without draft: 1.
reviewing
The record is complete and someone needs to approve it. The submitter can no longer edit the substantive fields, but the approver can — when they click approve, they can pass updated parameters in the same call, and Bezala will apply the edits before flipping the state.
Multiple approvers may be required (see approval workflows). While the chain is partially complete, the record stays in reviewing and the next approver is notified.
unapproved
An approver rejected the record with a comment. The record bounces back to the submitter, who can read the comment, edit, and re-submit. Re-submission moves it back to reviewing.
queue
Fully approved. Waiting for the next accounting batch. The record is locked from edits; the only change that can still happen is being included in a batch.
You can pull all queued expenses with GET /api/expenses (look for the *_in_queue arrays in the response).
accounted
Included in a batch that was sent to bookkeeping. Read-only. The record carries a reference to the batch document; the batch document carries the bookkeeping voucher reference and the SEPA payment file (where applicable).
You can pull all accounted expenses with GET /api/archive, paginated by batch date.
The transitions, by endpoint
The transition endpoints are the same shape across the four expense types — only the noun changes:
Transition | Receipt | Daily allowance | Trip | Reward |
Create as draft | POST /api/transactions with draft=1 | POST /api/daily_allowances with draft=1 | POST /api/trips with draft=1 | POST /api/rewards with draft=1 |
Create and submit | Same POST, no draft | Same | Same | Same |
Submit a draft | PUT /api/transactions/:id without draft=1 | PUT /api/daily_allowances/:id without draft=1 | PUT /api/trips/:id without draft=1 | PUT /api/rewards/:id without draft=1 |
Approve | PUT /api/transactions/:id/approve | PUT /api/daily_allowances/:id/approve | PUT /api/trips/:id/approve | PUT /api/rewards/:id/approve |
Disapprove (with comment) | POST /api/transactions/:id/disapprove | POST /api/daily_allowances/:id/disapprove | POST /api/trips/:id/disapprove | POST /api/rewards/:id/disapprove |
Send to accounting | PUT /api/send_batch (company-wide) | same | same | same |
The approve endpoints accept an optional payload with the same fields as the corresponding update endpoint, so you can edit and approve in a single call.
What you can and can't do at each state
Action | draft | reviewing | unapproved | queue | accounted |
Submitter edits the record | ✅ | ❌ | ✅ | ❌ | ❌ |
Approver edits while approving | n/a | ✅ | n/a | n/a | ❌ |
Submitter deletes the record | ✅ | ✅ | ✅ | ❌ | ❌ |
Approver approves | ❌ | ✅ | ❌ | ❌ | ❌ |
Approver disapproves | ❌ | ✅ | ❌ | ❌ | ❌ |
Included in next batch | ❌ | ❌ | ❌ | ✅ | (already was) |
A note on partial approval chains
When a record is in reviewing and there are multiple approvers in sequence (A then B then C), an approve call by A doesn't move the record to queue — it just advances the chain. The record stays in reviewing until the last approver in the chain approves. The state machine is "is anyone still expected to act?", not "has anyone approved yet?".
Disapproval at any point in the chain bumps the record all the way back to unapproved. The submitter has to fix it and re-submit; the chain restarts from the beginning.
Listing by state
The state query parameter on list endpoints filters to a single state:
The endpoints GET /api/expenses (non-accounted, grouped by state) and GET /api/archive (accounted, grouped by batch) are the two views the Bezala web UI uses. They're a good shape for any integration that wants the same dashboard data.



