Saved reports
Lists and reports a person builds (or asks the assistant for) and keeps: run, shared, pinned to Home and e-mailed on a schedule, each reader seeing what they may.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
GET /v1/entities/{entity}/saved-reports |
List saved reports | read |
|
POST /v1/entities/{entity}/saved-reports |
Save a report | read |
|
POST /v1/entities/{entity}/saved-reports/run |
Run a report | read |
|
GET /v1/saved-report-sources |
List what a report can read | read |
|
GET /v1/saved-reports/{report_id} |
Read a saved report | read |
|
PATCH /v1/saved-reports/{report_id} |
Change a saved report | read |
|
DELETE /v1/saved-reports/{report_id} |
Delete a saved report | read |
|
GET /v1/saved-reports/{report_id}/csv |
Download a saved report | read |
|
PUT /v1/saved-reports/{report_id}/pin |
Pin to Home | read |
|
DELETE /v1/saved-reports/{report_id}/pin |
Unpin from Home | read |
|
GET /v1/saved-reports/{report_id}/result |
Run a saved report | read |
|
POST /v1/saved-reports/{report_id}/send |
E-mail a report now | read |
GET /v1/entities/{entity}/saved-reports
List saved reports
The company's reports the caller may open: their own and those shared with them, the pinned ones first.
| Required scope | read (admin implies every scope) |
| Operation id | list_saved_reports_v1_entities__entity__saved_reports_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| query | pinned |
boolean | Only those on the caller's Home, in the order pinned. (default false) |
Example request
curl "$ERP_URL/v1/entities/CH01/saved-reports" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of SavedReportOut |
| 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)
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"name": "string",
"definition": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
},
"owner": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string"
},
"mine": true,
"sharing": {
"everyone": false,
"people": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"schedule": {
"every": "week",
"weekday": 0,
"day": 1,
"hour": 7,
"recipients": [
null
]
},
"next_send_at": "2026-09-21T09:30:00Z",
"last_sent_at": "2026-09-21T09:30:00Z",
"last_send_problem": "string",
"pinned": true,
"path": "string",
"created_at": "2026-09-21T09:30:00Z",
"updated_at": "2026-09-21T09:30:00Z"
}
]
POST /v1/entities/{entity}/saved-reports
Save a report
Keep a report: only definition is needed (the name defaults to the source's). Shared
with everyone or some people, they open it as themselves. A schedule e-mails it to its
owner and people it is shared with. Audited as saved_report.create.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | create_saved_report_v1_entities__entity__saved_reports_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
Request body
application/json (required): SavedReportIn
| Field | Type | Required | Description |
|---|---|---|---|
definition |
ReportDefinition |
yes | What the report lists. |
name |
string or null | What the report is called; default: the source's name. (min length 1; max length 120) | |
sharing |
ReportSharing or null |
Default: only the owner. | |
schedule |
ReportSchedule or null |
Default: not sent. | |
pinned |
boolean | Pin it to the owner's Home at once. (default false) |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/saved-reports" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"definition": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
},
"name": "Unpaid invoices in Germany"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | SavedReportOut |
| 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)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"name": "string",
"definition": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
},
"owner": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string"
},
"mine": true,
"sharing": {
"everyone": false,
"people": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"schedule": {
"every": "week",
"weekday": 0,
"day": 1,
"hour": 7,
"recipients": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"next_send_at": "2026-09-21T09:30:00Z",
"last_sent_at": "2026-09-21T09:30:00Z",
"last_send_problem": "string",
"pinned": true,
"path": "string",
"created_at": "2026-09-21T09:30:00Z",
"updated_at": "2026-09-21T09:30:00Z"
}
POST /v1/entities/{entity}/saved-reports/run
Run a report
Run a report without saving it: the rows (or groups, with their counts and sums) the
caller may see, the totals of everything found, and for each row where it opens in the app
(each group carries the report of its rows, drill). Changes nothing. 422
saved_report_invalid names a field its source does not have; saved_report_day a date
that is not one.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | run_report_v1_entities__entity__saved_reports_run_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| query | language |
"de" | "en" | "fr" | "it" | "sv" |
The language of the labels: en (default), de, fr, it, sv. (default "en") |
Request body
application/json (required): ReportDefinition
| Field | Type | Required | Description |
|---|---|---|---|
source |
"bills" | "customers" | "deals" | "journal_lines" | "leads" | "payments" | "products" | "purchase_orders" | "sales_invoices" | "subscriptions" | "vendors" |
yes | The kind of record the report lists (GET /v1/saved-report-sources lists each with its fields). |
columns |
array of string | The fields shown, in order; empty: the source's usual ones. (max items 12) | |
filters |
array of ReportFilter |
The conditions a row meets, all of them. (max items 16) | |
group_by |
string or null | A field to group the rows by: one line per value, with the count and the sums. Amounts in a record's own currency are grouped per currency as well. (max length 40) | |
sums |
array of string | The amounts or numbers added up for each group and in the totals; empty: the source's main amount. (max items 4) | |
sort |
array of ReportSort |
The order. (max items 3) | |
limit |
integer | Rows or groups shown, at most 500. (min 1; max 500; default 100) |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/saved-reports/run" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ReportResult |
| 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)
{
"entity": "string",
"source": "bills",
"title": "string",
"as_of": "2026-09-21",
"company_currency": "string",
"columns": [
{
"name": "string",
"label": "string",
"type": "string",
"currency": "string"
}
],
"rows": [
{
"values": {},
"labels": {},
"path": "string"
}
],
"groups": [
{
"value": null,
"label": "string",
"currency": "string",
"count": 1,
"sums": {},
"drill": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
}
}
],
"group_by": {
"name": "string",
"label": "string",
"type": "string",
"currency": "string"
},
"sums": [
{
"name": "string",
"label": "string",
"type": "string",
"currency": "string"
}
],
"totals": [
{
"currency": "string",
"count": 1,
"sums": {}
}
],
"found": 1,
"truncated": true,
"definition": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
}
}
GET /v1/saved-report-sources
List what a report can read
The kinds of record a report lists (invoices, bills, customers, vendors, deals, leads, payments, purchase orders, subscriptions, journal lines, products), each with its fields: their type, the values of a list, whether a report may group by them, and the usual columns.
| Required scope | read (admin implies every scope) |
| Operation id | list_sources_v1_saved_report_sources_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | language |
"de" | "en" | "fr" | "it" | "sv" |
The language of the labels: en (default), de, fr, it, sv. (default "en") |
Example request
curl "$ERP_URL/v1/saved-report-sources" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ReportSourceOut |
| 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)
[
{
"name": "bills",
"label": "string",
"fields": [
{
"name": "string",
"label": "string",
"type": "bool",
"values": [
null
],
"value_labels": {},
"group": true,
"default": true,
"currency": null
}
],
"sum": "string"
}
]
GET /v1/saved-reports/{report_id}
Read a saved report
A report of the caller's or one shared with them (404 saved_report_not_found for any
other).
| Required scope | read (admin implies every scope) |
| Operation id | get_saved_report_v1_saved_reports__report_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | report_id |
string (uuid) | yes | Saved report id (GET /v1/entities/{entity}/saved-reports). |
Example request
curl "$ERP_URL/v1/saved-reports/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SavedReportOut |
| 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)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"name": "string",
"definition": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
},
"owner": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string"
},
"mine": true,
"sharing": {
"everyone": false,
"people": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"schedule": {
"every": "week",
"weekday": 0,
"day": 1,
"hour": 7,
"recipients": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"next_send_at": "2026-09-21T09:30:00Z",
"last_sent_at": "2026-09-21T09:30:00Z",
"last_send_problem": "string",
"pinned": true,
"path": "string",
"created_at": "2026-09-21T09:30:00Z",
"updated_at": "2026-09-21T09:30:00Z"
}
PATCH /v1/saved-reports/{report_id}
Change a saved report
Its name, definition, sharing or schedule; only the fields sent change (unschedule
stops the e-mails). Only its owner (403 saved_report_not_yours). A person it is no longer
shared with loses it from their Home and its e-mails. Audited as saved_report.update.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | update_saved_report_v1_saved_reports__report_id__patch |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | report_id |
string (uuid) | yes | Saved report id (GET /v1/entities/{entity}/saved-reports). |
Request body
application/json (required): SavedReportUpdate
| Field | Type | Required | Description |
|---|---|---|---|
name |
string or null | A new name; null: kept. (min length 1; max length 120) | |
definition |
ReportDefinition or null |
What the report lists from now on; null: kept. | |
sharing |
ReportSharing or null |
Who may open it from now on; null: kept. | |
schedule |
ReportSchedule or null |
A schedule replaces the one there; see unschedule. |
|
unschedule |
boolean | Stop sending it. (default false) |
Example request
curl -X PATCH "$ERP_URL/v1/saved-reports/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Unpaid in Germany"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SavedReportOut |
| 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)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"name": "string",
"definition": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
},
"owner": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string"
},
"mine": true,
"sharing": {
"everyone": false,
"people": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"schedule": {
"every": "week",
"weekday": 0,
"day": 1,
"hour": 7,
"recipients": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"next_send_at": "2026-09-21T09:30:00Z",
"last_sent_at": "2026-09-21T09:30:00Z",
"last_send_problem": "string",
"pinned": true,
"path": "string",
"created_at": "2026-09-21T09:30:00Z",
"updated_at": "2026-09-21T09:30:00Z"
}
DELETE /v1/saved-reports/{report_id}
Delete a saved report
Only its owner; it leaves every Home it was pinned to. Audited as
saved_report.delete.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | delete_saved_report_v1_saved_reports__report_id__delete |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | report_id |
string (uuid) | yes | Saved report id (GET /v1/entities/{entity}/saved-reports). |
Example request
curl -X DELETE "$ERP_URL/v1/saved-reports/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | No content | |
| 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 |
GET /v1/saved-reports/{report_id}/csv
Download a saved report
Every row (or group) shown, as a CSV file a spreadsheet opens.
| Required scope | read (admin implies every scope) |
| Operation id | download_saved_report_v1_saved_reports__report_id__csv_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | report_id |
string (uuid) | yes | Saved report id (GET /v1/entities/{entity}/saved-reports). |
| query | language |
"de" | "en" | "fr" | "it" | "sv" |
The language of the labels: en (default), de, fr, it, sv. (default "en") |
Example request
curl "$ERP_URL/v1/saved-reports/3fa85f64-5717-4562-b3fc-2c963f66afa6/csv" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | The rows as CSV. | text/csv |
| 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 |
PUT /v1/saved-reports/{report_id}/pin
Pin to Home
Show the report on the caller's Home; pinning it again changes nothing.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | pin_saved_report_v1_saved_reports__report_id__pin_put |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | report_id |
string (uuid) | yes | Saved report id (GET /v1/entities/{entity}/saved-reports). |
Example request
curl -X PUT "$ERP_URL/v1/saved-reports/3fa85f64-5717-4562-b3fc-2c963f66afa6/pin" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SavedReportOut |
| 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)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"name": "string",
"definition": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
},
"owner": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string"
},
"mine": true,
"sharing": {
"everyone": false,
"people": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"schedule": {
"every": "week",
"weekday": 0,
"day": 1,
"hour": 7,
"recipients": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"next_send_at": "2026-09-21T09:30:00Z",
"last_sent_at": "2026-09-21T09:30:00Z",
"last_send_problem": "string",
"pinned": true,
"path": "string",
"created_at": "2026-09-21T09:30:00Z",
"updated_at": "2026-09-21T09:30:00Z"
}
DELETE /v1/saved-reports/{report_id}/pin
Unpin from Home
Take the report off the caller's Home.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | unpin_saved_report_v1_saved_reports__report_id__pin_delete |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | report_id |
string (uuid) | yes | Saved report id (GET /v1/entities/{entity}/saved-reports). |
Example request
curl -X DELETE "$ERP_URL/v1/saved-reports/3fa85f64-5717-4562-b3fc-2c963f66afa6/pin" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SavedReportOut |
| 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)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"name": "string",
"definition": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
},
"owner": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string"
},
"mine": true,
"sharing": {
"everyone": false,
"people": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"schedule": {
"every": "week",
"weekday": 0,
"day": 1,
"hour": 7,
"recipients": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
"next_send_at": "2026-09-21T09:30:00Z",
"last_sent_at": "2026-09-21T09:30:00Z",
"last_send_problem": "string",
"pinned": true,
"path": "string",
"created_at": "2026-09-21T09:30:00Z",
"updated_at": "2026-09-21T09:30:00Z"
}
GET /v1/saved-reports/{report_id}/result
Run a saved report
The report as of today, as the caller sees it.
| Required scope | read (admin implies every scope) |
| Operation id | run_saved_report_v1_saved_reports__report_id__result_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | report_id |
string (uuid) | yes | Saved report id (GET /v1/entities/{entity}/saved-reports). |
| query | language |
"de" | "en" | "fr" | "it" | "sv" |
The language of the labels: en (default), de, fr, it, sv. (default "en") |
Example request
curl "$ERP_URL/v1/saved-reports/3fa85f64-5717-4562-b3fc-2c963f66afa6/result" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ReportResult |
| 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)
{
"entity": "string",
"source": "bills",
"title": "string",
"as_of": "2026-09-21",
"company_currency": "string",
"columns": [
{
"name": "string",
"label": "string",
"type": "string",
"currency": "string"
}
],
"rows": [
{
"values": {},
"labels": {},
"path": "string"
}
],
"groups": [
{
"value": null,
"label": "string",
"currency": "string",
"count": 1,
"sums": {},
"drill": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
}
}
],
"group_by": {
"name": "string",
"label": "string",
"type": "string",
"currency": "string"
},
"sums": [
{
"name": "string",
"label": "string",
"type": "string",
"currency": "string"
}
],
"totals": [
{
"currency": "string",
"count": 1,
"sums": {}
}
],
"found": 1,
"truncated": true,
"definition": {
"filters": [
{
"field": "kind",
"op": "eq",
"value": "sales_invoice"
},
{
"field": "payment",
"op": "in",
"value": [
"open",
"overdue"
]
},
{
"field": "open_amount",
"op": "gt",
"value": "10000"
},
{
"field": "country",
"op": "eq",
"value": "DE"
}
],
"group_by": "sales_person",
"source": "sales_invoices",
"sums": [
"open_amount"
]
}
}
POST /v1/saved-reports/{report_id}/send
E-mail a report now
E-mail the report now to the recipients of its schedule (its owner alone without one),
each with what they see; the schedule stays as it is. Only its owner, at most once in five
minutes (409 saved_report_sent_recently).
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | send_saved_report_v1_saved_reports__report_id__send_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | report_id |
string (uuid) | yes | Saved report id (GET /v1/entities/{entity}/saved-reports). |
Example request
curl -X POST "$ERP_URL/v1/saved-reports/3fa85f64-5717-4562-b3fc-2c963f66afa6/send" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ReportSendOut |
| 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 |
| 429 | A per-organization limit was reached, e.g. messages sent per hour (rate_limited, with retry_after_seconds). |
ErrorEnvelope |
Example response (shape)
{
"sent": 1,
"skipped": 1,
"problem": "string"
}
Schemas
ReportColumn
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | The field, as in the definition. |
label |
string | yes | Its heading, in the language asked for. |
type |
string | yes | What it holds, as a source's field says (money, date …). |
currency |
string or null | yes | Of an amount: the field of its currency, or null for the company's. |
ReportDefinition
| Field | Type | Required | Description |
|---|---|---|---|
source |
"bills" | "customers" | "deals" | "journal_lines" | "leads" | "payments" | "products" | "purchase_orders" | "sales_invoices" | "subscriptions" | "vendors" |
yes | The kind of record the report lists (GET /v1/saved-report-sources lists each with its fields). |
columns |
array of string | The fields shown, in order; empty: the source's usual ones. (max items 12) | |
filters |
array of ReportFilter |
The conditions a row meets, all of them. (max items 16) | |
group_by |
string or null | A field to group the rows by: one line per value, with the count and the sums. Amounts in a record's own currency are grouped per currency as well. (max length 40) | |
sums |
array of string | The amounts or numbers added up for each group and in the totals; empty: the source's main amount. (max items 4) | |
sort |
array of ReportSort |
The order. (max items 3) | |
limit |
integer | Rows or groups shown, at most 500. (min 1; max 500; default 100) |
ReportFilter
| Field | Type | Required | Description |
|---|---|---|---|
field |
string | yes | A field of the source, e.g. open_amount. (max length 40) |
op |
"between" | "contains" | "empty" | "eq" | "gt" | "gte" | "in" | "lt" | "lte" | "ne" | "not_in" | "set" |
eq, ne, gt, gte, lt, lte, in and not_in (a list), contains (text), between (a list of two, both included), empty or set (no value). (default "eq") |
|
value |
any | What the field is compared with: a text, a number or amount as a string, true/false, a list for in, not_in and between. A date is 2026-10-01 or one counted from the company's today: today, today-30, today+7, start_of_week, start_of_month, end_of_month, start_of_quarter, end_of_quarter, start_of_year, end_of_year, start_of_last_month, end_of_last_month, start_of_last_quarter, end_of_last_quarter, start_of_last_year, end_of_last_year. A person is me (who reads the report), a name, an e-mail or an id. |
ReportGroup
| Field | Type | Required | Description |
|---|---|---|---|
value |
any | yes | The value grouped by; null for the rows without one. |
label |
string | yes | How the value reads. |
currency |
string or null | yes | The currency of the group's amounts, if any. |
count |
integer | yes | Rows in the group. |
sums |
map of string | yes | The sum of each field added up, by its name. |
drill |
ReportDefinition |
yes | The report of the group's rows: the same report narrowed to the value, not grouped. |
ReportOwner
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | The person's user id. |
name |
string | yes | The person's name, else their e-mail. |
ReportResult
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | The company's code. |
source |
"bills" | "customers" | "deals" | "journal_lines" | "leads" | "payments" | "products" | "purchase_orders" | "sales_invoices" | "subscriptions" | "vendors" |
yes | What the report lists. |
title |
string | yes | The source's name in the language asked for. |
as_of |
string (date) | yes | The company's today the report was run on. |
company_currency |
string | yes | The currency the company keeps its books in. |
columns |
array of ReportColumn |
yes | The columns of the rows, in order. |
rows |
array of ReportRow |
yes | The rows, when the report is not grouped. |
groups |
array of ReportGroup |
yes | The groups, when it is. |
group_by |
ReportColumn or null |
yes | The field grouped by; null: not grouped. |
sums |
array of ReportColumn |
yes | The fields the groups and totals add up. |
totals |
array of ReportTotal |
yes | Of every row found, per currency. |
found |
integer | yes | Rows found in all (or groups, when grouped). |
truncated |
boolean | yes | More were found than limit shows. |
definition |
ReportDefinition |
yes | The report as it was run, completed. |
ReportRow
| Field | Type | Required | Description |
|---|---|---|---|
values |
object | yes | The value of each column (amounts and numbers as strings, dates as ISO days); a list's value as stored, its words in labels. |
labels |
map of string | yes | The words of the list values of the row. |
path |
string or null | yes | Where the row opens in the app, e.g. /documents/…. |
ReportSchedule
| Field | Type | Required | Description |
|---|---|---|---|
every |
"day" | "month" | "week" |
How often it is sent: each day, week or month. (default "week") |
|
weekday |
integer | For week: 0 Monday (the default) to 6 Sunday. (min 0; max 6; default 0) |
|
day |
integer | For month: the day, 1 to 28. (min 1; max 28; default 1) |
|
hour |
integer | The hour in the company's time zone (default 7). (min 0; max 23; default 7) |
|
recipients |
array of string (uuid) | Who gets it (user ids): the owner and the people it is shared with; empty: the owner. (max items 50) |
ReportSendOut
| Field | Type | Required | Description |
|---|---|---|---|
sent |
integer | yes | E-mails sent. |
skipped |
integer | yes | Recipients not sent to (no mail server, a test copy). |
problem |
string or null | yes | Why nothing was sent, if so. |
ReportSharing
| Field | Type | Required | Description |
|---|---|---|---|
everyone |
boolean | Everyone in the organization may open the report. (default false) |
|
people |
array of string (uuid) | People of the organization the report is shared with (user ids). (max items 50) |
ReportSort
| Field | Type | Required | Description |
|---|---|---|---|
by |
string | yes | A field; in a grouped report also count or one of its sums. (max length 40) |
descending |
boolean | Largest or latest first. (default false) |
ReportSourceField
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | The field's name in a definition, e.g. open_amount. |
label |
string | yes | What the field is, in the language asked for. |
type |
"bool" | "country" | "date" | "enum" | "money" | "number" | "person" | "text" |
yes | What it holds: money an amount, enum one of values, person someone of the organization (me in a filter), country an ISO code. |
values |
array of string | yes | Of a list (enum): its values. |
value_labels |
map of string | yes | The words of each value. |
group |
boolean | yes | A report may group by it. |
default |
boolean | yes | Shown when a report names no columns. |
currency |
string or null | yes | Of an amount in the record's own currency: the field that holds it; null: the company's currency. |
ReportSourceOut
| Field | Type | Required | Description |
|---|---|---|---|
name |
"bills" | "customers" | "deals" | "journal_lines" | "leads" | "payments" | "products" | "purchase_orders" | "sales_invoices" | "subscriptions" | "vendors" |
yes | The source's name in a definition. |
label |
string | yes | What it lists, in the language asked for. |
fields |
array of ReportSourceField |
yes | What a report of it may show and filter. |
sum |
string or null | yes | The amount a grouped report adds up by default. |
ReportTotal
| Field | Type | Required | Description |
|---|---|---|---|
currency |
string or null | yes | Of the amounts in the records' own currency. |
count |
integer | yes | Rows found in this currency (or in all). |
sums |
map of string | yes | The sum of each field added up, by its name. |
SavedReportIn
| Field | Type | Required | Description |
|---|---|---|---|
definition |
ReportDefinition |
yes | What the report lists. |
name |
string or null | What the report is called; default: the source's name. (min length 1; max length 120) | |
sharing |
ReportSharing or null |
Default: only the owner. | |
schedule |
ReportSchedule or null |
Default: not sent. | |
pinned |
boolean | Pin it to the owner's Home at once. (default false) |
SavedReportOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | The report's id. |
entity |
string | yes | The company's code. |
name |
string | yes | What the report is called. |
definition |
ReportDefinition |
yes | What the report lists. |
owner |
ReportOwner |
yes | Who saved it: only they change it. |
mine |
boolean | yes | The person asking owns it (and may change it). |
sharing |
ReportSharing |
yes | Who else may open it. |
schedule |
ReportSchedule or null |
yes | When it is e-mailed; null: never. |
next_send_at |
string (date-time) or null | yes | When it is e-mailed next. |
last_sent_at |
string (date-time) or null | yes | When it was last e-mailed. |
last_send_problem |
string or null | yes | Why the last e-mail was not sent, if so. |
pinned |
boolean | yes | On the Home of the person asking. |
path |
string | yes | Where it opens in the app. |
created_at |
string (date-time) | yes | When it was saved. |
updated_at |
string (date-time) or null | yes | When it was last changed. |
SavedReportUpdate
| Field | Type | Required | Description |
|---|---|---|---|
name |
string or null | A new name; null: kept. (min length 1; max length 120) | |
definition |
ReportDefinition or null |
What the report lists from now on; null: kept. | |
sharing |
ReportSharing or null |
Who may open it from now on; null: kept. | |
schedule |
ReportSchedule or null |
A schedule replaces the one there; see unschedule. |
|
unschedule |
boolean | Stop sending it. (default false) |