Documents
How documents move through their state machine — transitions, states and references.
Documents (Belege) — offers, orders, delivery notes, invoices, credit notes — are the ERP's central business objects. They are also the object most integrations get wrong, because a document is not a record you edit freely. It is a state machine.
Everything happens through a transition
There is no DELETE /erp/documents/{id}, and no endpoint that sets a state directly. Creating, editing, transferring, cancelling and deleting a document are all transitions:
POST /erp/documents create — with a transitionId
PUT /erp/documents/{id}/states change state — with a transitionId
POST /erp/documents/{id}/types transfer into a follow-up documentA transition is more than "from state A to state B". It is identified by the document category, an optional source category (for takeovers), the transition, the source state (absent = creation) and the target state.
transitionId is derived from enum ordinals and is not a stable contract.
Never hard-code it, never store it long-term. Fetch it at runtime, per request. This is the single most common cause of an integration that works today and breaks after an update.
Finding the transition you need
Three questions, three endpoints:
Which documents may I create?
GET /erp/documents/types?qualifiers=SALE&languageCode=de
What can I do with this document right now?
GET /erp/documents/{id}/states
Which follow-up documents can I create from it?
GET /erp/documents/{id}/types
Each entry carries the transitionId you then send, plus typeId, typeLabel, targetState and transitionKey. Of those, transitionKey (the transition's name) is the only human-stable identifier — match on that if you need to recognise a specific transition, and take the transitionId from the same response.
The qualifier narrows the document world; there are only eight, so they are worth knowing: SALE, PURCHASE, COMMISSION, POINT_OF_SALE, SALES_CONTRACT, PURCHASE_CONTRACT, FABRICATION, RMA.
Categories and transitions, by contrast, number in the dozens and hundreds. Their names follow a grammar — categories are prefixed by counterparty (CUSTOMER_ORDER, SUPPLIER_ORDER, POS_*, FABRICATION_*), transitions are either lifecycle verbs (CREATE, BEGIN_EDITING, DELETE, PUBLISH, COPY) or takeovers in X_TO_Y form (OFFER_TO_ORDER, ORDER_TO_DELIVERY, DELIVERY_TO_INVOICE) — but do not work from a list. The valid combinations depend on your installation's configuration; enumerate them.
/states contains a collective ACCEPT entry with transitionId: 0. Don't fire it — a document becomes accepted as a side effect of being taken over into a follow-up document, not by a direct call.
Creating a document
Only transitionId is required:
Whether an account is required depends on the category. Ids travel as strings throughout.
Two things to get right in the order you do them:
Header fields such as the document date, default storage or report group are set with
PATCH /erp/documents/{id}— before you add lines.POS documents need
posRegisterIdandcashDrawerIdflat indocumentat creation time, not nested.
Leave the document number out. It is generated automatically (see below).
What you may change, and when
Every state carries a classification, and that classification — not the state name — decides what is allowed:
EDITABLE
Full edit allowed
INTERNAL_EDITABLE
Only the backend may change it (a workflow is running)
PARTIALLY_TRANSFERRED / TRANSFERRED
Partly or fully taken over into a follow-up
DELETED
Being deleted
A full-body update requires EDITABLE. On a non-editable document, PATCH still works — but only for a small whitelist of fields; anything else is rejected. A document in a workflow state, or deleted, is refused outright.
HISTORICAL_DATA is a dead end by design. Imported legacy documents get this state and have no transitions in or out. Any write is refused. If you need to process such data, do it before import.
Writes participate in optimistic locking. The x-get-leasable-lock, x-use-leasable-locks, x-refresh-leasable-locks and x-release-leasable-locks request headers, and x-active-leasable-locks in the response, are part of the contract — respect them when several clients or users touch the same document.
Lines
Note the doubled /document segment — a frequent 404.
Every line write returns the whole document, not the line.
On the document, the collection is called
lines.PUT …/line/{lineId}ignores a changed position — it forces the stored one. Use…/move/{position}(1-based) to reorder.
Document texts
Two positions: HEADER_TEXT and FOOTER_TEXT. A text either carries free content or references a text template, and transferableIntoSubsequentDocuments controls whether it survives a takeover.
Texts can be sent at creation, per line, or on the document header via PUT /erp/documents.
Two traps on the header update:
When reading, note that deleted texts are included in the response — filter them out.
How documents reference each other
The link lives on the lines, not on the document. A line carries sourceLine pointing at the line it came from; the document-level chain is derived from that.
To show a document's history, use GET /erp/documents/{id}/types/history — a tree of predecessors and successors with number, date, type, state and totals.
Partial delivery
Three line fields express it:
quantity
The line's quantity
quantityCommitted
How much has been passed to follow-up documents — can exceed quantity
complete
Whether the line is finally settled
The predecessor's state is then recomputed from those, not set by you: fully taken over → ACCEPTED, partly → PARTIALLY_ACCEPTED, otherwise back to SAVED. While a successor is being edited, the predecessor may show "successor is being edited" instead.
To offer a partial takeover, ask for the open quantities — for example GET /erp/documents/{id}/lines-to-transfer-to-customer-delivery — then send the chosen sourceLineId + quantity pairs in document.lines of the takeover call. That is the only way a reduced (or negative) quantity travels along.
A worked example: order → delivery note
The ORDER_TO_DELIVERY transition accepts a CUSTOMER_ORDER in state SAVED, PARTIALLY_ACCEPTED or PICKING_FINISHED, and produces a delivery note in SAVED with its lines linked back to the order and a fresh document number. Yes — the delivery note is created with reference to the order; that reference is what later drives the order's own state.
Document numbers
They are generated automatically. Each document type has a sequence configuration — typically fully automatic, with a length, a pad character and a prefix expression (for example the short year).
Consequences:
Don't send
numberon create; it is not part of the create body.externalNumberis the field for your reference.A blank number is rejected on save (except for historical imports).
Deleting a document returns its number to the pool, so numbers are not strictly monotonic. Don't derive ordering or counts from them.
Sequence configuration and next/last values can be inspected under /cmn/sequencer.
Deleting
Deletion is the DELETE transition, and it is allowed from exactly one state: EDIT. A saved document must first go through BEGIN_EDITING.
It is refused when:
active successor documents exist — take them back or cancel them first (references to already-inactive successors are cleaned up silently);
open items with movements exist — payments have already been recorded against it.
After a successful deletion the document may no longer exist, and the call answers 301 Moved Permanently with an empty body rather than a document. Treat that as success, not as an error.
For documents that must remain on record, use cancellation (CANCELLED) or dissolution (DISSOLVED) instead of deletion — those keep the document and its number.
Querying documents
Sales documents are queried through the document.querySalesDocuments template. It is already scoped — restricted to the sales-side qualifiers and excluding internal POS categories — so you cannot widen it; use the purchase or POS template instead.
Do not guess field paths. Enumerate them:
depth is what walks into relations. To explore an entity's own attributes and relations, use GET /cmn/computed-queries/fields?entityIdentifier=….
The semantics you usually want: a delivery note that is posted but not yet invoiced is one whose state is not ACCEPTED (so SAVED, or PARTIALLY_ACCEPTED when partly invoiced), with lines where complete is false and quantityCommitted is below quantity.
Checklist
transitionIdfetched at runtime, never cachedRight endpoint for the question —
/statesfor state changes,/typesfor follow-upsHeader fields patched before lines are added
PUT /erp/documentssent withoutlines, and with the completetextsarrayLine paths use the doubled
/documentsegmentNumber left to the sequencer
301after deletion handled as successLeasable-lock headers respected
Related
App Backends · Onlineshops and Marketplaces — order import creates documents
Last updated
Was this helpful?