Sales
GET /v1/entities/{entity}/quotes
List Quotes
Quotes of an entity, newest first. A draft or sent quote past its valid-until day is
expired.
| Required scope | read (admin implies every scope) |
| Operation id | list_quotes_v1_entities__entity__quotes_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| query | status |
array of "accepted" | "cancelled" | "declined" | "draft" | "expired" | "sent" or null |
One or more statuses. | |
| query | customer |
string or null | Customer code or id. | |
| query | q |
string or null | Text in the number, reference or customer. | |
| query | limit |
integer | How many to return, newest first; at most 1000. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/entities/CH01/quotes" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of SalesDocumentOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1
}
]
POST /v1/entities/{entity}/quotes
Create Quote
Create a draft quote, numbered at once (QT-2026-000001). The currency and payment terms
default to the customer's, valid_until to 30 days on. Each line is priced as an invoice of
that day would be: the product's defaults and the VAT determined for the customer. Nothing is
posted.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_quote_v1_entities__entity__quotes_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
Request body
application/json (required): SalesDocumentInput
| Field | Type | Required | Description |
|---|---|---|---|
customer |
string | yes | Code (or id) of an active customer of the entity. |
document_date |
string (date) or null | Day of the quote or order (ISO 8601); default today where the company is. | |
valid_until |
string (date) or null | Quotes: the last day the customer may accept it, not before the quote's day; default 30 days after it. Orders take none. | |
currency |
string or null | ISO 4217 code; default the customer's. (pattern ^[A-Z]{3}$) |
|
customer_reference |
string or null | The customer's reference, e.g. its request or purchase order number; printed and carried to the invoice as its order reference. (max length 200) | |
payment_terms_days |
integer or null | Payment terms in days; default the customer's. (min 0; max 365) | |
notes |
string or null | Printed under the lines. (max length 2000) | |
opportunity_id |
string (uuid) or null | The deal it is for (GET /v1/opportunities): one of the same customer, not lost. It never changes, and an order made from the quote is for the same deal. |
|
lines |
array of SalesLineInput |
yes | The lines. (min items 1; max items 200) |
discount_percent |
decimal string or null | A discount on the whole document in percent of the lines after their own discounts; split over the lines, so the VAT of each rate goes down by its share. Give this or discount_amount, not both. (max 100; greater than 0) |
|
discount_amount |
decimal string or null | A discount on the whole document as an amount, split over the lines like discount_percent; an invoice of part of an order takes its part of it. (greater than 0) |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/quotes" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"customer": "C-DE",
"customer_reference": "RFQ-2026-118",
"document_date": "2026-10-11",
"lines": [
{
"description": "Consulting, senior",
"product": "CONSULT",
"quantity": "8",
"unit_price": "180.00"
},
{
"description": "Travel, flat rate",
"quantity": "1",
"unit_price": "450.00"
}
],
"valid_until": "2026-11-10"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | SalesDocumentCard |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
GET /v1/entities/{entity}/sales-orders
List Sales Orders
Sales orders of an entity, newest first. An open order is partially_invoiced or
invoiced by what its invoices that are not void or rejected add up to.
| Required scope | read (admin implies every scope) |
| Operation id | list_sales_orders_v1_entities__entity__sales_orders_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| query | status |
array of "cancelled" | "closed" | "draft" | "invoiced" | "open" | "partially_invoiced" or null |
One or more statuses. | |
| query | customer |
string or null | Customer code or id. | |
| query | q |
string or null | Text in the number, reference or customer. | |
| query | limit |
integer | How many to return, newest first; at most 1000. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/entities/CH01/sales-orders" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of SalesDocumentOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1
}
]
POST /v1/entities/{entity}/sales-orders
Create Sales Order
Enter a sales order directly as a draft (SO-2026-000001), for an order that came without
a quote; confirm it to invoice it. An accepted quote makes its order itself. Nothing is
posted.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_sales_order_v1_entities__entity__sales_orders_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
Request body
application/json (required): SalesDocumentInput
| Field | Type | Required | Description |
|---|---|---|---|
customer |
string | yes | Code (or id) of an active customer of the entity. |
document_date |
string (date) or null | Day of the quote or order (ISO 8601); default today where the company is. | |
valid_until |
string (date) or null | Quotes: the last day the customer may accept it, not before the quote's day; default 30 days after it. Orders take none. | |
currency |
string or null | ISO 4217 code; default the customer's. (pattern ^[A-Z]{3}$) |
|
customer_reference |
string or null | The customer's reference, e.g. its request or purchase order number; printed and carried to the invoice as its order reference. (max length 200) | |
payment_terms_days |
integer or null | Payment terms in days; default the customer's. (min 0; max 365) | |
notes |
string or null | Printed under the lines. (max length 2000) | |
opportunity_id |
string (uuid) or null | The deal it is for (GET /v1/opportunities): one of the same customer, not lost. It never changes, and an order made from the quote is for the same deal. |
|
lines |
array of SalesLineInput |
yes | The lines. (min items 1; max items 200) |
discount_percent |
decimal string or null | A discount on the whole document in percent of the lines after their own discounts; split over the lines, so the VAT of each rate goes down by its share. Give this or discount_amount, not both. (max 100; greater than 0) |
|
discount_amount |
decimal string or null | A discount on the whole document as an amount, split over the lines like discount_percent; an invoice of part of an order takes its part of it. (greater than 0) |
Example request
curl -X POST "$ERP_URL/v1/entities/CH01/sales-orders" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"customer": "C-DE",
"customer_reference": "RFQ-2026-118",
"document_date": "2026-10-11",
"lines": [
{
"description": "Consulting, senior",
"product": "CONSULT",
"quantity": "8",
"unit_price": "180.00"
},
{
"description": "Travel, flat rate",
"quantity": "1",
"unit_price": "450.00"
}
],
"valid_until": "2026-11-10"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | SalesDocumentCard |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
GET /v1/public/quotes/{token}
View Quote Online
The quote as its customer sees it on the link, without a sign-in: the seller, the lines,
VAT, totals, until when it is valid and whether it can be answered (state). 404 for a
link that is wrong or turned off.
| Required scope | any valid credential |
| Operation id | view_quote_online_v1_public_quotes__token__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | token |
string | yes | The link's token, as in /app/quote/{token}. (max length 100) |
| query | language |
"de" | "en" | "fr" | "it" | "sv" or null |
de, fr, it, en or sv. |
Example request
curl "$ERP_URL/v1/public/quotes/{token}" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | QuoteOnlineView |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"state": "accepted",
"language": "de",
"test_copy": true,
"seller_name": "string",
"seller_lines": [
"string"
],
"seller_logo": "string",
"seller_colour": "string",
"customer_name": "string",
"buyer_lines": [
"string"
],
"number": "string",
"revision": 1,
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"currency": "string",
"customer_reference": "string",
"payment_terms_days": 1,
"lines": [
{
"position": 1,
"description": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "string",
"vat_rate": "string",
"net_amount": "string"
}
],
"vat": [
{
"rate": "string",
"name": "string",
"net": "string",
"tax": "string"
}
],
"discount_percent": "string",
"discount_amount": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"notes": [
"string"
],
"answered_by": "string",
"answered_at": "2026-09-21T09:30:00Z"
}
POST /v1/public/quotes/{token}/accept
Accept Quote Online
Accept the quote for your company, naming the version you read (revision): kept with
your name, the time and your internet address, and the seller's order is made from it. 409
quote_not_open_online when it changed, expired or was answered since.
| Required scope | any valid credential |
| Idempotency-Key | not used |
| Operation id | accept_quote_online_v1_public_quotes__token__accept_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | token |
string | yes | The link's token, as in /app/quote/{token}. (max length 100) |
Request body
application/json (required): OnlineAcceptInput
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Your full name. (min length 1; max length 200) |
email |
string or null | Your e-mail address. (max length 320) | |
revision |
integer | yes | The version you read (revision of the page). (min 1) |
agree |
true |
yes | You accept the quote for your company. |
customer_reference |
string or null | Your purchase order number, if any. (max length 200) |
Example request
curl -X POST "$ERP_URL/v1/public/quotes/{token}/accept" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"agree": true,
"customer_reference": "PO-4711",
"email": "anna@werkstatt.example",
"name": "Anna Muster",
"revision": 2
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | QuoteOnlineView |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"state": "accepted",
"language": "de",
"test_copy": true,
"seller_name": "string",
"seller_lines": [
"string"
],
"seller_logo": "string",
"seller_colour": "string",
"customer_name": "string",
"buyer_lines": [
"string"
],
"number": "string",
"revision": 1,
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"currency": "string",
"customer_reference": "string",
"payment_terms_days": 1,
"lines": [
{
"position": 1,
"description": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "string",
"vat_rate": "string",
"net_amount": "string"
}
],
"vat": [
{
"rate": "string",
"name": "string",
"net": "string",
"tax": "string"
}
],
"discount_percent": "string",
"discount_amount": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"notes": [
"string"
],
"answered_by": "string",
"answered_at": "2026-09-21T09:30:00Z"
}
POST /v1/public/quotes/{token}/decline
Decline Quote Online
Decline the quote, with why if you like, naming the version you read. 409
quote_not_open_online when it changed, expired or was answered since.
| Required scope | any valid credential |
| Idempotency-Key | not used |
| Operation id | decline_quote_online_v1_public_quotes__token__decline_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | token |
string | yes | The link's token, as in /app/quote/{token}. (max length 100) |
Request body
application/json (required): OnlineDeclineInput
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Your full name. (min length 1; max length 200) |
email |
string or null | Your e-mail address. (max length 320) | |
revision |
integer | yes | The version you read. (min 1) |
reason |
string or null | Why, if you like. (max length 500) |
Example request
curl -X POST "$ERP_URL/v1/public/quotes/{token}/decline" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Anna Muster",
"reason": "Budget",
"revision": 1
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | QuoteOnlineView |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"state": "accepted",
"language": "de",
"test_copy": true,
"seller_name": "string",
"seller_lines": [
"string"
],
"seller_logo": "string",
"seller_colour": "string",
"customer_name": "string",
"buyer_lines": [
"string"
],
"number": "string",
"revision": 1,
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"currency": "string",
"customer_reference": "string",
"payment_terms_days": 1,
"lines": [
{
"position": 1,
"description": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "string",
"vat_rate": "string",
"net_amount": "string"
}
],
"vat": [
{
"rate": "string",
"name": "string",
"net": "string",
"tax": "string"
}
],
"discount_percent": "string",
"discount_amount": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"notes": [
"string"
],
"answered_by": "string",
"answered_at": "2026-09-21T09:30:00Z"
}
GET /v1/public/quotes/{token}/pdf
Quote Online Pdf
The PDF of the version the page shows, as it was handed over. 409 while it is being changed.
| Required scope | any valid credential |
| Operation id | quote_online_pdf_v1_public_quotes__token__pdf_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | token |
string | yes | The link's token, as in /app/quote/{token}. (max length 100) |
| query | download |
boolean | true sends the file as an attachment. (default false) |
Example request
curl "$ERP_URL/v1/public/quotes/{token}/pdf" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | The PDF. | application/pdf |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
GET /v1/sales-documents/{document}
Get Sales Document
A quote or sales order by id or number: lines (with invoiced and left quantities on an order), the linked quote or order, its invoices and hand-overs. The ETag names its version.
| Required scope | read (admin implies every scope) |
| Operation id | get_sales_document_v1_sales_documents__document__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
Example request
curl "$ERP_URL/v1/sales-documents/QT-2026-000001" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SalesDocumentCard |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
PATCH /v1/sales-documents/{document}
Update Sales Document
Change a draft (lines replaces every line; every line is priced again). A sent quote
goes back to draft and is sent again. 409 otherwise; 412 when If-Match names an older
version.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | update_sales_document_v1_sales_documents__document__patch |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed quote or order answers 412. (max length 200) |
Request body
application/json (required): SalesDocumentUpdate
| Field | Type | Required | Description |
|---|---|---|---|
customer |
string or null | Code (or id) of an active customer. | |
document_date |
string (date) or null | New day of the document. | |
valid_until |
string (date) or null | Quotes: new last day. | |
currency |
string or null | ISO 4217 code. (pattern ^[A-Z]{3}$) |
|
customer_reference |
string or null | The customer's reference; null clears it. (max length 200) | |
payment_terms_days |
integer or null | Payment terms in days, 0 to 365. (min 0; max 365) | |
notes |
string or null | Printed under the lines; null clears it. (max length 2000) | |
lines |
array of SalesLineInput or null |
Replaces every line. (min items 1; max items 200) | |
discount_percent |
decimal string or null | A document discount in percent; replaces any. null removes it. (max 100; greater than 0) | |
discount_amount |
decimal string or null | A document discount as an amount; replaces any. null removes it. (greater than 0) |
Example request
curl -X PATCH "$ERP_URL/v1/sales-documents/QT-2026-000001" \
-H "Authorization: Bearer $ERP_KEY" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"description": "Consulting, senior",
"product": "CONSULT",
"quantity": "8",
"unit_price": "180.00"
},
{
"description": "Travel, flat rate",
"quantity": "1",
"unit_price": "450.00"
}
],
"valid_until": "2026-11-30"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SalesDocumentCard, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
POST /v1/sales-documents/{document}/accept
Accept Quote
The customer accepted the quote: it is accepted, and the open sales order is made from it
(same customer, currency, terms and lines, dated the day accepted, its own number). Answers
the order. 409 quote_expired when the quote was no longer valid on that day (change its
valid-until day and send it again) or when it is not draft or sent; 409
credit_limit_exceeded when the order would take the customer above its credit limit (the
quote stays as it is); 422 for a day before the quote or in the future. Only a person records
the customer's answer (an agent key answers 403 agent_records_no_answer); the customer may
give it on the quote's link.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | accept_quote_v1_sales_documents__document__accept_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed quote or order answers 412. (max length 200) |
Request body
application/json (required): AcceptInput
| Field | Type | Required | Description |
|---|---|---|---|
accepted_on |
string (date) or null | The day the customer accepted, within the quote's validity and not after today; default today. It is the day of the order. | |
customer_reference |
string or null | The customer's purchase order number, if any. (max length 200) |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/accept" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SalesDocumentCard, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
POST /v1/sales-documents/{document}/cancel
Cancel Sales Document
Withdraw a quote not yet decided, or cancel a draft or open order nothing was invoiced on
(a draft invoice counts: void it first). It keeps its number. 409 order_invoiced for an
order with invoices: close it for the rest instead.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | cancel_sales_document_v1_sales_documents__document__cancel_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed quote or order answers 412. (max length 200) |
Request body
application/json (required): erp__features__crm__contracts__sales__ReasonInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept on the document. (min length 1; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/cancel" \
-H "Authorization: Bearer $ERP_KEY" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{
"reason": "Customer chose another offer"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SalesDocumentCard, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
POST /v1/sales-documents/{document}/close
Close Sales Order
Close an open order for the rest, with why (the customer no longer wants it): what was invoiced stays, the rest of each line is closed and no longer invoiced. Final. 409 when nothing was invoiced (cancel it instead) or nothing is left.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | close_sales_order_v1_sales_documents__document__close_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed quote or order answers 412. (max length 200) |
Request body
application/json (required): erp__features__crm__contracts__sales__ReasonInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept on the document. (min length 1; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/close" \
-H "Authorization: Bearer $ERP_KEY" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{
"reason": "Customer chose another offer"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SalesDocumentCard, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
POST /v1/sales-documents/{document}/confirm
Confirm Sales Order
Confirm a draft sales order: it is open, to be invoiced, and no longer changes. 409
credit_limit_exceeded when it would take the customer above its credit limit (what it owes,
its invoices waiting for approval and its open orders, with this one): it stays a draft.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | confirm_sales_order_v1_sales_documents__document__confirm_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed quote or order answers 412. (max length 200) |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/confirm" \
-H "Authorization: Bearer $ERP_KEY" \
-H 'If-Match: "1"'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SalesDocumentCard, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
POST /v1/sales-documents/{document}/decline
Decline Quote
The customer declined the quote, with why (also once it expired). Final. Only a person records it (an agent key answers 403).
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | decline_quote_v1_sales_documents__document__decline_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed quote or order answers 412. (max length 200) |
Request body
application/json (required): erp__features__crm__contracts__sales__ReasonInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept on the document. (min length 1; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/decline" \
-H "Authorization: Bearer $ERP_KEY" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{
"reason": "Customer chose another offer"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | SalesDocumentCard, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
GET /v1/sales-documents/{document}/history
Sales Document History
The audit events of a quote or order: changes, hand-overs, decisions and invoices, oldest first.
| Required scope | read (admin implies every scope) |
| Operation id | sales_document_history_v1_sales_documents__document__history_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
Example request
curl "$ERP_URL/v1/sales-documents/QT-2026-000001/history" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of AuditEventOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"id": 1,
"at": "2026-09-21T09:30:00Z",
"actor": {},
"action": "string",
"object_type": "string",
"object_id": "string",
"request_id": "string",
"idempotency_key": "string",
"data": {}
}
]
POST /v1/sales-documents/{document}/invoice
Invoice Sales Order
Draft the sales invoice of an open order: everything left to invoice, or the quantities
lines asks per order line. Each invoice line names its order line (sales_line_id), at
the order's price, product and VAT rate type; the VAT is determined on the invoice's day, the
customer's reference becomes the order reference and the order's payment terms the due date.
Answers the draft invoice, to be checked, posted and sent like any other
(POST /v1/documents/{id}/submit). 409 for an order that is not open or has nothing left;
422 for more than is left on a line. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | invoice_sales_order_v1_sales_documents__document__invoice_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed quote or order answers 412. (max length 200) |
Request body
application/json (required): InvoiceOrderInput
| Field | Type | Required | Description |
|---|---|---|---|
document_date |
string (date) or null | Day of the invoice; default today. | |
supply_date |
string (date) or null | The date of supply, e.g. the day the goods were delivered; default: the day of the invoice. Where the law of the company's country says so, it decides the VAT period (see supply_date of POST /v1/documents). |
|
lines |
array of InvoiceLineInput or null |
What to invoice per order line; omitted: everything left to invoice. (min items 1; max items 200) |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/invoice" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | DocumentOut, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"withholding_code": "string",
"withholding_rate": "string",
"withholding_base": "0.00",
"withholding_amount": "0.00",
"creditable_amount": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party_code": "string",
"party_name": "string",
"party_status": "string",
"number": "string",
"external_number": "string",
"status": "string",
"document_date": "2026-09-21",
"supply_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"functional_total": "string",
"credits_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"notes": "string",
"legal_notes": [
"string"
],
"payment_iban": "string",
"payment_reference": "string",
"buyer_reference": "string",
"order_reference": "string",
"open_amount": "string",
"payment_blocked": false,
"tax_fx": {},
"tax_fx_rate": "string",
"tax_fx_net_total": "string",
"tax_fx_tax_total": "string",
"cash_discount_percent": "string",
"cash_discount_date": "2026-09-21",
"cash_discount_amount": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"provenance": {},
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"missing_content": [
{
"party": "buyer",
"field": "name",
"basis": "string",
"record_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
],
"source_file": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string",
"created_at": "2026-09-21T09:30:00Z"
},
"ic": {
"link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"role": "mirror",
"status": "string",
"partner_entity": "string",
"other_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"lines": [
{
"line_no": 1,
"description": "string",
"quantity": "string",
"creditable_quantity": "string",
"creditable_tax_amount": "string",
"creditable_discount_amount": "string",
"unit_price": "string",
"account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"obligation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sales_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"credits_line": 1,
"delivery_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"goods_returned": false,
"warehouse_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"as_credited": false,
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"use_tax": false,
"vat_scope": "in_scope",
"vat_scope_reason": "damages",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"tax": {},
"vat_recovery_percent": "100",
"tax_fx_net_amount": "string",
"tax_fx_tax_amount": "string",
"dimensions": {}
}
],
"delivery_notes": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string"
}
]
}
POST /v1/sales-documents/{document}/links
Create Quote Link
Make a link for a sent quote, to give the customer yourself (your own e-mail, a chat): on it they see the quote's current version and accept or decline it with their name. The token is shown this once; only its hash is kept. A quote this server e-mails carries a link of its own. Only a person makes one (an agent key answers 403). 409 for a quote that is not sent; 422 for an expired one.
| Required scope | post (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | create_quote_link_v1_sales_documents__document__links_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
Request body
application/json (required): QuoteLinkInput
| Field | Type | Required | Description |
|---|---|---|---|
handed_to |
string | yes | To whom you give the link, for the record. (min length 1; max length 320) |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/links" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"handed_to": "Anna Muster"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | QuoteLinkIssued |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"prefix": "string",
"handed_to": "string",
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"first_viewed_at": "2026-09-21T09:30:00Z",
"last_viewed_at": "2026-09-21T09:30:00Z",
"revoked_at": "2026-09-21T09:30:00Z",
"token": "string"
}
POST /v1/sales-documents/{document}/links/{quote_link}/revoke
Revoke Quote Link
Turn a quote's link off: its page no longer opens. Final; turning it off again changes nothing.
| Required scope | post (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | revoke_quote_link_v1_sales_documents__document__links__quote_link__revoke_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| path | quote_link |
string (uuid) | yes | Id of the link (online.links[].id). |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/links/{quote_link}/revoke" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | QuoteLinkOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"prefix": "string",
"handed_to": "string",
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"first_viewed_at": "2026-09-21T09:30:00Z",
"last_viewed_at": "2026-09-21T09:30:00Z",
"revoked_at": "2026-09-21T09:30:00Z"
}
POST /v1/sales-documents/{document}/mark-sent
Mark Sales Document Sent
Record that a person handed the PDF over outside this system (their own e-mail, post, the
customer's portal); nothing is sent. A draft quote is then sent and its version's PDF kept;
POST /v1/sales-documents/{document}/links gives a link to answer it online. Only a person
records. 422 for a day before the document or after today. Needs an Idempotency-Key.
| Required scope | post (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | mark_sales_document_sent_v1_sales_documents__document__mark_sent_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
Request body
application/json (required): SalesMarkSentInput
| Field | Type | Required | Description |
|---|---|---|---|
channel |
"email" | "other" | "portal" | "post" |
yes | email, post, portal or other. |
recipient |
string | yes | Who got it. (min length 1; max length 200) |
handed_over_on |
string (date) | yes | The day it was handed over: not before the document's day, not after today. |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/mark-sent" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"channel": "post",
"handed_over_on": "2026-10-12",
"recipient": "Berlin GmbH"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | SalesDocumentCard |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
GET /v1/sales-documents/{document}/pdf
Download a quote or order confirmation
The quote or order confirmation as a PDF in the customer's language, with its lines, VAT,
totals and payment terms; a quote says until when it is valid. The header
X-Content-SHA256 carries its SHA-256.
| Required scope | read (admin implies every scope) |
| Operation id | get_sales_document_pdf_v1_sales_documents__document__pdf_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| query | download |
boolean | true sends the file as an attachment; false inline. (default false) |
|
| query | language |
"de" | "en" | "fr" | "it" | "sv" or null |
de, fr, it, en or sv; default the customer's. |
Example request
curl "$ERP_URL/v1/sales-documents/QT-2026-000001/pdf" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | The PDF. | application/pdf |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
GET /v1/sales-documents/{document}/revisions/{revision}/pdf
Download a version of a quote as the customer got it
The PDF of a version of the quote exactly as it was handed over. 404 for a version that was never handed over.
| Required scope | read (admin implies every scope) |
| Operation id | get_quote_revision_pdf_v1_sales_documents__document__revisions__revision__pdf_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
| path | revision |
integer | yes | The version, from 1. (min 1) |
| query | download |
boolean | true sends the file as an attachment. (default false) |
Example request
curl "$ERP_URL/v1/sales-documents/QT-2026-000001/revisions/{revision}/pdf" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | The PDF. | application/pdf |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
POST /v1/sales-documents/{document}/send
Send Sales Document
E-mail the PDF to the customer, as an invoice is sent: to to (only the customer's own
addresses) or its billing address, within the organization's hourly limit. A draft quote is
then sent, and its mail carries a link on which the customer accepts or declines it online
(ADR-0088); its version's PDF is kept. Only a person sends (the policy action
send_document; an agent key answers 403). 409 in a test copy, for a declined or cancelled
document, or when this server has no mail server (download the PDF instead); 422 for an
expired quote or an address that is not the customer's; 429 beyond the hourly limit; 502 when
the mail server refuses, and nothing is recorded. Needs an Idempotency-Key.
| Required scope | post (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | send_sales_document_v1_sales_documents__document__send_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document |
string | yes | Quote or sales order id (uuid) or number, e.g. QT-2026-000001 or SO-2026-000001. |
Request body
application/json (required): SalesSendInput
| Field | Type | Required | Description |
|---|---|---|---|
to |
array of string or null | Addresses of the customer (as for an invoice); omitted: its billing address. (min items 1; max items 5) | |
message |
string or null | A text placed before the standard text. (max length 1000) |
Example request
curl -X POST "$ERP_URL/v1/sales-documents/QT-2026-000001/send" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | SalesDocumentCard |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
| 429 | A per-organization limit was reached, e.g. messages sent per hour (rate_limited, with retry_after_seconds). |
ErrorEnvelope |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "order",
"number": "string",
"status": "string",
"customer": "string",
"customer_name": "string",
"customer_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "string",
"document_date": "2026-09-21",
"valid_until": "2026-09-21",
"customer_reference": "string",
"opportunity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"discount_percent": "string",
"discount_amount": "0.00",
"net_total": "string",
"tax_total": "string",
"total": "string",
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"revision": 1,
"entity": "string",
"payment_terms_days": 1,
"notes": "string",
"legal_notes": [
"string"
],
"reason": "string",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z",
"created_by": {},
"opportunity_number": "string",
"quote": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"order": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string"
},
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"line_no": 1,
"description": "string",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"quantity": "string",
"unit_price": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"closed_quantity": "string",
"invoiced_quantity": "0",
"left_quantity": "0",
"shipped_quantity": "string",
"left_to_ship_quantity": "string"
}
],
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"status": "string",
"document_date": "2026-09-21",
"net_total": "string",
"total": "string"
}
],
"invoiced_net": "0.00",
"dispatches": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"filename": "string",
"sha256": "string",
"sent_by": {},
"sent_at": "2026-09-21T09:30:00Z"
}
],
"sending_configured": true,
"suggested_recipient": "string",
"online": {
"revision": 1,
"revisions": [
null
],
"links": [
null
],
"answer": null
}
}
Schemas
AcceptInput
| Field | Type | Required | Description |
|---|---|---|---|
accepted_on |
string (date) or null | The day the customer accepted, within the quote's validity and not after today; default today. It is the day of the order. | |
customer_reference |
string or null | The customer's purchase order number, if any. (max length 200) |
InvoiceLineInput
| Field | Type | Required | Description |
|---|---|---|---|
line_no |
integer | yes | Number of the order line. (min 1) |
quantity |
decimal string | yes | Quantity to invoice now. (greater than 0) |
InvoiceOrderInput
| Field | Type | Required | Description |
|---|---|---|---|
document_date |
string (date) or null | Day of the invoice; default today. | |
supply_date |
string (date) or null | The date of supply, e.g. the day the goods were delivered; default: the day of the invoice. Where the law of the company's country says so, it decides the VAT period (see supply_date of POST /v1/documents). |
|
lines |
array of InvoiceLineInput or null |
What to invoice per order line; omitted: everything left to invoice. (min items 1; max items 200) |
OnlineAcceptInput
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Your full name. (min length 1; max length 200) |
email |
string or null | Your e-mail address. (max length 320) | |
revision |
integer | yes | The version you read (revision of the page). (min 1) |
agree |
true |
yes | You accept the quote for your company. |
customer_reference |
string or null | Your purchase order number, if any. (max length 200) |
OnlineDeclineInput
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Your full name. (min length 1; max length 200) |
email |
string or null | Your e-mail address. (max length 320) | |
revision |
integer | yes | The version you read. (min 1) |
reason |
string or null | Why, if you like. (max length 500) |
OnlineLine
| Field | Type | Required | Description |
|---|---|---|---|
position |
integer | yes | Position, from 1. |
description |
string | yes | What is offered. |
quantity |
string | yes | Quantity. |
unit_price |
string | yes | Net unit price. |
discount_percent |
string or null | yes | Line discount in percent, if asked so. |
discount_amount |
string | yes | Line discount in money; 0 for none. |
vat_rate |
string | yes | VAT rate in percent. |
net_amount |
string | yes | Net of the line after its own discount. |
OnlineVat
| Field | Type | Required | Description |
|---|---|---|---|
rate |
string | yes | VAT rate in percent. |
name |
string or null | yes | The tax's name where a line bears several. |
net |
string | yes | Net at this rate. |
tax |
string | yes | VAT at this rate. |
OrderInvoiceOut
An invoice made from the order.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the invoice (GET /v1/documents/{id}). |
number |
string or null | yes | Its number; null before it is posted. |
status |
string | yes | draft, pending_approval, posted, rejected or void. |
document_date |
string (date) | yes | Day of the invoice. |
net_total |
string | yes | Net total of the invoice. |
total |
string | yes | Total including VAT. |
QuoteAnswerOut
| Field | Type | Required | Description |
|---|---|---|---|
revision |
integer | yes | The version answered. |
decision |
"accepted" | "declined" |
yes | accepted or declined. |
signer_name |
string | yes | The name the customer's person gave. |
signer_email |
string or null | yes | The address they gave; null if none. |
reason |
string or null | yes | Why they declined, if they said. |
link_prefix |
string | yes | The link it was given on. |
sha256 |
string | yes | SHA-256 of the PDF of the version answered. |
client_address |
string | yes | The internet address it came from. |
user_agent |
string or null | yes | The browser, as it named itself. |
answered_at |
string (date-time) | yes | When (UTC). |
QuoteLinkInput
| Field | Type | Required | Description |
|---|---|---|---|
handed_to |
string | yes | To whom you give the link, for the record. (min length 1; max length 320) |
QuoteLinkIssued
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the link. |
prefix |
string | yes | The link's public part, shown in the customer's answer. |
handed_to |
string | yes | To whom it was given. |
created_by |
object | yes | Who made it. |
created_at |
string (date-time) | yes | When (UTC). |
first_viewed_at |
string (date-time) or null | yes | When the customer first opened it. |
last_viewed_at |
string (date-time) or null | yes | When the customer last opened it. |
revoked_at |
string (date-time) or null | yes | When it was turned off; null if it works. |
token |
string | yes | The link's secret, shown this once: the customer's page is /app/quote/{token}. Only its hash is kept. |
QuoteLinkOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the link. |
prefix |
string | yes | The link's public part, shown in the customer's answer. |
handed_to |
string | yes | To whom it was given. |
created_by |
object | yes | Who made it. |
created_at |
string (date-time) | yes | When (UTC). |
first_viewed_at |
string (date-time) or null | yes | When the customer first opened it. |
last_viewed_at |
string (date-time) or null | yes | When the customer last opened it. |
revoked_at |
string (date-time) or null | yes | When it was turned off; null if it works. |
QuoteOnlineCard
Of a quote: its version, the versions handed over, its links and the online answer.
| Field | Type | Required | Description |
|---|---|---|---|
revision |
integer | yes | The current version. |
revisions |
array of QuoteRevisionOut |
yes | Versions handed over, the first first. |
links |
array of QuoteLinkOut |
yes | Links given to the customer, oldest first. |
answer |
QuoteAnswerOut or null |
yes | The customer's answer online; null if none. |
QuoteOnlineView
The quote as its customer sees it on the link.
| Field | Type | Required | Description |
|---|---|---|---|
state |
"accepted" | "declined" | "expired" | "open" | "revising" | "withdrawn" |
yes | open (it can be answered), expired, revising (the seller is changing it: a new version follows), accepted, declined or withdrawn. |
language |
"de" | "en" | "fr" | "it" | "sv" |
yes | The language of the texts. |
test_copy |
boolean | yes | From a test copy: not a real offer. |
seller_name |
string | yes | Who makes the offer. |
seller_lines |
array of string | yes | The seller's address and contact. |
seller_logo |
string or null | The seller's logo as a data:image/png;base64, address; null: none. |
|
seller_colour |
string or null | The seller's brand colour, #rrggbb; null: the default. |
|
customer_name |
string | yes | To whom. |
buyer_lines |
array of string | yes | The customer's address as printed. |
number |
string | yes | The quote's number. |
revision |
integer | yes | Its version. |
document_date |
string (date) | yes | Its day. |
valid_until |
string (date) | yes | The last day to accept it. |
currency |
string | yes | ISO 4217 currency. |
customer_reference |
string or null | yes | The customer's own reference. |
payment_terms_days |
integer | yes | Days to pay the invoices of the order. |
lines |
array of OnlineLine |
yes | The lines; empty while it is being changed. |
vat |
array of OnlineVat |
yes | VAT by rate. |
discount_percent |
string or null | yes | Document discount in percent. |
discount_amount |
string | yes | Document discount in money. |
net_total |
string | yes | Net total. |
tax_total |
string | yes | VAT. |
total |
string | yes | Total including VAT. |
notes |
array of string | yes | Notes and the VAT remarks the lines need. |
answered_by |
string or null | yes | Who answered online; null if not online. |
answered_at |
string (date-time) or null | yes | When it was accepted or declined. |
QuoteRevisionOut
| Field | Type | Required | Description |
|---|---|---|---|
revision |
integer | yes | The version, from 1. |
document_date |
string (date) | yes | The quote's day in that version. |
valid_until |
string (date) | yes | The last day to accept that version. |
currency |
string | yes | ISO 4217 currency. |
net_total |
string | yes | Net total, after discounts. |
tax_total |
string | yes | VAT. |
total |
string | yes | Total including VAT. |
sha256 |
string | yes | SHA-256 of the PDF the customer got. |
created_by |
object | yes | Who handed it over first. |
created_at |
string (date-time) | yes | When it was first handed over (UTC). |
SalesDispatchOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the hand-over. |
channel |
"email" | "other" | "portal" | "post" | "system_email" |
yes | system_email (this server sent it) or what a person recorded. |
handed_over_on |
string (date) | yes | The day it was handed over. |
recipients |
array of string | yes | To whom. |
subject |
string or null | yes | Subject of the e-mail; null for a recorded one. |
filename |
string | yes | The PDF handed over. |
sha256 |
string | yes | SHA-256 of that PDF, hex. |
sent_by |
object | yes | Who: type, id and name. |
sent_at |
string (date-time) | yes | When it was sent or recorded (UTC). |
SalesDocumentCard
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the quote or order. |
kind |
"order" | "quote" |
yes | quote or order. |
number |
string | yes | QT-2026-000001 or SO-2026-000001, per entity and year. |
status |
string | yes | A quote is draft, sent, expired (draft or sent, and its valid_until has passed), accepted, declined or cancelled. An order is draft, open, partially_invoiced, invoiced, closed (for the rest, with a reason) or cancelled. |
customer |
string | yes | Customer code. |
customer_name |
string | yes | Name of the customer. |
customer_id |
string (uuid) | yes | Id of the customer. |
currency |
string | yes | ISO 4217 currency. |
document_date |
string (date) | yes | Day of the quote or order. |
valid_until |
string (date) or null | yes | Quotes: last day to accept; null on orders. |
customer_reference |
string or null | yes | The customer's reference; null if none. |
opportunity_id |
string (uuid) or null | The deal it is for; null if none. | |
discount_percent |
string or null | The document discount in percent; null for an amount or none. | |
discount_amount |
string | The document discount in money; 0 for none. (default "0.00") |
|
net_total |
string | yes | Net total, after all discounts. |
tax_total |
string | yes | VAT, as the invoice would determine it on the day. |
total |
string | yes | Total including VAT. |
created_at |
string (date-time) | yes | When it was created (UTC). |
version |
integer | yes | Raised on every change; the ETag names it. |
revision |
integer | Quotes: the version, from 1; changing a sent quote begins the next (it is a draft until sent again). Orders: 1. (default 1) |
|
entity |
string | yes | Code of the entity. |
payment_terms_days |
integer | yes | Payment terms in days. |
notes |
string or null | yes | Free text printed under the lines. |
legal_notes |
array of string | yes | Remarks the VAT treatment of the lines requires. |
reason |
string or null | yes | Why it was declined, cancelled or closed for the rest; null otherwise. |
decided_by |
object or null | yes | Who accepted, declined, cancelled or closed it; null otherwise. |
decided_at |
string (date-time) or null | yes | When (UTC); null otherwise. |
created_by |
object | yes | Who created it (type, id, name). |
opportunity_number |
string or null | The number of the deal it is for; null if none. | |
quote |
SalesLinkOut or null |
yes | Of an order: the quote it was made from. |
order |
SalesLinkOut or null |
yes | Of an accepted quote: the order it became. |
lines |
array of SalesLineOut |
yes | The lines. |
invoices |
array of OrderInvoiceOut |
Of an order: the invoices made from it, oldest first. | |
invoiced_net |
string | Of an order: the net invoiced on invoices that are not void or rejected. (default "0.00") |
|
dispatches |
array of SalesDispatchOut |
yes | Hand-overs, oldest first. |
sending_configured |
boolean | yes | Whether this server can e-mail it (a mail server, and not a test copy). |
suggested_recipient |
string or null | yes | Where an e-mail goes without addresses given; null when the customer has none. |
online |
QuoteOnlineCard or null |
Of a quote: the versions handed over, the links given to answer it online and the customer's answer there (ADR-0088); null on an order. |
SalesDocumentInput
| Field | Type | Required | Description |
|---|---|---|---|
customer |
string | yes | Code (or id) of an active customer of the entity. |
document_date |
string (date) or null | Day of the quote or order (ISO 8601); default today where the company is. | |
valid_until |
string (date) or null | Quotes: the last day the customer may accept it, not before the quote's day; default 30 days after it. Orders take none. | |
currency |
string or null | ISO 4217 code; default the customer's. (pattern ^[A-Z]{3}$) |
|
customer_reference |
string or null | The customer's reference, e.g. its request or purchase order number; printed and carried to the invoice as its order reference. (max length 200) | |
payment_terms_days |
integer or null | Payment terms in days; default the customer's. (min 0; max 365) | |
notes |
string or null | Printed under the lines. (max length 2000) | |
opportunity_id |
string (uuid) or null | The deal it is for (GET /v1/opportunities): one of the same customer, not lost. It never changes, and an order made from the quote is for the same deal. |
|
lines |
array of SalesLineInput |
yes | The lines. (min items 1; max items 200) |
discount_percent |
decimal string or null | A discount on the whole document in percent of the lines after their own discounts; split over the lines, so the VAT of each rate goes down by its share. Give this or discount_amount, not both. (max 100; greater than 0) |
|
discount_amount |
decimal string or null | A discount on the whole document as an amount, split over the lines like discount_percent; an invoice of part of an order takes its part of it. (greater than 0) |
SalesDocumentOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the quote or order. |
kind |
"order" | "quote" |
yes | quote or order. |
number |
string | yes | QT-2026-000001 or SO-2026-000001, per entity and year. |
status |
string | yes | A quote is draft, sent, expired (draft or sent, and its valid_until has passed), accepted, declined or cancelled. An order is draft, open, partially_invoiced, invoiced, closed (for the rest, with a reason) or cancelled. |
customer |
string | yes | Customer code. |
customer_name |
string | yes | Name of the customer. |
customer_id |
string (uuid) | yes | Id of the customer. |
currency |
string | yes | ISO 4217 currency. |
document_date |
string (date) | yes | Day of the quote or order. |
valid_until |
string (date) or null | yes | Quotes: last day to accept; null on orders. |
customer_reference |
string or null | yes | The customer's reference; null if none. |
opportunity_id |
string (uuid) or null | The deal it is for; null if none. | |
discount_percent |
string or null | The document discount in percent; null for an amount or none. | |
discount_amount |
string | The document discount in money; 0 for none. (default "0.00") |
|
net_total |
string | yes | Net total, after all discounts. |
tax_total |
string | yes | VAT, as the invoice would determine it on the day. |
total |
string | yes | Total including VAT. |
created_at |
string (date-time) | yes | When it was created (UTC). |
version |
integer | yes | Raised on every change; the ETag names it. |
revision |
integer | Quotes: the version, from 1; changing a sent quote begins the next (it is a draft until sent again). Orders: 1. (default 1) |
SalesDocumentUpdate
Changes to a draft (a sent quote goes back to draft and is sent again). Omitted fields keep
their value; lines replaces every line.
| Field | Type | Required | Description |
|---|---|---|---|
customer |
string or null | Code (or id) of an active customer. | |
document_date |
string (date) or null | New day of the document. | |
valid_until |
string (date) or null | Quotes: new last day. | |
currency |
string or null | ISO 4217 code. (pattern ^[A-Z]{3}$) |
|
customer_reference |
string or null | The customer's reference; null clears it. (max length 200) | |
payment_terms_days |
integer or null | Payment terms in days, 0 to 365. (min 0; max 365) | |
notes |
string or null | Printed under the lines; null clears it. (max length 2000) | |
lines |
array of SalesLineInput or null |
Replaces every line. (min items 1; max items 200) | |
discount_percent |
decimal string or null | A document discount in percent; replaces any. null removes it. (max 100; greater than 0) | |
discount_amount |
decimal string or null | A document discount as an amount; replaces any. null removes it. (greater than 0) |
SalesLineInput
| Field | Type | Required | Description |
|---|---|---|---|
description |
string or null | What is offered; default the product's name (required on a line without a product). (min length 1; max length 500) | |
quantity |
decimal string | Quantity, a decimal string greater than 0. (greater than 0; default "1") |
|
unit_price |
decimal string or null | Net unit price in the document's currency; default the customer's price from its price list, else the product's list price on the document's day (required on a line without a product; 422 product_unpriced when the product has no price). (min 0) |
|
product |
string or null | Code (or id) of an active product of the entity (GET /v1/products): the line takes its name, price, supply and rate type unless it gives its own, and the invoice its revenue account. |
|
supply_type |
"electronic_services" | "goods" | "services" |
goods, services (default) or electronic_services. (default "services") |
|
rate_type |
"exempt" | "parking" | "reduced" | "special" | "standard" | "super_reduced" | "zero" |
Which VAT rate applies; default standard. (default "standard") |
|
tax_category |
string or null | One of the categories of the rate (GET /v1/entities/{entity}/vat-rate-types), e.g. books: it chooses the rate where the rate type has several (France reduced: 5.5 % or 10 %) and names the kind of an exempt supply (insurance). Omit for none. (max length 200) |
|
discount_percent |
decimal string or null | A discount on the line in percent (above 0, at most 100); carried to the order and its invoices. Give this or discount_amount, not both. (max 100; greater than 0) |
|
discount_amount |
decimal string or null | A discount on the line as an amount off quantity times unit price; an invoice of part of the line takes its part of it. (greater than 0) |
SalesLineOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the line. |
line_no |
integer | yes | Position, from 1. |
description |
string | yes | What is offered or ordered. |
product_id |
string (uuid) or null | yes | Id of the product; null for a free line. |
product |
string or null | Code of the product; null for none. | |
supply_type |
string | yes | goods, services or electronic_services. |
rate_type |
string | yes | The VAT rate type. |
tax_category |
string or null | yes | Named tax category; null when none. |
quantity |
string | yes | Quantity. |
unit_price |
string | yes | Net unit price. |
discount_percent |
string or null | The line discount in percent; null for an amount or none. | |
discount_amount |
string | The line discount in money; 0 for none. (default "0.00") |
|
document_discount_amount |
string | The line's share of the document discount. (default "0.00") |
|
net_amount |
string | yes | Quantity times unit price, rounded, less the line discount and its share of the document discount. |
tax_rate |
string | yes | VAT rate in percent, as the invoice would charge it. |
tax_amount |
string | yes | VAT of the line. |
closed_quantity |
string | yes | Of an order closed for the rest: what will not be invoiced any more. |
invoiced_quantity |
string | Of an order: the quantity on its invoices that are not void or rejected (drafts too). (default "0") |
|
left_quantity |
string | Of an open order: what is left to invoice (ordered, less closed, less invoiced); 0 otherwise. (default "0") |
|
shipped_quantity |
string or null | Of an order line of a stock item: what left on delivery notes (drafts too) and with invoices that ship it; null for other lines. | |
left_to_ship_quantity |
string or null | Of an open order's line of a stock item: what is left to ship (ordered, less closed, less shipped); 0 on an order no longer open; null for other lines. |
SalesLinkOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the linked quote or order. |
number |
string | yes | Its number. |
status |
string | yes | Its status. |
SalesMarkSentInput
| Field | Type | Required | Description |
|---|---|---|---|
channel |
"email" | "other" | "portal" | "post" |
yes | email, post, portal or other. |
recipient |
string | yes | Who got it. (min length 1; max length 200) |
handed_over_on |
string (date) | yes | The day it was handed over: not before the document's day, not after today. |
SalesSendInput
| Field | Type | Required | Description |
|---|---|---|---|
to |
array of string or null | Addresses of the customer (as for an invoice); omitted: its billing address. (min items 1; max items 5) | |
message |
string or null | A text placed before the standard text. (max length 1000) |
erp__features__crm__contracts__sales__ReasonInput
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept on the document. (min length 1; max length 500) |