Recurring journals
Journal entries that repeat (rent, allocations, accruals) monthly, quarterly or yearly, optionally reversed on the first day of the next period: posted on demand by a run that is idempotent, each entry through the approval policy.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
GET /v1/recurring-journals |
List recurring journals | read |
|
POST /v1/recurring-journals |
Create a recurring journal | draft |
required |
POST /v1/recurring-journals/run |
Post the recurring journals that are due | draft |
required |
GET /v1/recurring-journals/{recurring_journal_id} |
Get a recurring journal | read |
|
PATCH /v1/recurring-journals/{recurring_journal_id} |
Change a recurring journal | draft |
|
POST /v1/recurring-journals/{recurring_journal_id}/activate |
Activate a recurring journal | draft |
|
POST /v1/recurring-journals/{recurring_journal_id}/pause |
Pause a recurring journal | draft |
|
GET /v1/recurring-journals/{recurring_journal_id}/runs |
List the entries a recurring journal made | read |
GET /v1/recurring-journals
List recurring journals
Recurring journals, the one that falls due first at the top (the ended ones last).
due_through is the due list: the recurring journals a run through that day would make
entries for, the same rule as the Role Center cue. Read-only.
| Required scope | read (admin implies every scope) |
| Operation id | list_recurring_journals_v1_recurring_journals_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string or null | Only this entity's recurring journals (code or id). | |
| query | status |
"active" | "draft" | "ended" | "paused" or null |
Only those with this status: draft, active, paused or ended. |
|
| query | due_through |
string (date) or null | Only those a run through this day would make entries for: active, with a next due date on or before it (ISO 8601). This is the list the Role Center's recurring_journals_due counts. |
|
| query | limit |
integer | How many to return, next due first. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/recurring-journals" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of RecurringJournalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"activated_at": "2026-09-29T08:10:00Z",
"activated_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"created_at": "2026-09-29T08:05:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"description": "Accrued office rent",
"entity": "CH01",
"entries_made": 1,
"frequency": "monthly",
"id": "5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b",
"lines": [
{
"account": "6000",
"credit": "0.00",
"debit": "4500.00",
"description": "Office rent Zürich",
"dimensions": {}
},
{
"account": "2300",
"credit": "4500.00",
"debit": "0.00",
"description": "Accrued rent",
"dimensions": {}
}
],
"name": "Office rent accrual",
"next_due": "2026-10-31",
"reverse_next_period": true,
"start_date": "2026-09-30",
"status": "active"
}
]
POST /v1/recurring-journals
Create a recurring journal
Create a recurring journal as a draft: the description and lines of a journal entry
that repeats, how often, from when until when, and whether each entry is reversed on the
first day of the next period (an accrual). Nothing is posted until a person activates it and
a run makes the entries that are due (POST /v1/recurring-journals/run). The first entry is
built without being kept, so a missing account, lines that do not balance or a closed period
are refused now (404, 409, 422) instead of at the first run. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_recurring_journal_v1_recurring_journals_post |
Request body
application/json (required): RecurringJournalInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity whose books the entries are in. |
name |
string | yes | What the recurring journal is called in lists, e.g. Office rent accrual. (min length 1; max length 200) |
description |
string | yes | The description of every entry it makes, shown in the journal and the general ledger. (min length 1; max length 500) |
frequency |
"monthly" | "quarterly" | "yearly" |
yes | monthly, quarterly (every 3 months) or yearly. The due dates are counted from start_date and keep its day of the month; a month that is too short takes its last day, and a start on the last day of a month stays on month ends (30 Sep, 31 Oct, 30 Nov; 31 Jan, 28 Feb, 31 Mar). |
start_date |
string (date) | yes | The first due date (ISO 8601): the date of the first entry. It may lie in the past, in an open period. |
end_date |
string (date) or null | The last day an entry may fall due, inclusive (ISO 8601); null for no end. After the last due date the recurring journal ends by itself. | |
reverse_next_period |
boolean | An accrual: each entry is reversed on the first day of the period after its own (the entry's auto_reverse_on), when it posts. (default false) |
|
lines |
array of RecurringJournalLineInput |
yes | The lines of every entry, at least two. Debits and credits must balance. (min items 2) |
Example request
curl -X POST "$ERP_URL/v1/recurring-journals" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"description": "Accrued office rent",
"entity": "CH01",
"frequency": "monthly",
"lines": [
{
"account": "6000",
"debit": "4500.00",
"description": "Office rent Zürich"
},
{
"account": "2300",
"credit": "4500.00",
"description": "Accrued rent"
}
],
"name": "Office rent accrual",
"reverse_next_period": true,
"start_date": "2026-09-30"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | RecurringJournalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"activated_at": "2026-09-29T08:10:00Z",
"activated_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"created_at": "2026-09-29T08:05:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"description": "Accrued office rent",
"entity": "CH01",
"entries_made": 1,
"frequency": "monthly",
"id": "5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b",
"lines": [
{
"account": "6000",
"credit": "0.00",
"debit": "4500.00",
"description": "Office rent Zürich",
"dimensions": {}
},
{
"account": "2300",
"credit": "4500.00",
"debit": "0.00",
"description": "Accrued rent",
"dimensions": {}
}
],
"name": "Office rent accrual",
"next_due": "2026-10-31",
"reverse_next_period": true,
"start_date": "2026-09-30",
"status": "active"
}
POST /v1/recurring-journals/run
Post the recurring journals that are due
Make the entry of every due date on or before through (never after today where the
entity is) of every active recurring journal, in date order, each dated its due date.
Each entry is submitted like an entry typed by hand: the approval policy decides for the
caller whether it posts or waits for a second person (pending_approval). An accrual's entry
is reversed on the first day of the next period when it posts. It is idempotent: a
repeated, retried or simultaneous run makes nothing twice (one entry per recurring journal
and due date, enforced by the database); an Idempotency-Key replays the first answer. A
period that cannot be made (a closed period, an inactive account) is failed with its error,
stops that recurring journal at that date and leaves the others running; the next run tries
it again. An entry the second person rejects is made again by the next run the same way. At
most twelve due dates per recurring journal in one run: more_due says to run
again. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | run_recurring_journals_v1_recurring_journals_run_post |
Request body
application/json (required): RecurringJournalRunInput
| Field | Type | Required | Description |
|---|---|---|---|
through |
string (date) | yes | Make the entries that fall due on or before this day (ISO 8601, inclusive); never after today where the entity is. |
entity |
string or null | Only the recurring journals of this entity (code or id). | |
template |
string (uuid) or null | Only this recurring journal (it must be active). |
Example request
curl -X POST "$ERP_URL/v1/recurring-journals/run" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"through": "2026-10-31"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringJournalRunResult |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"failed": 0,
"items": [
{
"due_date": "2026-10-31",
"entry_id": "8e4f2a1b-3c5d-4e6f-9a7b-1c2d3e4f5a6b",
"entry_number": "JE-2026-000123",
"status": "posted",
"template_id": "5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b",
"template_name": "Office rent accrual"
}
],
"made": 1,
"more_due": false,
"through": "2026-10-31"
}
GET /v1/recurring-journals/{recurring_journal_id}
Get a recurring journal
One recurring journal: its lines, schedule, status, next due date and how many entries it has made. 404 when it does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | get_recurring_journal_v1_recurring_journals__recurring_journal_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | recurring_journal_id |
string (uuid) | yes | Id of the recurring journal (GET /v1/recurring-journals). |
Example request
curl "$ERP_URL/v1/recurring-journals/5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringJournalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"activated_at": "2026-09-29T08:10:00Z",
"activated_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"created_at": "2026-09-29T08:05:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"description": "Accrued office rent",
"entity": "CH01",
"entries_made": 1,
"frequency": "monthly",
"id": "5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b",
"lines": [
{
"account": "6000",
"credit": "0.00",
"debit": "4500.00",
"description": "Office rent Zürich",
"dimensions": {}
},
{
"account": "2300",
"credit": "4500.00",
"debit": "0.00",
"description": "Accrued rent",
"dimensions": {}
}
],
"name": "Office rent accrual",
"next_due": "2026-10-31",
"reverse_next_period": true,
"start_date": "2026-09-30",
"status": "active"
}
PATCH /v1/recurring-journals/{recurring_journal_id}
Change a recurring journal
Change a recurring journal. Omitted fields keep their value; lines replaces every line.
The next entry is built without being kept to check the change. frequency and start_date
cannot change once an entry was made (409): end it and create a new one. A change of lines,
end_date or reverse_next_period returns an active or paused recurring journal to draft,
so a person activates it again. An end date before the next due date ends it. 409 for an
ended recurring journal; 404, 422 as when creating.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | update_recurring_journal_v1_recurring_journals__recurring_journal_id__patch |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | recurring_journal_id |
string (uuid) | yes | Id of the recurring journal (GET /v1/recurring-journals). |
Request body
application/json (required): RecurringJournalUpdate
| Field | Type | Required | Description |
|---|---|---|---|
name |
string or null | What it is called. (min length 1; max length 200) | |
description |
string or null | The description of the entries. (min length 1; max length 500) | |
frequency |
"monthly" | "quarterly" | "yearly" or null |
monthly, quarterly or yearly; before the first entry. |
|
start_date |
string (date) or null | The first due date (ISO 8601); before the first entry. | |
end_date |
string (date) or null | The last due date, inclusive; null removes the end. |
|
reverse_next_period |
boolean or null | Whether each entry is reversed in the next period. | |
lines |
array of RecurringJournalLineInput or null |
The new lines; they replace all existing lines. (min items 2) |
Example request
curl -X PATCH "$ERP_URL/v1/recurring-journals/5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"end_date": "2027-12-31",
"reverse_next_period": true
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringJournalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"activated_at": "2026-09-29T08:10:00Z",
"activated_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"created_at": "2026-09-29T08:05:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"description": "Accrued office rent",
"entity": "CH01",
"entries_made": 1,
"frequency": "monthly",
"id": "5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b",
"lines": [
{
"account": "6000",
"credit": "0.00",
"debit": "4500.00",
"description": "Office rent Zürich",
"dimensions": {}
},
{
"account": "2300",
"credit": "4500.00",
"debit": "0.00",
"description": "Accrued rent",
"dimensions": {}
}
],
"name": "Office rent accrual",
"next_due": "2026-10-31",
"reverse_next_period": true,
"start_date": "2026-09-30",
"status": "active"
}
POST /v1/recurring-journals/{recurring_journal_id}/activate
Activate a recurring journal
Make a draft or paused recurring journal run, so that runs make its entries. A person's step: an agent is refused (403). The next entry is built without being kept first. Each entry a run makes still goes through the approval policy. 409 unless it is a draft or paused.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | activate_recurring_journal_v1_recurring_journals__recurring_journal_id__activate_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | recurring_journal_id |
string (uuid) | yes | Id of the recurring journal (GET /v1/recurring-journals). |
Example request
curl -X POST "$ERP_URL/v1/recurring-journals/5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b/activate" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringJournalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"activated_at": "2026-09-29T08:10:00Z",
"activated_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"created_at": "2026-09-29T08:05:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"description": "Accrued office rent",
"entity": "CH01",
"entries_made": 1,
"frequency": "monthly",
"id": "5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b",
"lines": [
{
"account": "6000",
"credit": "0.00",
"debit": "4500.00",
"description": "Office rent Zürich",
"dimensions": {}
},
{
"account": "2300",
"credit": "4500.00",
"debit": "0.00",
"description": "Accrued rent",
"dimensions": {}
}
],
"name": "Office rent accrual",
"next_due": "2026-10-31",
"reverse_next_period": true,
"start_date": "2026-09-30",
"status": "active"
}
POST /v1/recurring-journals/{recurring_journal_id}/pause
Pause a recurring journal
Stop an active recurring journal from making entries, with a reason kept in the audit trail, until it is activated again. 409 unless it is active.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | pause_recurring_journal_v1_recurring_journals__recurring_journal_id__pause_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | recurring_journal_id |
string (uuid) | yes | Id of the recurring journal (GET /v1/recurring-journals). |
Request body
application/json (required): RecurringJournalPause
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept with it and in the audit trail. (min length 1; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/recurring-journals/5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b/pause" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Lease under renegotiation"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringJournalOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"activated_at": "2026-09-29T08:10:00Z",
"activated_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"created_at": "2026-09-29T08:05:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"description": "Accrued office rent",
"entity": "CH01",
"entries_made": 1,
"frequency": "monthly",
"id": "5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b",
"lines": [
{
"account": "6000",
"credit": "0.00",
"debit": "4500.00",
"description": "Office rent Zürich",
"dimensions": {}
},
{
"account": "2300",
"credit": "4500.00",
"debit": "0.00",
"description": "Accrued rent",
"dimensions": {}
}
],
"name": "Office rent accrual",
"next_due": "2026-10-31",
"reverse_next_period": true,
"start_date": "2026-09-30",
"status": "active"
}
GET /v1/recurring-journals/{recurring_journal_id}/runs
List the entries a recurring journal made
Every period a run has made or tried for the recurring journal, oldest first: the due
date, what came of it (posted, pending_approval, rejected, failed), the entry with its
number and status now, its reversal (an accrual's), the error of a failed period, and who ran
it. 404 when the recurring journal does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | list_recurring_journal_runs_v1_recurring_journals__recurring_journal_id__runs_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | recurring_journal_id |
string (uuid) | yes | Id of the recurring journal (GET /v1/recurring-journals). |
Example request
curl "$ERP_URL/v1/recurring-journals/5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b/runs" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of RecurringJournalRunOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"created_at": "2026-10-01T08:30:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"due_date": "2026-09-30",
"entry_id": "8e4f2a1b-3c5d-4e6f-9a7b-1c2d3e4f5a6b",
"entry_number": "JE-2026-000123",
"entry_status": "posted",
"id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"reversal_id": "0f1e2d3c-4b5a-4968-8776-655443322110",
"reversal_number": "JE-2026-000124",
"status": "posted",
"template_id": "5b1c7e2a-9d34-4f6b-a8e1-3c2d4e5f6a7b"
}
]
Schemas
RecurringJournalInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity whose books the entries are in. |
name |
string | yes | What the recurring journal is called in lists, e.g. Office rent accrual. (min length 1; max length 200) |
description |
string | yes | The description of every entry it makes, shown in the journal and the general ledger. (min length 1; max length 500) |
frequency |
"monthly" | "quarterly" | "yearly" |
yes | monthly, quarterly (every 3 months) or yearly. The due dates are counted from start_date and keep its day of the month; a month that is too short takes its last day, and a start on the last day of a month stays on month ends (30 Sep, 31 Oct, 30 Nov; 31 Jan, 28 Feb, 31 Mar). |
start_date |
string (date) | yes | The first due date (ISO 8601): the date of the first entry. It may lie in the past, in an open period. |
end_date |
string (date) or null | The last day an entry may fall due, inclusive (ISO 8601); null for no end. After the last due date the recurring journal ends by itself. | |
reverse_next_period |
boolean | An accrual: each entry is reversed on the first day of the period after its own (the entry's auto_reverse_on), when it posts. (default false) |
|
lines |
array of RecurringJournalLineInput |
yes | The lines of every entry, at least two. Debits and credits must balance. (min items 2) |
RecurringJournalLineInput
| Field | Type | Required | Description |
|---|---|---|---|
account |
string | yes | Account code (or id) in the entity, e.g. 6000. |
debit |
decimal string | Debit amount in the line's currency, e.g. 4500.00; zero or omitted on a credit line. Exactly one of debit and credit must be above zero. (min 0; default "0") |
|
credit |
decimal string | Credit amount; zero or omitted on a debit line. (min 0; default "0") |
|
currency |
string or null | ISO 4217 currency of the line; default the entity's functional currency. Each entry converts it at the rate of its own date. (pattern ^[A-Z]{3}$) |
|
description |
string or null | Text of this line; may be null. | |
dimensions |
map of string | Analysis tags of the line, as on a journal entry, e.g. {"cost_centre": "ADMIN"}; empty when omitted. |
RecurringJournalLineOut
A line of a recurring journal as stored.
| Field | Type | Required | Description |
|---|---|---|---|
account |
string | yes | Account code. |
debit |
string | yes | Debit amount in the line's currency. |
credit |
string | yes | Credit amount in the line's currency. |
currency |
string or null | yes | Currency of the line; null: the functional one. |
description |
string or null | yes | Text of the line. |
dimensions |
map of string | yes | Analysis tags of the line. |
RecurringJournalOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the recurring journal. |
entity |
string | yes | Code of the entity. |
name |
string | yes | What it is called in lists. |
description |
string | yes | The description of every entry it makes. |
status |
"active" | "draft" | "ended" | "paused" |
yes | draft (never activated, or its lines, start, frequency, end date or reversal changed since), active (runs make entries), paused or ended (the end date has passed). |
frequency |
"monthly" | "quarterly" | "yearly" |
yes | monthly, quarterly or yearly. |
start_date |
string (date) | yes | The first due date. |
end_date |
string (date) or null | yes | The last due date, inclusive; null for none. |
next_due |
string (date) or null | yes | The next due date an entry is to be made for; null once it has ended. |
reverse_next_period |
boolean | yes | Whether each entry is reversed on the first day of the next period. |
lines |
array of RecurringJournalLineOut |
yes | The lines of every entry. |
activated_by |
object or null | yes | Who last activated it; null for a draft. |
activated_at |
string (date-time) or null | yes | When it was last activated (UTC); null for a draft. |
paused_by |
object or null | yes | Who paused it. |
paused_at |
string (date-time) or null | yes | When it was paused (UTC). |
pause_reason |
string or null | yes | Why it was paused. |
created_by |
object | yes | Who created it. |
created_at |
string (date-time) | yes | When it was created (UTC). |
entries_made |
integer | yes | How many entries its runs have made so far (a rejected entry not counted). |
RecurringJournalPause
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept with it and in the audit trail. (min length 1; max length 500) |
RecurringJournalRunInput
| Field | Type | Required | Description |
|---|---|---|---|
through |
string (date) | yes | Make the entries that fall due on or before this day (ISO 8601, inclusive); never after today where the entity is. |
entity |
string or null | Only the recurring journals of this entity (code or id). | |
template |
string (uuid) or null | Only this recurring journal (it must be active). |
RecurringJournalRunItem
| Field | Type | Required | Description |
|---|---|---|---|
template_id |
string (uuid) | yes | The recurring journal the entry was made from. |
template_name |
string | yes | Its name. |
due_date |
string (date) | yes | The due date of the period; also the entry date. |
status |
"failed" | "pending_approval" | "posted" | "rejected" |
yes | posted, pending_approval (the approval policy asked for a second person; the run record becomes posted or rejected when the entry is decided) or failed (nothing was made; error says why, the next run tries again). A rejected entry's period is due again: the next run makes it again. |
entry_id |
string (uuid) or null | yes | The journal entry; null when it failed. |
entry_number |
string or null | yes | Its number once posted. |
error |
string or null | yes | Why a failed attempt failed; null otherwise. |
RecurringJournalRunOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the run record. |
template_id |
string (uuid) | yes | The recurring journal. |
due_date |
string (date) | yes | The due date of the period. |
status |
"failed" | "pending_approval" | "posted" | "rejected" |
yes | What came of the period; see the run result. |
entry_id |
string (uuid) or null | yes | The journal entry; null when it failed. |
entry_number |
string or null | yes | Its number once posted. |
entry_status |
string or null | yes | The entry's status now (pending_approval, posted, rejected...). |
reversal_id |
string (uuid) or null | yes | The entry that reversed it (an accrual's reversal); null when none. |
reversal_number |
string or null | yes | The reversal's number. |
error |
string or null | yes | Why a failed attempt failed. |
created_by |
object | yes | Who ran it (type, id, name). |
created_at |
string (date-time) | yes | When (UTC). |
RecurringJournalRunResult
| Field | Type | Required | Description |
|---|---|---|---|
through |
string (date) | yes | The day the run went through. |
made |
integer | yes | Entries made by this run (posted or waiting for approval). |
failed |
integer | yes | Periods that failed; they stay due. |
items |
array of RecurringJournalRunItem |
yes | One item per period made or failed, by recurring journal and due date. |
more_due |
boolean | yes | True when a recurring journal still has due dates after this run (twelve per run at most, and a run stops at a failed period): run again. |
RecurringJournalUpdate
Changes to a recurring journal. Omitted fields keep their value; null removes the end
date. frequency and start_date can change only before the first entry was made. A change
of lines, end_date or reverse_next_period returns an active or paused recurring journal
to draft (its activation cleared): a person activates it again.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string or null | What it is called. (min length 1; max length 200) | |
description |
string or null | The description of the entries. (min length 1; max length 500) | |
frequency |
"monthly" | "quarterly" | "yearly" or null |
monthly, quarterly or yearly; before the first entry. |
|
start_date |
string (date) or null | The first due date (ISO 8601); before the first entry. | |
end_date |
string (date) or null | The last due date, inclusive; null removes the end. |
|
reverse_next_period |
boolean or null | Whether each entry is reversed in the next period. | |
lines |
array of RecurringJournalLineInput or null |
The new lines; they replace all existing lines. (min items 2) |