History, undo and redo in the API
vokse keeps a change ledger per household: one row per atomic change with the exact payload to revert it, who made it and when. The API exposes the rows a write produced, lists the history with its performers, and reverts any recorded change, precisely, from any client.
The X-Vokse-History-Events header
Every successful write (204 deletes included) returns the ids of the history rows it produced, comma-separated. A bulk action exposes its group id only. Keep them if you want to offer Undo to your users; idempotent replays carry no header because the first response already did.
PATCH /households/current/budgets/2026-08/categories/01J8… HTTP/1.1Authorization: Bearer vokse_sk_…Idempotency-Key: 3f1c…{"assignedCents":"10000"}HTTP/1.1 200 OKX-Vokse-History-Events: 01M0G0W2879NYKEG8CY3RH42XNReading the history
GET /households/{id}/history returns changes newest first with cursor pagination. Filter by member (userId), family (budget, transaction, category…), kind (budget.assign, transaction.update…), a time window, a group (its members) or explicit ids. Reversal rows are changes too and are listed unless includeReversals=false.
curl "https://api.vokse.ai/households/current/history?family=budget&from=2026-07-17T00:00:00Z&limit=50" \ -H "Authorization: Bearer vokse_sk_…"Undo and redo
POST /history/{eventId}/undo applies the exact inverse of a change through the same domain logic as the original write, records the reversal as a new history row and marks the change undone. POST /history/{eventId}/redo reverts that reversal. Both need an Idempotency-Key and return the reversal, the updated target and what was affected.
curl -X POST https://api.vokse.ai/households/current/history/01M0G0W2879NYKEG8CY3RH42XN/undo \ -H "Authorization: Bearer vokse_sk_…" \ -H "Idempotency-Key: 7c2f9b1e-4a8d-4e3a-9c1b-2f6e8d0a1b3c"- Owner or editor only; an owner-only change (a month reset, a deletion) needs an owner to revert it.
- An API key needs write:history AND the write scope of the data reverted (write:budgets for a budget change, write:transactions for a transaction, …).
- A revert is refused with 409 HISTORY_STALE when the data no longer matches the state the change recorded, nothing is changed.
- Two clients racing for the same undo: exactly one wins, the other gets 409 HISTORY_ALREADY_UNDONE.
- Domain refusals pass through unchanged, e.g. INSUFFICIENT_CATEGORY_FUNDS when moving money back is no longer possible.
- Changes recorded with undoable=false (closing or reconciling an account) are listed for review but cannot be reverted.
Error codes
- HISTORY_STALE: the entity moved on since the change; see details.reason.
- HISTORY_ALREADY_UNDONE: another client reverted it first.
- HISTORY_NOT_UNDONE: redo asked for a change that is in force.
- HISTORY_NOT_UNDOABLE: recorded for review only.
- HISTORY_PARTIAL: a group revert applied some members and refused others; see partial.failed.