Recurring invoices
Templates of invoices that repeat (monthly, quarterly, yearly): created as drafts, activated by a human after a second person approves the terms, and made on demand by a run that is idempotent and never makes a period twice.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
GET /v1/recurring-invoices |
List recurring invoices | read |
|
POST /v1/recurring-invoices |
Create a recurring invoice | draft |
required |
POST /v1/recurring-invoices/run |
Make the recurring invoices that are due | draft |
required |
GET /v1/recurring-invoices/{template_id} |
Get a recurring invoice | read |
|
PATCH /v1/recurring-invoices/{template_id} |
Change a recurring invoice | draft |
|
POST /v1/recurring-invoices/{template_id}/activate |
Activate a recurring invoice | draft |
|
POST /v1/recurring-invoices/{template_id}/pause |
Pause a recurring invoice | draft |
|
GET /v1/recurring-invoices/{template_id}/runs |
List the invoices a recurring invoice made | read |
GET /v1/recurring-invoices
List recurring invoices
Recurring invoice templates, the one that falls due first at the top (templates without a
next due date, the ended ones, last). due_through is the due list: the templates a run
through that day would make invoices for, the same rule as the Role Center cue
(recurring_due in GET /v1/entities/{entity}/overview). Read-only.
| Required scope | read (admin implies every scope) |
| Operation id | list_recurring_invoices_v1_recurring_invoices_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string or null | Only this entity's templates (code or id). | |
| query | status |
"active" | "draft" | "ended" | "paused" | "pending_approval" or null |
Only templates with this status: draft, pending_approval, active, paused or ended. |
|
| query | party |
string or null | Only this customer's templates (code or id); needs entity. |
|
| query | due_through |
string (date) or null | Only the templates a run through this day would make invoices for: active, with a next due date on or before it (ISO 8601). This is the list the Role Center's recurring_due counts. |
|
| query | limit |
integer | How many templates to return, the next to fall due first. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/recurring-invoices" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of RecurringInvoiceOut |
| 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)
[
{
"approved_at": "2026-10-28T09:12:44Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"auto_submit": false,
"created_at": "2026-10-27T16:03:10Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"entity": "CH01",
"frequency": "monthly",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"invoices_made": 1,
"lines": [
{
"description": "Hosting and support",
"quantity": "1",
"rate_type": "standard",
"reverse_charge": false,
"supply_type": "services",
"unit_price": "450.00",
"vat_scope": "in_scope"
}
],
"name": "Hosting and support, monthly",
"net_total": "450.00",
"next_due": "2026-12-01",
"party_code": "C-CH",
"party_id": "7d1e2f3a-4b5c-4d6e-8f70-91a2b3c4d5e6",
"party_name": "Zürich Kunde AG",
"payment_terms_days": 30,
"start_date": "2026-11-01",
"status": "active",
"terms_approved": true,
"terms_digest": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
}
]
POST /v1/recurring-invoices
Create a recurring invoice
Create a recurring invoice as a draft: the customer, currency, lines, frequency,
start and end dates and payment terms of an invoice that repeats. Nothing is invoiced until
a person activates it (POST .../{template_id}/activate) and a second person approves its
terms, and then a run makes the invoices (POST /v1/recurring-invoices/run). The first
invoice is built without being kept, so a missing account, an unknown customer or a tax
decision that cannot be made is refused now (404, 422) instead of at the first run. The
next due date is the start date. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_recurring_invoice_v1_recurring_invoices_post |
Request body
application/json (required): RecurringInvoiceInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity that issues the invoices. |
party |
string | yes | Code (or id) of the customer the invoices are for. |
name |
string | yes | What the template is called in lists, e.g. Hosting and support, monthly. It is not printed on the invoices. (min length 1; max length 200) |
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 (start on the 31st: 31 Jan, 28 Feb, 31 Mar, 30 Apr). |
start_date |
string (date) | yes | The first due date (ISO 8601): the date of the first invoice. It may lie in the past; a run then makes the invoices that are due, a year of monthly ones at most per run. |
end_date |
string (date) or null | The last day an invoice may fall due, inclusive (ISO 8601); null for no end. After the last due date the template ends by itself. | |
currency |
string or null | ISO 4217 currency of the invoices; default: the customer's currency. (pattern ^[A-Z]{3}$) |
|
payment_terms_days |
integer or null | Days from the invoice date to its due date; default: the customer's own payment terms. (min 0; max 365) | |
auto_submit |
boolean | false (default): each due invoice is created as a draft for a person to submit. true: the system submits it through the approval policy as it is made; the approval of the terms says so on its face. (default false) |
|
notes |
string or null | Free text printed on every invoice (the invoice's notes). |
|
lines |
array of DocumentLineInput |
yes | The lines of every invoice, as on POST /v1/documents: tax is determined for each invoice on its own date by the same rules as for an invoice entered by hand. (min items 1) |
Example request
curl -X POST "$ERP_URL/v1/recurring-invoices" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"frequency": "monthly",
"lines": [
{
"description": "Hosting and support",
"quantity": "1",
"unit_price": "450.00"
}
],
"name": "Hosting and support, monthly",
"party": "C-CH",
"payment_terms_days": 30,
"start_date": "2026-11-01"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | RecurringInvoiceOut |
| 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)
{
"approved_at": "2026-10-28T09:12:44Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"auto_submit": false,
"created_at": "2026-10-27T16:03:10Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"entity": "CH01",
"frequency": "monthly",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"invoices_made": 1,
"lines": [
{
"description": "Hosting and support",
"quantity": "1",
"rate_type": "standard",
"reverse_charge": false,
"supply_type": "services",
"unit_price": "450.00",
"vat_scope": "in_scope"
}
],
"name": "Hosting and support, monthly",
"net_total": "450.00",
"next_due": "2026-12-01",
"party_code": "C-CH",
"party_id": "7d1e2f3a-4b5c-4d6e-8f70-91a2b3c4d5e6",
"party_name": "Zürich Kunde AG",
"payment_terms_days": 30,
"start_date": "2026-11-01",
"status": "active",
"terms_approved": true,
"terms_digest": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
}
POST /v1/recurring-invoices/run
Make the recurring invoices that are due
Make the invoices of every active recurring invoice that has a due date on or before
through (never after today where the template's company is), in date order, as ordinary
invoices dated their due date.
Each is a draft for a person to submit, or, when the template has auto_submit, is
submitted by the system through the normal path (the approval policy, the open period, the
numbering), which the approval of the terms authorised. It is idempotent: a repeated,
retried or simultaneous run makes nothing twice (one invoice per template 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 customer) is failed with its error, stops that template
at that date and leaves the others running; the next run tries it again. At most twelve due
dates per template are made in one run: more_due says to run again. A person or a script
calls this, and the scheduled close job runs it daily for the entities whose close calendar
names recurring_invoices (PUT /v1/entities/{entity}/close-schedule). Needs an
Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | run_recurring_invoices_v1_recurring_invoices_run_post |
Request body
application/json (required): RecurringRunInput
| Field | Type | Required | Description |
|---|---|---|---|
through |
string (date) | yes | Make the invoices that fall due on or before this day (ISO 8601, inclusive). It may not lie more than a day after the server's date (UTC), so your own date is always accepted. |
entity |
string or null | Only the templates of this entity (code or id); default: all. | |
template |
string (uuid) or null | Only this template (it must be active); default: every one. |
Example request
curl -X POST "$ERP_URL/v1/recurring-invoices/run" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"through": "2026-11-30"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringRunResult |
| 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": [
{
"document_id": "9b2f6a3e-1c4d-4e8a-8f10-5d7c2a9e4b11",
"due_date": "2026-11-01",
"status": "drafted",
"template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"template_name": "Hosting and support, monthly"
},
{
"document_id": "0f1e2d3c-4b5a-4968-8776-655443322110",
"due_date": "2026-12-01",
"status": "drafted",
"template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"template_name": "Hosting and support, monthly"
}
],
"made": 2,
"more_due": false,
"through": "2026-12-01"
}
GET /v1/recurring-invoices/{template_id}
Get a recurring invoice
One recurring invoice: its terms, status, next due date, whether the current terms are the
approved ones (terms_approved), the pending approval request and how many invoices it has
made. 404 when it does not exist.
| Required scope | read (admin implies every scope) |
| Operation id | get_recurring_invoice_v1_recurring_invoices__template_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | template_id |
string (uuid) | yes | Id of the recurring invoice (GET /v1/recurring-invoices). |
Example request
curl "$ERP_URL/v1/recurring-invoices/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringInvoiceOut |
| 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)
{
"approved_at": "2026-10-28T09:12:44Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"auto_submit": false,
"created_at": "2026-10-27T16:03:10Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"entity": "CH01",
"frequency": "monthly",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"invoices_made": 1,
"lines": [
{
"description": "Hosting and support",
"quantity": "1",
"rate_type": "standard",
"reverse_charge": false,
"supply_type": "services",
"unit_price": "450.00",
"vat_scope": "in_scope"
}
],
"name": "Hosting and support, monthly",
"net_total": "450.00",
"next_due": "2026-12-01",
"party_code": "C-CH",
"party_id": "7d1e2f3a-4b5c-4d6e-8f70-91a2b3c4d5e6",
"party_name": "Zürich Kunde AG",
"payment_terms_days": 30,
"start_date": "2026-11-01",
"status": "active",
"terms_approved": true,
"terms_digest": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
}
PATCH /v1/recurring-invoices/{template_id}
Change a recurring invoice
Change a recurring invoice. Omitted fields keep their value; lines replaces every line.
Only name changes without consequence: any change to the terms pauses an active template
(so it makes no invoice until a second person approves the new terms) and takes one waiting
for approval back to draft, withdrawing the request. frequency and start_date cannot change
once an invoice was made (409): end the template and create a new one. 409 for an ended
template; 404, 422 as when creating.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | update_recurring_invoice_v1_recurring_invoices__template_id__patch |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | template_id |
string (uuid) | yes | Id of the recurring invoice (GET /v1/recurring-invoices). |
Request body
application/json (required): RecurringInvoiceUpdate
| Field | Type | Required | Description |
|---|---|---|---|
party |
string or null | Code (or id) of the customer. | |
name |
string or null | What the template is called. (min length 1; max length 200) | |
frequency |
"monthly" | "quarterly" | "yearly" or null |
monthly, quarterly or yearly; before the first invoice. |
|
start_date |
string (date) or null | The first due date (ISO 8601); before the first invoice. | |
end_date |
string (date) or null | The last due date, inclusive; null removes the end. |
|
currency |
string or null | ISO 4217 currency of the invoices. (pattern ^[A-Z]{3}$) |
|
payment_terms_days |
integer or null | Days to pay; null takes the customer's own terms. (min 0; max 365) |
|
auto_submit |
boolean or null | Whether the system submits each invoice as it is made. | |
notes |
string or null | Free text on every invoice. | |
lines |
array of DocumentLineInput or null |
The new lines; they replace all existing lines (at least one). (min items 1) |
Example request
curl -X PATCH "$ERP_URL/v1/recurring-invoices/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"end_date": "2027-10-31",
"payment_terms_days": 14
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringInvoiceOut |
| 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)
{
"approved_at": "2026-10-28T09:12:44Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"auto_submit": false,
"created_at": "2026-10-27T16:03:10Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"entity": "CH01",
"frequency": "monthly",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"invoices_made": 1,
"lines": [
{
"description": "Hosting and support",
"quantity": "1",
"rate_type": "standard",
"reverse_charge": false,
"supply_type": "services",
"unit_price": "450.00",
"vat_scope": "in_scope"
}
],
"name": "Hosting and support, monthly",
"net_total": "450.00",
"next_due": "2026-12-01",
"party_code": "C-CH",
"party_id": "7d1e2f3a-4b5c-4d6e-8f70-91a2b3c4d5e6",
"party_name": "Zürich Kunde AG",
"payment_terms_days": 30,
"start_date": "2026-11-01",
"status": "active",
"terms_approved": true,
"terms_digest": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
}
POST /v1/recurring-invoices/{template_id}/activate
Activate a recurring invoice
Ask for the terms of a draft or paused recurring invoice to be approved, so that runs make
its invoices. A human decision: the policy action activate_recurring_invoice denies
agents (403, naming the rule). By default a second person approves
(POST /v1/approvals/{approval_id}/approve; never the person who created the template or
asked), and the approval is bound to the digest of the terms: it activates only these terms,
and says on its face whether the invoices are submitted automatically (auto_submit). A
paused template whose terms are the ones already approved runs again at once. An
organization with one person allows the action in its approval policy. 409 when the customer
is not active, the template is not a draft or paused, or no due date is left before its end
date.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | activate_recurring_invoice_v1_recurring_invoices__template_id__activate_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | template_id |
string (uuid) | yes | Id of the recurring invoice (GET /v1/recurring-invoices). |
Example request
curl -X POST "$ERP_URL/v1/recurring-invoices/3fa85f64-5717-4562-b3fc-2c963f66afa6/activate" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringActivation |
| 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)
{
"approval_id": "c2d4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f",
"policy": "require_approval",
"template": {
"approval_id": "c2d4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f",
"auto_submit": false,
"created_at": "2026-10-27T16:03:10Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"entity": "CH01",
"frequency": "monthly",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"invoices_made": 1,
"lines": [
{
"description": "Hosting and support",
"quantity": "1",
"rate_type": "standard",
"reverse_charge": false,
"supply_type": "services",
"unit_price": "450.00",
"vat_scope": "in_scope"
}
],
"name": "Hosting and support, monthly",
"net_total": "450.00",
"next_due": "2026-12-01",
"party_code": "C-CH",
"party_id": "7d1e2f3a-4b5c-4d6e-8f70-91a2b3c4d5e6",
"party_name": "Zürich Kunde AG",
"payment_terms_days": 30,
"start_date": "2026-11-01",
"status": "pending_approval",
"terms_approved": false
}
}
POST /v1/recurring-invoices/{template_id}/pause
Pause a recurring invoice
Stop an active recurring invoice from making invoices, with a reason kept in the audit trail. It makes none until it is activated again (at once if its terms are unchanged since the approval). Pausing is open to anyone who may draft: it makes nothing happen. 409 unless the template is active.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | pause_recurring_invoice_v1_recurring_invoices__template_id__pause_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | template_id |
string (uuid) | yes | Id of the recurring invoice (GET /v1/recurring-invoices). |
Request body
application/json (required): RecurringPause
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept with the template and in the audit. (min length 1; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/recurring-invoices/3fa85f64-5717-4562-b3fc-2c963f66afa6/pause" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Price list under renegotiation: hold invoices"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | RecurringInvoiceOut |
| 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)
{
"approved_at": "2026-10-28T09:12:44Z",
"approved_by": {
"id": "user:bob",
"name": "Bob",
"type": "human"
},
"auto_submit": false,
"created_at": "2026-10-27T16:03:10Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"currency": "CHF",
"entity": "CH01",
"frequency": "monthly",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"invoices_made": 1,
"lines": [
{
"description": "Hosting and support",
"quantity": "1",
"rate_type": "standard",
"reverse_charge": false,
"supply_type": "services",
"unit_price": "450.00",
"vat_scope": "in_scope"
}
],
"name": "Hosting and support, monthly",
"net_total": "450.00",
"next_due": "2026-12-01",
"party_code": "C-CH",
"party_id": "7d1e2f3a-4b5c-4d6e-8f70-91a2b3c4d5e6",
"party_name": "Zürich Kunde AG",
"payment_terms_days": 30,
"start_date": "2026-11-01",
"status": "active",
"terms_approved": true,
"terms_digest": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
}
GET /v1/recurring-invoices/{template_id}/runs
List the invoices a recurring invoice made
Every period a run has made or tried for the template, oldest first: the due date, what came
of it (drafted, posted, pending_approval, failed), the invoice with its number and its
status now, the error of a failed period, and who ran it. 404 when the template does not
exist.
| Required scope | read (admin implies every scope) |
| Operation id | list_recurring_invoice_runs_v1_recurring_invoices__template_id__runs_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | template_id |
string (uuid) | yes | Id of the recurring invoice (GET /v1/recurring-invoices). |
Example request
curl "$ERP_URL/v1/recurring-invoices/3fa85f64-5717-4562-b3fc-2c963f66afa6/runs" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of RecurringRunOut |
| 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-11-02T08:30:00Z",
"created_by": {
"id": "user:alice",
"name": "Alice",
"type": "human"
},
"document_id": "9b2f6a3e-1c4d-4e8a-8f10-5d7c2a9e4b11",
"document_number": "INV-2026-000042",
"document_status": "posted",
"due_date": "2026-11-01",
"id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"status": "posted",
"template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
]
Schemas
RecurringActivation
| Field | Type | Required | Description |
|---|---|---|---|
template |
RecurringInvoiceOut |
yes | The template after the request. |
policy |
string | yes | allow (it is active now, or runs again with the terms already approved) or require_approval (a second person decides: approval_id). |
approval_id |
string (uuid) or null | yes | The approval request when a second person must decide; else null. |
RecurringInvoiceInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity that issues the invoices. |
party |
string | yes | Code (or id) of the customer the invoices are for. |
name |
string | yes | What the template is called in lists, e.g. Hosting and support, monthly. It is not printed on the invoices. (min length 1; max length 200) |
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 (start on the 31st: 31 Jan, 28 Feb, 31 Mar, 30 Apr). |
start_date |
string (date) | yes | The first due date (ISO 8601): the date of the first invoice. It may lie in the past; a run then makes the invoices that are due, a year of monthly ones at most per run. |
end_date |
string (date) or null | The last day an invoice may fall due, inclusive (ISO 8601); null for no end. After the last due date the template ends by itself. | |
currency |
string or null | ISO 4217 currency of the invoices; default: the customer's currency. (pattern ^[A-Z]{3}$) |
|
payment_terms_days |
integer or null | Days from the invoice date to its due date; default: the customer's own payment terms. (min 0; max 365) | |
auto_submit |
boolean | false (default): each due invoice is created as a draft for a person to submit. true: the system submits it through the approval policy as it is made; the approval of the terms says so on its face. (default false) |
|
notes |
string or null | Free text printed on every invoice (the invoice's notes). |
|
lines |
array of DocumentLineInput |
yes | The lines of every invoice, as on POST /v1/documents: tax is determined for each invoice on its own date by the same rules as for an invoice entered by hand. (min items 1) |
RecurringInvoiceOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the template. |
entity |
string | yes | Code of the entity that issues the invoices. |
party_id |
string (uuid) | yes | Id of the customer. |
party_code |
string | yes | Code of the customer, e.g. C-CH. |
party_name |
string | yes | Name of the customer. |
name |
string | yes | What the template is called in lists. |
status |
"active" | "draft" | "ended" | "paused" | "pending_approval" |
yes | draft, pending_approval (a second person is to approve the terms), active (runs generate invoices), 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 invoice is to be made for; null once the template has ended. It is due when it is on or before the day of the run. |
currency |
string | yes | ISO 4217 currency of the invoices. |
payment_terms_days |
integer or null | yes | Days from the invoice date to its due date; null: the customer's terms. |
auto_submit |
boolean | yes | Whether the system submits each invoice as it is made (through the approval policy) instead of leaving a draft. |
notes |
string or null | yes | Free text printed on every invoice. |
net_total |
string | yes | Net total of one invoice (the sum of the line amounts, rounded to the currency), before VAT. |
lines |
array of RecurringLineOut |
yes | The lines of every invoice. |
terms_digest |
string or null | yes | SHA-256 of the terms a second person approved; null before the first approval. |
terms_approved |
boolean | yes | Whether the current terms are the approved ones. False after a change to an approved template: it stays paused until the new terms are approved. |
approval_id |
string (uuid) or null | yes | The pending approval request (POST /v1/approvals/{approval_id}/approve) while status is pending_approval; else null. |
approved_by |
object or null | yes | Who approved the terms (type, id, name); null before. |
approved_at |
string (date-time) or null | yes | When the terms were approved (UTC). |
paused_by |
object or null | yes | Who paused it, or changed its terms. |
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 the template. |
created_at |
string (date-time) | yes | When it was created (UTC). |
invoices_made |
integer | yes | How many invoices its runs have made so far. |
RecurringInvoiceUpdate
Changes to a template. Omitted fields keep their value; null clears an optional one.
A change to anything but name is a change of the terms: it pauses an active template (and
takes a template waiting for approval back to draft), and the template runs again only after
a second person has approved the new terms. frequency and start_date can change only
before the first invoice was made.
| Field | Type | Required | Description |
|---|---|---|---|
party |
string or null | Code (or id) of the customer. | |
name |
string or null | What the template is called. (min length 1; max length 200) | |
frequency |
"monthly" | "quarterly" | "yearly" or null |
monthly, quarterly or yearly; before the first invoice. |
|
start_date |
string (date) or null | The first due date (ISO 8601); before the first invoice. | |
end_date |
string (date) or null | The last due date, inclusive; null removes the end. |
|
currency |
string or null | ISO 4217 currency of the invoices. (pattern ^[A-Z]{3}$) |
|
payment_terms_days |
integer or null | Days to pay; null takes the customer's own terms. (min 0; max 365) |
|
auto_submit |
boolean or null | Whether the system submits each invoice as it is made. | |
notes |
string or null | Free text on every invoice. | |
lines |
array of DocumentLineInput or null |
The new lines; they replace all existing lines (at least one). (min items 1) |
RecurringLineOut
A line of a recurring invoice as stored: the fields of an invoice line as entered. It is
a model of its own so that DocumentLineInput, an input everywhere else, is not an output.
| Field | Type | Required | Description |
|---|---|---|---|
description |
string or null | Text of the line, e.g. Consulting September 2026. Required, except on a line that names a product (default: the product's name) or credits a line (credits_line; default: that line's text). (min length 1) |
|
quantity |
string | Quantity as a decimal string, greater than 0; default 1. (default "1") |
|
unit_price |
string or null | Net unit price; negative only on vendor documents (e.g. a discount line). Required, except on a line that names a product: left out, it takes the customer's price from its price list in the document's currency, else the product's list price on the document date (422 product_unpriced when it has neither); and on a credit note line with credits_line: left out, the line credits that line as it was, at its price and (unless the line gives its own) its discount. |
|
discount_percent |
string or null | A discount on the line, in percent of quantity times unit price (at most 100; 0 for none). Give this or discount_amount, not both. |
|
discount_amount |
string or null | A discount on the line, as an amount off quantity times unit price in the document currency, at most what the line is worth (0 for none). Give this or discount_percent. |
|
credits_line |
integer or null | Credit notes with credits: the number of the line of the credited document this line credits. Without a unit_price the line credits it as it was: it takes that line's price and its discount with its share of the document discount, in proportion to the quantity it credits (what is left of them when it credits the rest of the line), and returns them as its own unit_price and discount, with as_credited true when it gave no discount either. To change the quantity of such a line later, send it again without price and discount; sent with them, it credits them as given. With a unit_price only the discount the line gives applies; where the credited line had a discount (its own or a share of the document discount) the line must give one (0 for none), else 422 credit_discount_required. The line also takes that line's tax decision as stored (supply_type, rate_type, tax_category, vat_scope and the tax: treatment, taxing jurisdiction, rates), so it reverses exactly the tax charged; one it states that differs is not taken, with the warning credit_takes_credited_vat. (min 1) |
|
account |
string or null | Defaults to the product's revenue account, else the party or entity default. | |
product |
string or null | Sales documents: code (or id) of an active product of the entity (GET /v1/products). The line takes the product's revenue account, supply_type and rate_type unless it names its own, and its name and price when it gives none (the price in force: GET /v1/products/{product_id}/price); the document's provenance.filled says what was taken. |
|
obligation |
string (uuid) or null | Sales documents: id of the performance obligation of an active revenue contract of the same customer and currency that the line bills (GET /v1/revenue-contracts/{contract_id}). The line is booked to the contract liability account (the role contract_liability), not to revenue: the recognition run makes the revenue (POST /v1/periods/{period_id}/revenue-recognition). It takes no account of its own, is not a supply of goods, and the net billed of the contract never exceeds its transaction price (posting is refused, 422). |
|
sales_line |
string (uuid) or null | Sales invoices: id of the sales order line the line invoices (GET /v1/sales-documents/{document}), of an open order of the same customer and currency. The invoices of an order line never add up to more than was ordered less what was closed (422); a draft reserves its quantity until it is voided. Usually set by POST /v1/sales-documents/{document}/invoice. |
|
delivery_line |
string (uuid) or null | Sales invoices: id of the line of a posted delivery note of the same customer that the line invoices (GET /v1/delivery-notes/{note_id}), of the same product. What the invoices of a delivery note line add up to, less what credit notes took back, never exceeds what was delivered less what came back (422). The goods left with the delivery note, so the invoice moves no stock. Usually set by POST /v1/delivery-notes/{note_id}/invoice. A line of a stock item that names none ships with the invoice: posting the invoice issues the goods (Dr cost of goods sold / Cr inventory). |
|
goods_returned |
boolean | Sales credit notes: the goods of this line came back. The line names the line it credits (credits_line), of a stock item; posting the credit note brings the quantity back into stock at the cost it left with (Dr inventory / Cr cost of goods sold). Without it no stock moves. (default false) |
|
warehouse |
string or null | Code (or id) of the warehouse the goods come back to (goods_returned) or ship from with the invoice (a stock item's invoice line that names no delivery note line); default the entity's default warehouse. |
|
supply_type |
"electronic_services" | "goods" | "services" |
goods, services (default) or electronic_services; part of the place-of-supply decision. (default "services") |
|
rate_type |
"exempt" | "parking" | "reduced" | "special" | "standard" | "super_reduced" | "zero" |
Which VAT rate of the jurisdiction applies: standard (default), reduced, super_reduced, parking, zero, special or exempt. (default "standard") |
|
tax_category |
string or null | One of the categories of the rate (GET /v1/entities/{entity}/vat-rate-types), e.g. books: it chooses the rate where the rate type has several (France reduced: 5.5 % or 10 %) and names the kind of an exempt supply (insurance). Omit for none. (max length 200) |
|
printed_tax_amount |
string or null | VAT shown on a vendor bill; compared with the determined VAT. (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$) |
|
vat_recovery_percent |
string or null | Purchase bills and their credit notes: how much of the line's input VAT the company may deduct, in percent (0 to 100, two places), e.g. 0 for a gift whose VAT the law excludes or 50 for a car used half privately. The rest is booked as part of the cost on the line's own account, not as input VAT, so no VAT return reports it. Omit (null) to take the account's vat_recovery_percent, else 100. A self-assessed line still owes all of its VAT. Below 100 it is refused (422) on a line without input VAT of our own: a sales line, one outside VAT, foreign VAT booked as cost, US sales or use tax, a Canadian line of several taxes. |
|
reverse_charge |
boolean | Vendor documents: the vendor invoices under reverse charge (EN 16931 VAT category AE, e.g. UStG §13b), so the VAT is self-assessed at the determined rate. (default false) |
|
use_tax |
boolean | A US company's bill line the vendor charged no sales tax on, for something used in the company's own state: the company self-assesses use tax on it at the state's rate and its use tax area's (ADR-0064). Refused on sales documents and for companies outside the US. (default false) |
|
vat_scope |
"in_scope" | "outside" |
in_scope (default): the tax is determined as usual. outside: the line is outside the scope of VAT, like interest and fees for late payment (compensation for damage), a supply within a VAT group or an Organschaft. It needs vat_scope_reason, has no tax determination and rate 0, carries no tax tag (so no VAT report or return has it) and prints the remark of its reason in the document's language. It takes no tax_category, printed_tax_amount or reverse_charge, and a document with such a line carries no cash discount. (default "in_scope") |
|
vat_scope_reason |
"damages" | "organschaft" | "vat_group" or null |
Required with vat_scope outside, refused otherwise: damages (compensation for damage), vat_group (a supply within a Swiss VAT group, Art. 13 MWSTG) or organschaft (an Innenumsatz of a German Organschaft, § 2 para. 2 no. 2 UStG). |
|
dimensions |
map of string | Dimension code to value code, e.g. {"cost_centre": "CC-200", "project": "P-17"} (GET /v1/dimensions): each an active value, valid on the document date in its company. They go onto the journal lines of the line's amount (not its VAT or the receivable or payable). An account may require a dimension (required_accounts of the dimension): the document is then refused at submission without it. |
|
until |
string (date) or null | The last due date the line is invoiced on: the last day of the revenue contract it bills, after that contract was ended early or shortened; null for every due date. |
RecurringPause
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept with the template and in the audit. (min length 1; max length 500) |
RecurringRunInput
| Field | Type | Required | Description |
|---|---|---|---|
through |
string (date) | yes | Make the invoices that fall due on or before this day (ISO 8601, inclusive). It may not lie more than a day after the server's date (UTC), so your own date is always accepted. |
entity |
string or null | Only the templates of this entity (code or id); default: all. | |
template |
string (uuid) or null | Only this template (it must be active); default: every one. |
RecurringRunItem
| Field | Type | Required | Description |
|---|---|---|---|
template_id |
string (uuid) | yes | The template the invoice was made from. |
template_name |
string | yes | Its name. |
due_date |
string (date) | yes | The due date of the period; also the invoice date. |
status |
"drafted" | "failed" | "pending_approval" | "posted" |
yes | drafted (left for a person to submit), posted, pending_approval (the approval policy asked for a second person) or failed (nothing was made; error says why, and the next run tries again). |
document_id |
string (uuid) or null | yes | The invoice; null when it failed. |
document_number |
string or null | yes | Its number once posted; null for a draft or a failure. |
error |
string or null | yes | Why a failed attempt failed; null otherwise. |
RecurringRunOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the run record. |
template_id |
string (uuid) | yes | The template. |
due_date |
string (date) | yes | The due date of the period. |
status |
"drafted" | "failed" | "pending_approval" | "posted" |
yes | What came of the period; see the run result. |
document_id |
string (uuid) or null | yes | The invoice; null when it failed. |
document_number |
string or null | yes | Its number once posted. |
document_status |
string or null | yes | The invoice's status now (draft, pending_approval, posted, void...). |
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). |
RecurringRunResult
| Field | Type | Required | Description |
|---|---|---|---|
through |
string (date) | yes | The day the run went through. |
made |
integer | yes | Invoices made by this run (drafts and posted ones). |
failed |
integer | yes | Periods that failed; they stay due. |
items |
array of RecurringRunItem |
yes | One entry per period made or failed, by template and due date. |
more_due |
boolean | yes | True when a template still has due dates after this run (a run makes twelve per template at most, and stops at a failed period): run again. |