Agents
Built-in agents (bill capture, reconciliation, close review, assistant, Q&A).
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
POST /v1/agents/ask |
Ask a question about the books | read |
|
POST /v1/agents/assistant |
Talk to the assistant | read |
|
POST /v1/agents/assistant/stream |
Talk to the assistant, with its progress | read |
|
POST /v1/agents/bill-capture |
Capture a vendor bill from a file | draft |
required |
POST /v1/agents/bill-capture/read |
Read a vendor bill without booking it | draft |
required |
POST /v1/agents/entities/{entity}/reconcile |
Propose bank reconciliations | draft |
required |
POST /v1/agents/form-suggestions |
Suggest what to enter in a form | read |
|
POST /v1/agents/periods/{period_id}/close-review |
Review a period before closing | draft |
required |
POST /v1/agents/ask
Ask a question about the books
Answer a question about the books with read-only tools; figures cite entries.
A language model looks the figures up in the ledger, subledgers and reports and answers in
prose. citations are the entry and document numbers the tools actually returned;
unverified_references are numbers in the answer that no tool returned and should be
checked. tool_calls is what the agent looked at. It cannot change anything. Answers 502
when the model fails and 503 when no language model is configured on the server.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | ask_books_v1_agents_ask_post |
Request body
application/json (required): AskRequest
| Field | Type | Required | Description |
|---|---|---|---|
question |
string | yes | The question in plain language, 3 to 4000 characters, e.g. What was the total of office expenses in September 2026?. (min length 3; max length 4000) |
entity |
string or null | Default entity for the question. |
Example request
curl -X POST "$ERP_URL/v1/agents/ask" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"question": "What was the total of office expenses (account 6500) in September 2026?"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | Answer |
| 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 |
| 503 | The agents are not configured on this server (agents_unavailable). |
ErrorEnvelope |
Example response (shape)
{
"answer": "string",
"citations": [
"string"
],
"unverified_references": [
"string"
],
"tool_calls": [
{
"name": "string",
"input": {},
"ok": true,
"summary": "string"
}
]
}
POST /v1/agents/assistant
Talk to the assistant
One turn of a conversation with the assistant: the answer to the person's last message.
A language model works in the books with the same tools an MCP client has, as an agent
acting for the caller: it looks figures up, answers with charts (charts; each value no tool
returned is listed in the chart's unverified), and with draft it may create and change
records and run the period-end steps, whose postings wait for approval where the policy says
so; with read only it reads. It asks before it changes anything and answers in language.
With period_id it closes that period with the person: close gives the checklist's state
after the turn and the open steps only a person can take, each with its place in the app; the
assistant never closes a period. The conversation is not stored: send what was said so far
with each message. Answers 404 for an unknown period or company, 502 when the model fails and
503 when no language model is configured.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | talk_to_the_assistant_v1_agents_assistant_post |
Request body
application/json (required): AssistantRequest
| Field | Type | Required | Description |
|---|---|---|---|
messages |
array of Message |
yes | The conversation so far, oldest first; the last message is the person's (at most 40, each up to 8000 characters, the last up to 4000). (min items 1; max items 40) |
entity |
string or null | Code of the company the person is working in, e.g. CH01; default: the organization's first. |
|
period_id |
string (uuid) or null | Id (uuid) of a period to close with the person: the assistant works through its close, and the answer lists the person's open steps (actions). |
|
language |
"de" | "en" | "fr" | "it" | "sv" |
The language the assistant answers in. (default "en") |
|
page |
string or null | The page of the app the person is on, e.g. /cash-forecast: the assistant is given the tools of that page directly, so it answers sooner. (max length 300; pattern ^/) |
Example request
curl -X POST "$ERP_URL/v1/agents/assistant" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"language": "en",
"messages": [
{
"role": "user",
"text": "Show me unearned revenue this year to date."
}
],
"page": "/analytics"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | AssistantReply |
| 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 |
| 503 | The agents are not configured on this server (agents_unavailable). |
ErrorEnvelope |
Example response (shape)
{
"reply": "string",
"charts": [
{
"title": "string",
"kind": "bar",
"categories": [
"string"
],
"series": [
null
],
"unit": "string",
"unverified": [
"string"
]
}
],
"cards": [
{
"kind": "string",
"title": "string",
"data": {}
}
],
"close": {
"period_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"period": "string",
"status": "string",
"ready": true,
"actions": [
null
]
},
"tool_calls": [
{
"name": "string",
"input": {},
"ok": true,
"summary": "string"
}
],
"model": "string"
}
POST /v1/agents/assistant/stream
Talk to the assistant, with its progress
POST /v1/agents/assistant, answered as it works: the person sees what the assistant does.
The same turn as POST /v1/agents/assistant, as application/x-ndjson: a step line for
each tool the assistant calls (its name and area, e.g. bank, vat, trends), a working
line every few seconds while the model thinks, and a last line done with the answer or
error with the status and error an ordinary request would have answered (502 when the
model fails, model_busy when it is overloaded, 503 when no model is configured). An unknown
period or company is a 404 before anything is streamed.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | talk_to_the_assistant_streaming_v1_agents_assistant_stream_post |
Request body
application/json (required): AssistantRequest
| Field | Type | Required | Description |
|---|---|---|---|
messages |
array of Message |
yes | The conversation so far, oldest first; the last message is the person's (at most 40, each up to 8000 characters, the last up to 4000). (min items 1; max items 40) |
entity |
string or null | Code of the company the person is working in, e.g. CH01; default: the organization's first. |
|
period_id |
string (uuid) or null | Id (uuid) of a period to close with the person: the assistant works through its close, and the answer lists the person's open steps (actions). |
|
language |
"de" | "en" | "fr" | "it" | "sv" |
The language the assistant answers in. (default "en") |
|
page |
string or null | The page of the app the person is on, e.g. /cash-forecast: the assistant is given the tools of that page directly, so it answers sooner. (max length 300; pattern ^/) |
Example request
curl -X POST "$ERP_URL/v1/agents/assistant/stream" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"language": "en",
"messages": [
{
"role": "user",
"text": "Show me unearned revenue this year to date."
}
],
"page": "/analytics"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | One JSON object per line: step for each tool the assistant calls (tool, area), working every few seconds while it thinks, then done with the answer (the fields of AssistantReply) or error (status, error). |
application/x-ndjson |
| 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 |
| 503 | The agents are not configured on this server (agents_unavailable). |
ErrorEnvelope |
POST /v1/agents/bill-capture
Capture a vendor bill from a file
Read a vendor invoice into a draft bill (VAT re-determined) queued for approval.
Send the file as multipart/form-data. The agent reads it with a language model, matches
or creates the vendor, determines the VAT again for every line and submits the bill as an
agent, so it always waits for a human approval (status pending_approval) with the
evidence attached; warnings lists what to check. XRechnung XML and PDFs with embedded
ZUGFeRD / Factur-X data are booked from the XML without a model call (source
einvoice). A file read before with POST /v1/agents/bill-capture/read is booked from that
reading, without asking the model again. A file that is not an invoice answers
not_an_invoice. Refused with 409 when
the same file was captured before, 422 for an unsupported or oversized file, 502 when the
model fails, and 503 when no language model is configured on the server (e-invoices need
none).
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | capture_bill_v1_agents_bill_capture_post |
Request body
multipart/form-data (required): Body_capture_bill_v1_agents_bill_capture_post
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code of the entity the bill is for, e.g. CH01. |
file |
string | yes | The vendor invoice: PDF, PNG, JPEG or WebP, or an e-invoice (XRechnung XML, or a ZUGFeRD / Factur-X PDF); at most 20 MB. |
vendor |
string or null | Code of the vendor, e.g. V-CLOUD, if you know it; otherwise the vendor is matched by VAT number, IBAN or name. |
|
create_vendor |
boolean | true (default): create the vendor when none matches (the approval policy may require a second person to activate it); false: answer needs_review instead. (default true) |
|
line_accounts |
string or null | Optional JSON array of reviewed account codes, one per captured line. Every account must be an allowed expense or asset account of this company. The stored extraction is kept unchanged; the reviewed choices are used for this bill. |
Example request
curl -X POST "$ERP_URL/v1/agents/bill-capture" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@statement.xml" \
-F "entity=CH01"
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | CaptureResult |
| 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 |
| 503 | The agents are not configured on this server (agents_unavailable). |
ErrorEnvelope |
Example response (shape)
{
"source": "model",
"status": "needs_review",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"vendor": {
"code": "string",
"name": "string",
"matched_by": "created",
"status": "string"
},
"total": "string",
"currency": "string",
"confidence": 1.5,
"warnings": [
"string"
],
"extracted": {
"is_invoice": true,
"vendor_name": "string",
"vendor_vat_number": "string",
"vendor_iban": "string",
"vendor_country": "string",
"vendor_street": "string",
"vendor_postal_code": "string",
"vendor_town": "string",
"invoice_number": "string",
"order_reference": "string",
"invoice_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"lines": [
{
"description": null,
"quantity": null,
"unit_price": null,
"vat_rate_percent": null,
"vat_amount": null,
"supply_type": null,
"account": null,
"account_reason": null
}
],
"net_total": 1.5,
"vat_total": 1.5,
"gross_total": 1.5,
"confidence": 1.5,
"evidence": [
{
"field": null,
"quote": null
}
],
"notes": "string"
},
"einvoice": {
"status": "draft",
"valid": true,
"label": "string",
"profile": "string",
"syntax": "string",
"container": "string",
"violations": [
null
],
"warnings": [
"string"
],
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sha256": "string",
"bill_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_kind": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"vendor": null,
"total": "string",
"currency": "string",
"invoice": {
"syntax": null,
"specification_id": null,
"business_process": null,
"number": null,
"issue_date": null,
"type_code": null,
"currency": null,
"tax_currency": null,
"tax_point_date": null,
"tax_point_code": null,
"due_date": null,
"buyer_reference": null,
"project_reference": null,
"contract_reference": null,
"order_reference": null,
"sales_order_reference": null,
"receiving_advice": null,
"despatch_advice": null,
"tender_reference": null,
"invoiced_object": null,
"accounting_reference": null,
"payment_terms": null,
"notes": null,
"preceding_invoices": null,
"seller": null,
"buyer": null,
"payee": null,
"tax_representative": null,
"delivery_date": null,
"period_start": null,
"period_end": null,
"deliver_to": null,
"payment_means": null,
"remittance_information": null,
"creditor_reference_id": null,
"allowances_charges": null,
"totals": null,
"vat_breakdown": null,
"lines": null,
"attachments": null
}
}
}
POST /v1/agents/bill-capture/read
Read a vendor bill without booking it
Read a vendor invoice and say what it is, before anything is booked.
The first step of the bill wizard. An e-invoice is read from its XML (and checked against
the EN 16931 and XRechnung rules, einvoice); any other PDF or image is read by the
language model, and the reading is stored with the file (document_id). The answer names
the known vendor the bill matches (vendor) or, for an unknown one, its master data as
printed (new_vendor, to create it with POST /v1/entities/{entity}/parties), and whether
a bill can be made from it (bookable). Book it with POST /v1/agents/bill-capture and the
same file: the stored reading is used, the model is not asked again. Nothing is booked and
no vendor is created here. When no language model answers in time, nothing is read or stored
and the answer is status waiting: send the file again after retry_after_seconds.
Refused with 409 when the file already has a bill, 422 for an unsupported or oversized file,
502 when the model fails otherwise and 503 when no language model is configured (e-invoices
need none).
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | read_bill_v1_agents_bill_capture_read_post |
Request body
multipart/form-data (required): Body_read_bill_v1_agents_bill_capture_read_post
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code of the entity the bill is for, e.g. CH01. |
file |
string | yes | The vendor invoice: PDF, PNG, JPEG or WebP, or an e-invoice (XRechnung XML, or a ZUGFeRD / Factur-X PDF); at most 20 MB. |
Example request
curl -X POST "$ERP_URL/v1/agents/bill-capture/read" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@statement.xml" \
-F "entity=CH01"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | BillReading |
| 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 |
| 503 | The agents are not configured on this server (agents_unavailable). |
ErrorEnvelope |
Example response (shape)
{
"status": "read",
"retry_after_seconds": 1,
"source": "einvoice",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sha256": "string",
"extracted": {
"is_invoice": true,
"vendor_name": "string",
"vendor_vat_number": "string",
"vendor_iban": "string",
"vendor_country": "string",
"vendor_street": "string",
"vendor_postal_code": "string",
"vendor_town": "string",
"invoice_number": "string",
"order_reference": "string",
"invoice_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"lines": [
null
],
"net_total": 1.5,
"vat_total": 1.5,
"gross_total": 1.5,
"confidence": 1.5,
"evidence": [
null
],
"notes": "string"
},
"vendor": {
"code": "string",
"name": "string",
"matched_by": "created",
"status": "string"
},
"new_vendor": {
"name": "string",
"country": "string",
"vat_number": "string",
"iban": "string",
"currency": "string",
"street": "string",
"postal_code": "string",
"town": "string",
"email": "string"
},
"bookable": true,
"warnings": [
"string"
],
"einvoice": {
"valid": true,
"label": "string",
"profile": "string",
"syntax": "string",
"container": "string",
"attachment": "string",
"violations": [
null
],
"invoice": {
"syntax": null,
"specification_id": null,
"business_process": null,
"number": null,
"issue_date": null,
"type_code": null,
"currency": null,
"tax_currency": null,
"tax_point_date": null,
"tax_point_code": null,
"due_date": null,
"buyer_reference": null,
"project_reference": null,
"contract_reference": null,
"order_reference": null,
"sales_order_reference": null,
"receiving_advice": null,
"despatch_advice": null,
"tender_reference": null,
"invoiced_object": null,
"accounting_reference": null,
"payment_terms": null,
"notes": null,
"preceding_invoices": null,
"seller": null,
"buyer": null,
"payee": null,
"tax_representative": null,
"delivery_date": null,
"period_start": null,
"period_end": null,
"deliver_to": null,
"payment_means": null,
"remittance_information": null,
"creditor_reference_id": null,
"allowances_charges": null,
"totals": null,
"vat_breakdown": null,
"lines": null,
"attachments": null
}
}
}
POST /v1/agents/entities/{entity}/reconcile
Propose bank reconciliations
Propose matches or postings for the entity's unmatched bank lines, oldest first.
For each line a language model, shown the line, its scored candidate invoices and bills and
the postable accounts, proposes to match documents, post to an account, or leave the
line. Every proposal is checked in code before it is used. With apply true, valid
proposals at or above min_confidence are carried out as the reconciliation agent, which
cannot post, so each one waits for a human approval (status pending_approval); the
others come back as proposed, rejected or left. Answers 502 when the model fails and
503 when no language model is configured on the server.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | reconcile_bank_v1_agents_entities__entity__reconcile_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
Request body
application/json (required): ReconcileRequest
| Field | Type | Required | Description |
|---|---|---|---|
apply |
boolean | Submit valid proposals for approval. (default true) |
|
min_confidence |
number | Proposals the model is less sure of than this (0 to 1) are returned as proposed and not applied; default 0.6. (min 0; max 1; default 0.6) |
|
limit |
integer | How many unmatched bank lines to review, oldest first, 1 to 40; default 25. (min 1; max 40; default 25) |
Example request
curl -X POST "$ERP_URL/v1/agents/entities/CH01/reconcile" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"apply": true,
"limit": 20,
"min_confidence": 0.7
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ReconcileResult |
| 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 |
| 503 | The agents are not configured on this server (agents_unavailable). |
ErrorEnvelope |
Example response (shape)
{
"entity": "string",
"reviewed": 1,
"outcomes": [
{
"line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"booking_date": "2026-09-21",
"amount": "string",
"currency": "string",
"counterparty": "string",
"action": "leave",
"status": "left",
"detail": "string",
"confidence": 1.5,
"reasons": [
"string"
],
"allocations": [
null
],
"account": "string",
"document_ids": {},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
]
}
POST /v1/agents/form-suggestions
Suggest what to enter in a form
Propose values for a form's fields from what they are for and the company.
The screen sends what the form creates and each field with its explanation, options and what
was entered so far; a language model proposes a value and a one-sentence reason for each field
it can, in the language asked. Nothing is saved: the screen fills the form and the person
checks it before saving. Facts only the person knows (names, addresses, amounts, bank
details) are left out, a choice outside its options or a number that is not one is dropped.
When no model answers in time the answer is status: "waiting" with retry_after_seconds;
503 when no language model is configured on the server.
| Required scope | read (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | suggest_form_values_v1_agents_form_suggestions_post |
Request body
application/json (required): FormSuggestionRequest
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code of the company the form is for, e.g. CH01. |
form |
string | yes | What the form creates, in a sentence, e.g. A support queue. (min length 3; max length 300) |
language |
"de" | "en" | "fr" | "it" | "sv" |
The language the suggested texts are written in. (default "en") |
|
fields |
array of FormFieldIn |
yes | The form's fields, in screen order. (min items 1; max items 40) |
Example request
curl -X POST "$ERP_URL/v1/agents/form-suggestions" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"fields": [
{
"help": "What the team calls it",
"label": "Name",
"name": "name"
},
{
"help": "Working hours to the first reply to an urgent case",
"kind": "number",
"label": "Urgent: first reply",
"name": "urgent_first"
}
],
"form": "A support queue: where customer cases land",
"language": "en"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | FormSuggestions |
| 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 |
| 503 | The agents are not configured on this server (agents_unavailable). |
ErrorEnvelope |
Example response (shape)
{
"status": "ready",
"suggestions": [
{
"field": "string",
"value": "string",
"reason": "string"
}
],
"retry_after_seconds": 1
}
POST /v1/agents/periods/{period_id}/close-review
Review a period before closing
Prepare a period for closing and write the close memo. It never locks the period.
A language model works through the close checklist, the flux analysis, the unusual entries
and the subledger ties with read-only tools, may run depreciation and FX revaluation when
run_adjustments is true (queued for approval, not posted), records issues (blocker,
warning or info) and writes the memo in Markdown, which is also stored as a document
(memo_document_id). ready is true only when the checklist is ready and no issue is a
blocker. A human closes the period (POST /v1/periods/{period_id}/close). Answers 404 for
an unknown period, 502 when the model fails or answers with no memo (nothing is stored then)
and 503 when no language model is configured.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | review_close_v1_agents_periods__period_id__close_review_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | period_id |
string (uuid) | yes | Fiscal period id (GET /v1/entities/{entity}/periods). |
Request body
application/json (required): CloseReviewRequest
| Field | Type | Required | Description |
|---|---|---|---|
materiality |
decimal string | Threshold in the entity's functional currency (decimal string) above which a variance or amount is flagged; default 1000. (min 0; default "1000") |
|
run_adjustments |
boolean | Allow the agent to run depreciation and FX revaluation. (default true) |
Example request
curl -X POST "$ERP_URL/v1/agents/periods/3fa85f64-5717-4562-b3fc-2c963f66afa6/close-review" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"materiality": "1000",
"run_adjustments": false
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | CloseReview |
| 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 |
| 503 | The agents are not configured on this server (agents_unavailable). |
ErrorEnvelope |
Example response (shape)
{
"period_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"period": "string",
"entity": "string",
"memo": "string",
"issues": [
{
"severity": "blocker",
"title": "string",
"detail": "string",
"references": [
"string"
]
}
],
"memo_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"ready": true,
"checklist": {
"entity": "string",
"period": "string",
"period_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"period_status": "string",
"ready": true,
"checks": [
{
"id": null,
"title": null,
"status": null,
"detail": null,
"code": null,
"details": null
}
]
},
"tool_calls": [
{
"name": "string",
"input": {},
"ok": true,
"summary": "string"
}
]
}
Schemas
AllocationInput-Output
| Field | Type | Required | Description |
|---|---|---|---|
document |
string | yes | Document id or number. |
amount |
string | yes | Paid amount applied to the document, in the payment currency. |
document_amount |
string or null | What the allocation settles on the document, in the document's currency (cash discount not included). Only for a document in another currency than the payment, e.g. a CHF receipt for a EUR invoice: give the EUR amount it settles, such as the open amount when the customer meant to pay the invoice in full. Default: amount converted at the payment date's rate. The difference between what the document carried and what the bank received is booked as realised FX gain or loss. |
|
discount |
string | Cash discount (Skonto) taken on top of the amount, in the document's currency: the document is settled by its amount + discount. Only on invoices and bills with cash discount terms, until their cash discount date. (default "0.00") |
Answer
| Field | Type | Required | Description |
|---|---|---|---|
answer |
string | yes | The answer in prose. |
citations |
array of string | yes | Entry and document numbers the tools returned. |
unverified_references |
array of string | yes | Numbers in the answer that no tool returned; treat with care. |
tool_calls |
array of ToolCall |
yes | The read-only tools the agent called to find the figures. |
AskRequest
| Field | Type | Required | Description |
|---|---|---|---|
question |
string | yes | The question in plain language, 3 to 4000 characters, e.g. What was the total of office expenses in September 2026?. (min length 3; max length 4000) |
entity |
string or null | Default entity for the question. |
AssistantCard
| Field | Type | Required | Description |
|---|---|---|---|
kind |
string | yes | What the card is, by the feature module that made it, e.g. report (a list or report the assistant built, which one click saves). |
title |
string | yes | The card's title, in the person's language. (max length 200) |
data |
object | yes | What the module's screen shows, in the module's own shape (for report: entity and definition, the body of POST /v1/entities/{entity}/saved-reports/run). |
AssistantReply
| Field | Type | Required | Description |
|---|---|---|---|
reply |
string | yes | The assistant's answer, in the language asked for. |
charts |
array of Chart |
yes | Charts shown beside the answer. |
cards |
array of AssistantCard |
What the feature modules show beside the answer, e.g. a list the assistant built (report), which one click saves. |
|
close |
PeriodState or null |
yes | With period_id: the period's close after this turn. |
tool_calls |
array of ToolCall |
yes | What the assistant looked at and did. |
model |
string | yes | The model that answered. |
AssistantRequest
| Field | Type | Required | Description |
|---|---|---|---|
messages |
array of Message |
yes | The conversation so far, oldest first; the last message is the person's (at most 40, each up to 8000 characters, the last up to 4000). (min items 1; max items 40) |
entity |
string or null | Code of the company the person is working in, e.g. CH01; default: the organization's first. |
|
period_id |
string (uuid) or null | Id (uuid) of a period to close with the person: the assistant works through its close, and the answer lists the person's open steps (actions). |
|
language |
"de" | "en" | "fr" | "it" | "sv" |
The language the assistant answers in. (default "en") |
|
page |
string or null | The page of the app the person is on, e.g. /cash-forecast: the assistant is given the tools of that page directly, so it answers sooner. (max length 300; pattern ^/) |
BillReading
What a vendor bill says, before anything is booked.
| Field | Type | Required | Description |
|---|---|---|---|
status |
"read" | "waiting" |
read: the file was read (extracted); waiting: no language model answered in time, so nothing was read or stored: send the same file again after retry_after_seconds (the bill wizard does it by itself). (default "read") |
|
retry_after_seconds |
integer or null | For waiting: when to send the file again; else null. |
|
source |
"einvoice" | "model" |
yes | einvoice: read from structured XML, no model call; model: read by the language model. |
document_id |
string (uuid) or null | yes | Id of the stored file with the model's reading; null for an e-invoice (stored when its bill is created). |
sha256 |
string | yes | SHA-256 of the file. |
extracted |
ExtractedBill or null |
yes | What was read from the file; null while waiting. |
vendor |
VendorMatch or null |
yes | The known vendor the bill matches (by VAT number, IBAN or name); null for an unknown one. |
new_vendor |
VendorProposal or null |
yes | For an unknown vendor: its master data as the bill gives it. |
bookable |
boolean | yes | true when a bill can be created from it: an invoice with a date and lines (and, for an e-invoice, no fatal rule broken). |
warnings |
array of string | yes | What to look at before creating the bill. |
einvoice |
CheckResult or null |
For an e-invoice: its rule check; else null. |
Body_capture_bill_v1_agents_bill_capture_post
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code of the entity the bill is for, e.g. CH01. |
file |
string | yes | The vendor invoice: PDF, PNG, JPEG or WebP, or an e-invoice (XRechnung XML, or a ZUGFeRD / Factur-X PDF); at most 20 MB. |
vendor |
string or null | Code of the vendor, e.g. V-CLOUD, if you know it; otherwise the vendor is matched by VAT number, IBAN or name. |
|
create_vendor |
boolean | true (default): create the vendor when none matches (the approval policy may require a second person to activate it); false: answer needs_review instead. (default true) |
|
line_accounts |
string or null | Optional JSON array of reviewed account codes, one per captured line. Every account must be an allowed expense or asset account of this company. The stored extraction is kept unchanged; the reviewed choices are used for this bill. |
Body_read_bill_v1_agents_bill_capture_read_post
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code of the entity the bill is for, e.g. CH01. |
file |
string | yes | The vendor invoice: PDF, PNG, JPEG or WebP, or an e-invoice (XRechnung XML, or a ZUGFeRD / Factur-X PDF); at most 20 MB. |
CaptureResult
| Field | Type | Required | Description |
|---|---|---|---|
source |
"einvoice" | "model" |
einvoice: booked from structured XML, no model call. (default "model") |
|
status |
"needs_review" | "not_an_invoice" | "pending_approval" | "posted" |
yes | pending_approval (bill drafted, waits for a person), posted, not_an_invoice or needs_review (nothing booked). |
document_id |
string (uuid) | yes | Id (uuid) of the stored source file. |
bill_id |
string (uuid) or null | Id (uuid) of the draft bill; null when none was made. | |
approval_id |
string (uuid) or null | Id (uuid) of the approval request; null when none was made. | |
vendor |
VendorMatch or null |
The vendor the bill was assigned to; null when none. | |
total |
string or null | Gross total of the bill in its currency; null when none. | |
currency |
string or null | Currency of the bill, ISO 4217; null when none. | |
confidence |
number | yes | 0 to 1: the model's confidence in the extraction. |
warnings |
array of string | yes | What to check before approving, e.g. totals that do not tie. |
extracted |
ExtractedBill |
yes | What the model read from the file. |
einvoice |
EInvoiceResult or null |
The e-invoice check result, for einvoice captures; else null. |
Chart
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | yes | The chart's title, in the person's language. (max length 200) |
kind |
"bar" | "line" |
yes | line for a balance or a value over time, bar for amounts per period or a comparison. |
categories |
array of string | yes | The labels along the axis, e.g. months 2026-01 .. 2026-10. (min items 1; max items 60) |
series |
array of ChartSeries |
yes | The lines or bars, one per series. (min items 1; max items 8) |
unit |
string or null | A currency code (CHF) for amounts, % for percentages, days, or null. (max length 8) |
|
unverified |
array of string | Values of the chart that no tool returned in this turn; to be checked. |
ChartSeries
| Field | Type | Required | Description |
|---|---|---|---|
label |
string | yes | What the series is, e.g. Unearned revenue. (max length 120) |
values |
array of string or null | yes | One value per category, exactly as the tools returned it; null for none. |
CloseAction
| Field | Type | Required | Description |
|---|---|---|---|
check |
string | yes | The close check the step belongs to (approvals), vat_return, bank_sign_off, or close for closing the period. |
title |
string | yes | What the person does, in English, e.g. Approve or reject what waits for approval. |
ref |
string or null | yes | What the step is about where a check has several: the VAT period (2026-Q3) of a VAT return, the account code of a bank account. |
path |
string | yes | Where in the app the person does it, e.g. /approvals. |
CloseReview
| Field | Type | Required | Description |
|---|---|---|---|
period_id |
string (uuid) | yes | Id (uuid) of the period. |
period |
string | yes | Label of the period, e.g. 2026-09. |
entity |
string | yes | Code of the entity. |
memo |
string | yes | The close memo in Markdown: summary, recommendation, results and actions, as the model wrote it. The stored document (memo_document_id) starts with a line before it: that a language model wrote it (which one) and the checklist's state. |
issues |
array of Issue |
yes | The findings the agent recorded. |
memo_document_id |
string (uuid) | yes | Id (uuid) of the stored memo document. |
ready |
boolean | yes | True when the checklist is ready and no issue is a blocker. |
checklist |
Checklist |
yes | The close checklist after the review. |
tool_calls |
array of ToolCall |
yes | What the agent looked at and did. |
CloseReviewRequest
| Field | Type | Required | Description |
|---|---|---|---|
materiality |
decimal string | Threshold in the entity's functional currency (decimal string) above which a variance or amount is flagged; default 1000. (min 0; default "1000") |
|
run_adjustments |
boolean | Allow the agent to run depreciation and FX revaluation. (default true) |
Evidence
| Field | Type | Required | Description |
|---|---|---|---|
field |
string | yes | Which extracted field this supports, e.g. 'invoice_number'. |
quote |
string | yes | Verbatim text from the document. |
ExtractedBill
| Field | Type | Required | Description |
|---|---|---|---|
is_invoice |
boolean | yes | False if the file is not a vendor invoice. |
vendor_name |
string or null | Vendor name as printed. | |
vendor_vat_number |
string or null | Vendor VAT number as printed. | |
vendor_iban |
string or null | Vendor IBAN as printed. | |
vendor_country |
string or null | ISO 3166 alpha-2 code. | |
vendor_street |
string or null | Vendor street and building number as printed. | |
vendor_postal_code |
string or null | Vendor postal code as printed. | |
vendor_town |
string or null | Vendor town as printed. | |
invoice_number |
string or null | The vendor's invoice number. | |
order_reference |
string or null | Our purchase order number as quoted on the invoice (PO, order no., Bestellnummer, BT-13), copied exactly; null if none. | |
invoice_date |
string (date) or null | Invoice date (ISO 8601). | |
due_date |
string (date) or null | Due date (ISO 8601), if printed. | |
currency |
string or null | ISO 4217 code. | |
lines |
array of ExtractedLine |
The invoice lines the model read. | |
net_total |
number or null | Net total as printed, a number. | |
vat_total |
number or null | VAT total as printed, a number. | |
gross_total |
number or null | Gross total as printed, a number. | |
confidence |
number | yes | 0 to 1: how sure you are of the extraction overall. |
evidence |
array of Evidence |
Quotes from the document that support fields of the extraction. | |
notes |
string or null | Anything a reviewer should know. |
ExtractedLine
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | yes | Line text as printed. |
quantity |
number | Quantity, a number. (default 1) |
|
unit_price |
number | yes | Net unit price, excluding VAT. |
vat_rate_percent |
number or null | VAT rate printed, e.g. 8.1 | |
vat_amount |
number or null | VAT printed for this line. | |
supply_type |
"electronic_services" | "goods" | "services" |
goods, services or electronic_services. (default "services") |
|
account |
string or null | Suggested account code from the chart provided. | |
account_reason |
string or null | Why that account; null if none. |
FieldSuggestion
| Field | Type | Required | Description |
|---|---|---|---|
field |
string | yes | The field's key, one of the request's. |
value |
string | yes | The value proposed, as the field takes it. |
reason |
string | yes | Why, in one sentence. |
FormFieldIn
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | The field's key in the form, e.g. name. (max length 80) |
label |
string | yes | The field's label on the screen. (max length 120) |
help |
string or null | The screen's explanation of the field. (max length 600) | |
kind |
"boolean" | "choice" | "number" | "text" |
What the field takes. (default "text") |
|
options |
array of string or null | The values a choice field allows. (max items 60) |
|
value |
string or null | What the person entered so far, if anything. (max length 2000) |
FormSuggestionRequest
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code of the company the form is for, e.g. CH01. |
form |
string | yes | What the form creates, in a sentence, e.g. A support queue. (min length 3; max length 300) |
language |
"de" | "en" | "fr" | "it" | "sv" |
The language the suggested texts are written in. (default "en") |
|
fields |
array of FormFieldIn |
yes | The form's fields, in screen order. (min items 1; max items 40) |
FormSuggestions
| Field | Type | Required | Description |
|---|---|---|---|
status |
"ready" | "waiting" |
yes | waiting: no model answered in time; ask again after retry_after_seconds. Nothing was suggested. |
suggestions |
array of FieldSuggestion |
yes | A value per field the model could propose; fields it left out are absent. |
retry_after_seconds |
integer or null | With waiting: when asking again may succeed. |
Issue
| Field | Type | Required | Description |
|---|---|---|---|
severity |
"blocker" | "info" | "warning" |
yes | blocker (do not lock the period), warning or info. |
title |
string | yes | A short title. |
detail |
string | yes | What was found. |
references |
array of string | Entry numbers, document numbers or account codes. |
Message
| Field | Type | Required | Description |
|---|---|---|---|
role |
"assistant" | "user" |
user (the default) for what the person wrote, assistant for an earlier answer. (default "user") |
|
text |
string | yes | What was said. (min length 1; max length 8000) |
Outcome
| Field | Type | Required | Description |
|---|---|---|---|
line_id |
string (uuid) | yes | Id (uuid) of the bank line. |
booking_date |
string (date) | yes | Booking date of the line (ISO 8601). |
amount |
string | yes | Signed amount of the bank line in its currency: positive money in, negative money out. |
currency |
string | yes | ISO 4217 currency of the bank line. |
counterparty |
string or null | yes | Counterparty name on the line; null if none. |
action |
"leave" | "match" | "post" |
yes | What the model proposed: match, post or leave. |
status |
"left" | "pending_approval" | "posted" | "proposed" | "rejected" |
yes | pending_approval (applied, waits for a person), posted, proposed (valid but not applied), rejected (failed validation) or left. |
detail |
string | yes | A sentence on what happened or why not. |
confidence |
number | yes | 0 to 1: how sure the model was. |
reasons |
array of string | yes | The model's reasons. |
allocations |
array of AllocationInput-Output |
For a match: the documents and amounts settled. | |
account |
string or null | For a post: the counter account code; null otherwise. | |
document_ids |
map of string (uuid) | For a match: the id of each document in allocations, by its number. |
|
approval_id |
string (uuid) or null | Id (uuid) of the approval request when one was created; else null. |
PeriodState
| Field | Type | Required | Description |
|---|---|---|---|
period_id |
string (uuid) | yes | Id (uuid) of the period. |
period |
string | yes | Label of the period, e.g. 2026-09. |
status |
string | yes | open or closed, after this turn. |
ready |
boolean | yes | True when the close checklist passes, after this turn. |
actions |
array of CloseAction |
yes | The open steps that only a person can take, each with where to take it. |
ReconcileRequest
| Field | Type | Required | Description |
|---|---|---|---|
apply |
boolean | Submit valid proposals for approval. (default true) |
|
min_confidence |
number | Proposals the model is less sure of than this (0 to 1) are returned as proposed and not applied; default 0.6. (min 0; max 1; default 0.6) |
|
limit |
integer | How many unmatched bank lines to review, oldest first, 1 to 40; default 25. (min 1; max 40; default 25) |
ReconcileResult
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code of the entity. |
reviewed |
integer | yes | How many unmatched bank lines were reviewed. |
outcomes |
array of Outcome |
yes | One outcome per reviewed line. |
ToolCall
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | The tool the agent called. |
input |
object | yes | The arguments it passed. |
ok |
boolean | yes | False when the tool failed. |
summary |
string | yes | A short account of the result. |
VendorMatch
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes | Vendor code, e.g. V-CLOUD. |
name |
string | yes | Vendor name. |
matched_by |
"created" | "given" | "iban" | "name" | "vat_number" |
yes | How the vendor was found: vat_number, iban, name, created or given. |
status |
string or null | The vendor's status: active, or draft until a second person activates it (a bill of a draft vendor stays a draft). |
VendorProposal
The vendor as the bill describes it, for a person to create (all as printed).
| Field | Type | Required | Description |
|---|---|---|---|
name |
string or null | yes | Name as printed. |
country |
string or null | yes | ISO 3166-1 alpha-2 country, if it could be read. |
vat_number |
string or null | yes | VAT number as printed. |
iban |
string or null | yes | IBAN, only when it passes its check digits. |
currency |
string | yes | The bill's currency, else the entity's. |
street |
string or null | Street as printed. | |
postal_code |
string or null | Postal code as printed. | |
town |
string or null | Town as printed. | |
email |
string or null | E-mail address, from an e-invoice. |