StabisoDocs

API reference

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.
Stabiso, the Enterprise System of Record · generated from the code and its guides · release 6f03a5e846b3