Skip to main content

Approval workflows

When an expense leaves draft and enters reviewing, Bezala has to figure out who needs to approve it. This article explains how that decision works, how chains and branches behave, and how you can override the default chain on a per-expense basis.

J
Written by Julia Winberg

Where approvers come from

Four different sources can contribute approvers to an expense's chain:

  1. The user's default approver. Set on the user record. Every user has one.

  2. Account approvers. Set on the expense account being charged. Each expense account can list zero or more approvers.

  3. Cost-center approvers. Set on the cost centers the expense is tagged with. Each cost center can list zero or more approvers.

  4. Transaction-specific approvers. Set ad-hoc on a single record by calling POST /api/transactions/:id/approvers. These override everything else.

Bezala combines the sources into a single ordered list of approval steps. The exact merge rules are configurable per company, but the principle is: the user's manager always gets a say, and any account or cost center with its own approvers gets a say if the expense touches it.

then and or operators

When an account or cost center has multiple approvers, those approvers are joined by an operator that controls whether they approve in sequence or as alternatives:

  • then — sequential. A then B means A must approve first, and then B must approve. Both approvals are required.

  • or — branching. A or B means either A or B is sufficient. Whichever one acts first satisfies that step of the chain.

You can build chains like A then (B or C) then D. In the API, the operator is set per approver entry:

This reads as "12, then either 17 or someone, then 18". The operator on each entry tells Bezala what to do after that approver acts.

The chain in motion

When an expense enters reviewing:

  1. Bezala computes the chain by combining the user's approver, account approvers, and cost-center approvers.

  2. The first step of the chain is active. Whoever is at that step (or any of them, if it's an or step) can act.

  3. They approve. If the step is then, the chain advances. If or, the step is satisfied and the chain advances.

  4. The new active step lights up; the next approver is notified.

  5. When the last step is satisfied, the expense moves from reviewing to queue.

If anyone in the chain disapproves at any point, the chain is abandoned and the expense moves to unapproved. The submitter sees the disapproval comment, fixes the issue, and resubmits — which restarts the chain from step 1.

Inspecting the chain

When you read an expense, the response includes its current approval state — who has approved, who is up next, and any disapproval comments. The exact field names vary slightly by expense type; check the /apipie reference for the resource you're working with.

The list endpoint also accepts a special state filter, pending_my_approval, that returns just the expenses currently waiting on the calling user:

This is the right query for a "what's in my inbox?" dashboard.

Approving via the API

The approve endpoints take an optional payload with the same fields as the corresponding update endpoint. Sending an empty body just approves; sending a body lets you edit and approve in one call:

If the embedded edit fails validation, the approval is also aborted — Bezala does both or neither. The two-in-one shape exists because approvers commonly fix small mistakes (a missed cost center, a typo in the description) at approval time.

Disapproval requires a comment:

Overriding the chain on a single expense

Sometimes the default chain isn't right — a particular receipt needs a senior signature, or routing through a specific project lead. POST /api/transactions/:id/approvers replaces the chain on this one transaction:

Setting an explicit chain has a side effect: it resets approved_by and sends the transaction back to reviewing. If anyone had already approved under the old chain, that approval is wiped — the new chain starts from scratch. This is intentional: changing who approves changes the audit trail, and Bezala doesn't carry over consents that were given under different conditions.

A few quirks worth knowing

  • Self-approval is forbidden. A user cannot approve their own expense, even if they're somewhere in their own chain. Bezala will skip them.

  • Inactive users are skipped. If a user in the chain has been deactivated, Bezala falls through to the next approver. The chain doesn't get stuck on a missing person.

  • Manager and accountant roles get implicit visibility. Even if a manager isn't in a specific chain, they can see expenses flowing through their organisation. Approval still requires being in the chain (or holding the transaction-specific approver slot).

  • Approvals are auditable. Every state transition is timestamped and attributed. If you need that audit trail in your own system, pull it on read — it's part of the standard expense response.

Did this answer your question?