Files
dashback/docs/api/openapi.json
2026-09-27 18:01:08 +02:00

12209 lines
329 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"openapi": "3.0.3",
"info": {
"title": "Dash API",
"version": "1.0.0",
"description": "Backend API for Dash — Google SSO auth, MSSQL, session cookies.\n\nBrowser clients must send cookies (`credentials: 'include'`). Sessions use an HTTP-only `token` cookie (`Secure`, `SameSite=Strict`, 24h); HTTPS is required for login."
},
"servers": [
{
"url": "http://localhost:4208/api",
"description": "Local default (PORT / .env)"
}
],
"tags": [
{
"name": "Auth",
"description": "Google SSO and session"
},
{
"name": "Health",
"description": "Connectivity checks"
},
{
"name": "Events",
"description": "SSE update signals for live clients"
},
{
"name": "Article",
"description": "Articles under categories"
},
{
"name": "Category",
"description": "Category tree and membership"
},
{
"name": "Company",
"description": "Company CRUD"
},
{
"name": "CompanySetting",
"description": "Company-scoped settings"
},
{
"name": "Unit",
"description": "Company-scoped units of measure"
},
{
"name": "Brand",
"description": "Company-scoped brands"
},
{
"name": "Supplier",
"description": "Company-scoped suppliers (Lieferanten)"
},
{
"name": "Delivery",
"description": "Company-scoped goods receipts (Lieferungen)"
},
{
"name": "Quote",
"description": "Company-scoped quotes (Angebote)"
},
{
"name": "Order",
"description": "Company-scoped sales orders (Aufträge)"
},
{
"name": "Customergroup",
"description": "Company-scoped customer groups"
},
{
"name": "Entity",
"description": "Company-scoped entities, addresses, and contacts"
},
{
"name": "Image",
"description": "Content-addressed AVIF by fullHash"
},
{
"name": "Posterminal",
"description": "Company-scoped POS terminals"
},
{
"name": "Printer",
"description": "Company-scoped receipt and label printers"
},
{
"name": "Ledger",
"description": "Append-only ledger transactions"
},
{
"name": "Stock",
"description": "Manual stock additions and removals"
},
{
"name": "Serper",
"description": "Serper Google image search and thumbnail streaming"
}
],
"paths": {
"/unit": {
"get": {
"tags": [
"Unit"
],
"summary": "List units",
"description": "Lists units for the authenticated user's selected company (default-language symbol/name).",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "unit/list.md",
"responses": {
"200": {
"description": "Unit list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"units": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Unit"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"put": {
"tags": [
"Unit"
],
"summary": "Upsert unit",
"description": "Insert or update unit by `si` for the selected company. Upserts `unittext` for the company language.\n`basesifactor` must be finite and `> 0`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "unit/upsert.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"si",
"basesi",
"basesifactor",
"symbol",
"name"
],
"properties": {
"si": {
"type": "string"
},
"basesi": {
"type": "string"
},
"basesifactor": {
"type": "number"
},
"symbol": {
"type": "string"
},
"name": {
"type": "string"
}
},
"example": {
"si": "l",
"basesi": "l",
"basesifactor": 1,
"symbol": "L",
"name": "Liter"
}
}
}
}
},
"responses": {
"200": {
"description": "Upserted unit",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"unit": {
"$ref": "#/components/schemas/Unit"
}
}
}
}
}
},
"400": {
"description": "Invalid unit or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Unit"
],
"summary": "Delete unit",
"description": "Deletes `si` for the selected company. Fails with `Unit in use` if articles still reference it.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "unit/delete.md",
"parameters": [
{
"in": "query",
"name": "si",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Missing si or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company or unit not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Unit in use",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/supplier": {
"get": {
"tags": [
"Supplier"
],
"summary": "List suppliers",
"description": "Lists suppliers (`entity` rows in the company `Lieferanten` customergroup)\nfor the authenticated user's selected company.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "supplier/list.md",
"responses": {
"200": {
"description": "Supplier list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"suppliers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Supplier"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Supplier"
],
"summary": "Create supplier",
"description": "Creates a supplier in the company `Lieferanten` group (created on first use).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "supplier/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
},
"example": {
"name": "Großhandel Müller"
}
}
}
}
},
"responses": {
"201": {
"description": "Created supplier",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"supplier": {
"$ref": "#/components/schemas/Supplier"
}
}
}
}
}
},
"400": {
"description": "Name required or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/supplier/{id}": {
"patch": {
"tags": [
"Supplier"
],
"summary": "Update supplier",
"description": "Renames a supplier for the selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "supplier/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
},
"example": {
"name": "Großhandel Müller GmbH"
}
}
}
}
},
"responses": {
"200": {
"description": "Updated supplier",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"supplier": {
"$ref": "#/components/schemas/Supplier"
}
}
}
}
}
},
"400": {
"description": "Name required, invalid id, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Supplier or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Supplier"
],
"summary": "Delete supplier",
"description": "Deletes the supplier entity for the selected company.\nRelated `entityhistory` cascades; `manifest.entity_id` is set null.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "supplier/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Supplier not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/supplier/{id}/articles": {
"get": {
"tags": [
"Supplier"
],
"summary": "List supplier articles by SKUs",
"description": "Lists article-supplier assignments for the specified supplier (entity ID),\noptionally filtered by a list of SKUs (matching either article SKU or supplier SKU).\nIncludes linked article details (name, slug, photos/articlepic, eans/articleean).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "supplier/articles.get.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Supplier entity ID"
},
{
"in": "query",
"name": "skus",
"required": false,
"schema": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"description": "Optional SKU or comma-separated SKUs to filter by"
}
],
"responses": {
"200": {
"description": "List of supplier articles",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"articles": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SupplierArticle"
}
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Supplier or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Supplier"
],
"summary": "Query supplier articles by SKUs (bulk)",
"description": "Queries article-supplier assignments for the specified supplier (entity ID) with a\nJSON list of SKUs in the request body, avoiding URL length limits for large SKU lists.\nIncludes linked article details (name, slug, photos/articlepic, eans/articleean).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "supplier/articles.post.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Supplier entity ID"
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"skus": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of SKUs to filter by (empty array returns empty list)"
}
},
"example": {
"skus": [
"SKU-1",
"SKU-2"
]
}
}
}
}
},
"responses": {
"200": {
"description": "List of supplier articles",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"articles": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SupplierArticle"
}
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Supplier or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/stock/adjust": {
"post": {
"tags": [
"Stock"
],
"summary": "Adjust stock manually",
"description": "Writes signed (+ or -) entries to the append-only ledger under an implicit manifest\nof type \"adjustment\", allocating a number from the \"adjustment\" number scheme.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "stock/adjust.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StockAdjustmentInput"
}
}
}
},
"responses": {
"201": {
"description": "Stock adjusted successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StockAdjustmentResult"
}
}
}
},
"400": {
"description": "Missing fields, invalid amount, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article or warehouse not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/serper/images": {
"post": {
"tags": [
"Serper"
],
"summary": "Search images via Serper.dev and stream WebP thumbnails (SSE)",
"description": "Queries Google Image search via `https://google.serper.dev/images` using the\ncompany setting `keys.serper` (or server `SERPER_API_KEY`), and streams the\nresults via Server-Sent Events (`text/event-stream`).\n\n1. First emits an `images` event with the full search result list (including `imageUrl`).\n2. Then streams `thumb` events with WebP base64 thumbnails (max 200×200) as they are generated.\n3. Emits `done` when all thumbnails have completed (or `error` on failure).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "serper/images.md",
"x-sse-events": [
{
"name": "images",
"description": "First event; list of search result images with id, url, width, and height",
"payload": "{ \"images\": [ { \"id\": 0, \"url\": \"…\", \"width\": 1000, \"height\": 800 } ] }"
},
{
"name": "thumb",
"description": "Emitted for each image as its 200×200 max WebP thumbnail is generated",
"payload": "{ \"id\": 0, \"dataUrl\": \"data:image/webp;base64,…\" }"
},
{
"name": "done",
"description": "Emitted after all thumbnails have finished streaming; closes the SSE stream",
"payload": "{ \"total\": 100 }"
},
{
"name": "error",
"description": "Emitted when an error occurs during Serper request or processing",
"payload": "{ \"error\": \"…\" }"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"q"
],
"properties": {
"q": {
"type": "string",
"description": "Image search query (e.g. \"Purize logo\")"
},
"gl": {
"type": "string",
"default": "de",
"description": "Country code"
},
"hl": {
"type": "string",
"default": "de",
"description": "Language code"
},
"num": {
"type": "integer",
"default": 100,
"minimum": 1,
"maximum": 100,
"description": "Number of search results to fetch"
},
"preferSource": {
"type": "boolean",
"default": false,
"description": "Whether to prefer downloading from the full imageUrl before falling back to Google thumbnailUrl"
}
},
"example": {
"q": "Purize logo",
"gl": "de",
"hl": "de",
"num": 100
}
}
}
}
},
"responses": {
"200": {
"description": "`text/event-stream` — emits `images`, `thumb`, `done` (and `error` on failure).\n",
"content": {
"text/event-stream": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "Query missing, invalid input, or Serper API key not configured",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/serper/image": {
"post": {
"tags": [
"Serper"
],
"summary": "Crawl image from URL, crop white/transparency borders, and return AVIF",
"description": "Fetches the image at the provided `url`, crops surrounding white and\ntransparent border margins, resizes to max 1000px, and returns the AVIF image.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "serper/image.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"type": "string",
"description": "Absolute HTTP/HTTPS URL of the image to crawl",
"example": "https://example.com/logo.png"
}
}
}
}
}
},
"responses": {
"200": {
"description": "AVIF image bytes",
"headers": {
"Content-Type": {
"schema": {
"type": "string",
"example": "image/avif"
}
},
"X-Image-Width": {
"schema": {
"type": "integer",
"example": 540
}
},
"X-Image-Height": {
"schema": {
"type": "integer",
"example": 179
}
}
},
"content": {
"image/avif": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"400": {
"description": "URL missing, invalid input, or image processing error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"502": {
"description": "Bad Gateway (crawling the upstream image URL failed)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/quote": {
"get": {
"tags": [
"Quote"
],
"summary": "List quotes",
"description": "Lists quotes (`manifest` rows with `type = 'quote'`)\nfor the authenticated user's selected company.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "quote/list.md",
"responses": {
"200": {
"description": "Quote list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"quotes": {
"type": "array",
"items": {
"$ref": "#/components/schemas/QuoteSummary"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Quote"
],
"summary": "Create quote",
"description": "Creates a quote with header and lines in one transaction.\n`date` is the document date (valid-until).\n`entityId` must be a company customer (not Lieferanten/POS), or null.\nEach line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "quote/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"number",
"date",
"lines"
],
"properties": {
"number": {
"type": "string"
},
"date": {
"type": "string",
"format": "date-time",
"description": "Document date / valid-until"
},
"entityId": {
"type": "integer",
"nullable": true,
"description": "Customer entity (manifest.entity_id)"
},
"lines": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ManifestLineInput"
}
}
},
"example": {
"number": "Q-2026-001",
"date": "2026-08-31",
"entityId": 1,
"lines": [
{
"sku": "1",
"name": "Sample Article",
"pu": 1,
"amount": 10,
"net": 99.5,
"taxrate": 19
}
]
}
}
}
}
},
"responses": {
"201": {
"description": "Created quote",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"quote": {
"$ref": "#/components/schemas/Quote"
}
}
}
}
}
},
"400": {
"description": "Invalid quote, invalid lines, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company, entity, or article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/quote/{id}": {
"get": {
"tags": [
"Quote"
],
"summary": "Get quote",
"description": "Returns a quote with lines for the selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "quote/get.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Quote",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"quote": {
"$ref": "#/components/schemas/Quote"
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Quote or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"patch": {
"tags": [
"Quote"
],
"summary": "Update quote",
"description": "Patches header fields that are present. If `lines` is present, replaces\nall lines; if omitted, leaves lines unchanged. Returns the full quote.\n`date` is the document date (valid-until).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "quote/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"number": {
"type": "string"
},
"date": {
"type": "string",
"format": "date-time",
"description": "Document date / valid-until"
},
"entityId": {
"type": "integer",
"nullable": true
},
"lines": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ManifestLineInput"
}
}
},
"example": {
"number": "Q-2026-001b",
"lines": []
}
}
}
}
},
"responses": {
"200": {
"description": "Updated quote",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"quote": {
"$ref": "#/components/schemas/Quote"
}
}
}
}
}
},
"400": {
"description": "Invalid quote, invalid lines, invalid id, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Quote, entity, article, or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Quote"
],
"summary": "Delete quote",
"description": "Deletes the quote (`manifestarticle` rows cascade).",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "quote/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Quote not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/printer": {
"get": {
"tags": [
"Printer"
],
"summary": "List printers",
"description": "Lists printers for the selected company, ordered by sort and id.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "printer/list.md",
"responses": {
"200": {
"description": "List of printers",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"printers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Printer"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Printer"
],
"summary": "Create printer",
"description": "Creates a new printer for the selected company.\n`type` must be `\"esc/pos\"` (lines), `\"zpl\"` (pages), or `\"ipp\"`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "printer/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrinterCreateInput"
}
}
}
},
"responses": {
"201": {
"description": "Created printer",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"printer": {
"$ref": "#/components/schemas/Printer"
}
}
}
}
}
},
"400": {
"description": "Invalid input or missing fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/printer/order": {
"put": {
"tags": [
"Printer"
],
"summary": "Reorder printers",
"description": "Reorders printers for the selected company.\n`printerIds` must include all existing printer IDs exactly once.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "printer/order.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"printerIds"
],
"properties": {
"printerIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Applied printer order",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"printerIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"400": {
"description": "Invalid printer order or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/printer/{id}": {
"get": {
"tags": [
"Printer"
],
"summary": "Get printer by ID",
"description": "Retrieves a single printer for the authenticated user's selected company.",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Printer ID"
}
],
"x-markdown": "printer/get.md",
"responses": {
"200": {
"description": "Printer details",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"printer": {
"$ref": "#/components/schemas/Printer"
}
}
}
}
}
},
"400": {
"description": "Invalid ID or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Printer not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"patch": {
"tags": [
"Printer"
],
"summary": "Update printer (partial)",
"description": "Updates specified fields of an existing printer for the selected company.",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Printer ID"
}
],
"x-markdown": "printer/update.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrinterUpdateInput"
}
}
}
},
"responses": {
"200": {
"description": "Updated printer",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"printer": {
"$ref": "#/components/schemas/Printer"
}
}
}
}
}
},
"400": {
"description": "Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Printer not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"put": {
"tags": [
"Printer"
],
"summary": "Update printer",
"description": "Updates specified fields of an existing printer for the selected company.",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Printer ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrinterUpdateInput"
}
}
}
},
"responses": {
"200": {
"description": "Updated printer",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"printer": {
"$ref": "#/components/schemas/Printer"
}
}
}
}
}
},
"400": {
"description": "Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Printer not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Printer"
],
"summary": "Delete printer",
"description": "Deletes a printer for the authenticated user's selected company.",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Printer ID"
}
],
"x-markdown": "printer/delete.md",
"responses": {
"204": {
"description": "Printer deleted"
},
"400": {
"description": "Invalid ID or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Printer not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/posterminal": {
"get": {
"tags": [
"Posterminal"
],
"summary": "List POS terminals",
"description": "Lists POS terminals for the authenticated user's selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "posterminal/list.md",
"responses": {
"200": {
"description": "Posterminal list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"posterminals": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Posterminal"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"put": {
"tags": [
"Posterminal"
],
"summary": "Create POS terminal",
"description": "Creates a new POS terminal for the selected company.\nThe pairingpin is randomly generated server-side and returned in the response.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "posterminal/upsert.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
},
"example": {
"name": "Kassa 1"
}
}
}
}
},
"responses": {
"200": {
"description": "Created posterminal with generated pairingpin",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"posterminal": {
"$ref": "#/components/schemas/Posterminal"
}
}
}
}
}
},
"400": {
"description": "Invalid posterminal or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/posterminal/{id}": {
"delete": {
"tags": [
"Posterminal"
],
"summary": "Delete POS terminal",
"description": "Deletes a POS terminal for the selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "posterminal/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Posterminal not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/posterminal/{id}/category/{categoryId}": {
"put": {
"tags": [
"Posterminal"
],
"summary": "Enable category on terminal",
"description": "Toggles a single category's visibility on a terminal (idempotent).",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "posterminal/setOn.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"in": "path",
"name": "categoryId",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Updated category IDs for this terminal",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"categoryIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid terminal/category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Posterminal not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Posterminal"
],
"summary": "Disable category on terminal",
"description": "Removes a single category's visibility on a terminal (idempotent).",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "posterminal/setOff.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"in": "path",
"name": "categoryId",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Updated category IDs for this terminal",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"categoryIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid terminal/category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Posterminal not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/posterminal/{id}/all": {
"put": {
"tags": [
"Posterminal"
],
"summary": "Enable all categories on terminal",
"description": "Walks the company's category tree and inserts any missing links\nfor this terminal. Returns the updated category IDs.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "posterminal/setOnAll.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Updated category IDs for this terminal",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"categoryIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid terminal",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Posterminal not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Posterminal"
],
"summary": "Disable all categories on terminal",
"description": "Removes all category links for this terminal.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "posterminal/setOffAll.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Updated category IDs for this terminal",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"categoryIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid terminal",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Posterminal not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/image/{fullHash}": {
"get": {
"tags": [
"Image"
],
"summary": "Full image by hash",
"description": "Returns the full AVIF by `pic.hash` (SHA-256 hex).\nAuth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "image/get.md",
"parameters": [
{
"in": "path",
"name": "fullHash",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-fA-F0-9]{64}$"
},
"description": "SHA-256 hex of the full image bytes"
}
],
"responses": {
"200": {
"description": "AVIF image bytes",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"example": "private, max-age=31536000, immutable"
}
},
"ETag": {
"schema": {
"type": "string"
}
}
},
"content": {
"image/avif": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"304": {
"description": "Not modified (If-None-Match matched ETag)"
},
"400": {
"description": "Invalid hash",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Image not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/thumb/{fullHash}": {
"get": {
"tags": [
"Image"
],
"summary": "Thumb image by hash",
"description": "Returns the thumb AVIF for `pic.hash` (fullHash — not thumbhash).\nAuth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "thumb/get.md",
"parameters": [
{
"in": "path",
"name": "fullHash",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-fA-F0-9]{64}$"
},
"description": "SHA-256 hex of the full image bytes"
}
],
"responses": {
"200": {
"description": "AVIF thumb bytes",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"example": "private, max-age=31536000, immutable"
}
},
"ETag": {
"schema": {
"type": "string"
}
}
},
"content": {
"image/avif": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"304": {
"description": "Not modified (If-None-Match matched ETag)"
},
"400": {
"description": "Invalid hash",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Image not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/order": {
"get": {
"tags": [
"Order"
],
"summary": "List orders",
"description": "Lists sales orders (`manifest` rows with `type = 'order'`)\nfor the authenticated user's selected company.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "order/list.md",
"responses": {
"200": {
"description": "Order list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"orders": {
"type": "array",
"items": {
"$ref": "#/components/schemas/OrderSummary"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Order"
],
"summary": "Create order",
"description": "Creates a sales order with header and lines in one transaction.\n`date` is the document date (expected delivery).\n`entityId` must be a company customer (not Lieferanten/POS), or null.\nEach line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "order/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"number",
"date",
"lines"
],
"properties": {
"number": {
"type": "string"
},
"date": {
"type": "string",
"format": "date-time",
"description": "Document date / expected delivery"
},
"entityId": {
"type": "integer",
"nullable": true,
"description": "Customer entity (manifest.entity_id)"
},
"lines": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ManifestLineInput"
}
}
},
"example": {
"number": "O-2026-001",
"date": "2026-09-15",
"entityId": 1,
"lines": [
{
"sku": "1",
"name": "Sample Article",
"pu": 1,
"amount": 10,
"net": 99.5,
"taxrate": 19
}
]
}
}
}
}
},
"responses": {
"201": {
"description": "Created order",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"order": {
"$ref": "#/components/schemas/Order"
}
}
}
}
}
},
"400": {
"description": "Invalid order, invalid lines, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company, entity, or article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/order/{id}": {
"get": {
"tags": [
"Order"
],
"summary": "Get order",
"description": "Returns a sales order with lines for the selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "order/get.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Order",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"order": {
"$ref": "#/components/schemas/Order"
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Order or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"patch": {
"tags": [
"Order"
],
"summary": "Update order",
"description": "Patches header fields that are present. If `lines` is present, replaces\nall lines; if omitted, leaves lines unchanged. Returns the full order.\n`date` is the document date (expected delivery).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "order/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"number": {
"type": "string"
},
"date": {
"type": "string",
"format": "date-time",
"description": "Document date / expected delivery"
},
"entityId": {
"type": "integer",
"nullable": true
},
"lines": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ManifestLineInput"
}
}
},
"example": {
"number": "O-2026-001b",
"lines": []
}
}
}
}
},
"responses": {
"200": {
"description": "Updated order",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"order": {
"$ref": "#/components/schemas/Order"
}
}
}
}
}
},
"400": {
"description": "Invalid order, invalid lines, invalid id, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Order, entity, article, or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Order"
],
"summary": "Delete order",
"description": "Deletes the sales order (`manifestarticle` rows cascade).",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "order/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Order not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/number": {
"get": {
"tags": [
"Number"
],
"summary": "List number sequences",
"description": "Lists the number sequences of the authenticated user's selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "number/list.md",
"responses": {
"200": {
"description": "Number sequence list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"numbers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NumberSequence"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/number/{id}": {
"patch": {
"tags": [
"Number"
],
"summary": "Update number sequence",
"description": "Updates sequence settings for the selected company. Only provided fields\nare changed (`prefix`, `padding`, `next`). `prefix` is trimmed and must\nbe at most 50 characters. `padding` must be an integer `>= 0`. `next`\nmust be an integer `>= 1`. If no field is provided, the row is returned\nunchanged.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "number/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"prefix": {
"type": "string"
},
"padding": {
"type": "integer",
"minimum": 0
},
"next": {
"type": "integer",
"minimum": 1
}
},
"example": {
"prefix": "A-",
"padding": 4,
"next": 100
}
}
}
}
},
"responses": {
"200": {
"description": "Updated number sequence",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"number": {
"$ref": "#/components/schemas/NumberSequence"
}
}
}
}
}
},
"400": {
"description": "Invalid number, invalid id, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Number sequence or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/ledger": {
"post": {
"tags": [
"Ledger"
],
"summary": "Add ledger entry",
"description": "Appends a new entry to the append-only ledger table.\n`hash_version` is fixed to 1. `prev_hash` is cryptographically chained:\nfor the genesis row it defaults to `TODO_TSE_BASED_INITHASH`, and for subsequent rows\nit is the SHA-512 hex hash of the canonical JSON representation of the previous highest-id entry.\nRequires authentication and membership in the company owning the referenced manifest article.\n",
"security": [
{
"cookieAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LedgerEntryInput"
},
"example": {
"manifestarticle_id": 1,
"transaction_type": "PAYMENT",
"amount": 150,
"transaction_text": "Payment received for invoice"
}
}
}
},
"responses": {
"201": {
"description": "Ledger entry added successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ledger": {
"$ref": "#/components/schemas/LedgerEntry"
}
}
}
}
}
},
"400": {
"description": "Invalid ledger entry or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Manifest article or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"get": {
"tags": [
"Ledger"
],
"summary": "Query ledger by manifest article ID or SKU",
"description": "Alternative query endpoint supporting `?manifestarticle_id=...` or `?sku=...`,\nwith optional `?transaction_type=...`.\n",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "query",
"name": "manifestarticle_id",
"schema": {
"type": "integer"
},
"description": "Query entries for this manifest article line ID"
},
{
"in": "query",
"name": "sku",
"schema": {
"type": "string"
},
"description": "Query entries for this article SKU"
},
{
"in": "query",
"name": "transaction_type",
"schema": {
"type": "string"
},
"description": "Optional transaction type filter"
}
],
"responses": {
"200": {
"description": "List of ledger entries",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ledger": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LedgerEntry"
}
}
}
}
}
}
},
"400": {
"description": "Missing required query parameter or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/ledger/manifestarticle/{manifestarticleId}": {
"get": {
"tags": [
"Ledger"
],
"summary": "Query ledger by manifest article ID",
"description": "Lists ledger entries for a specific manifest article line, optionally filtered by transaction type.",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "manifestarticleId",
"required": true,
"schema": {
"type": "integer"
},
"description": "Manifest article line ID"
},
{
"in": "query",
"name": "transaction_type",
"schema": {
"type": "string"
},
"description": "Optional transaction type filter"
}
],
"responses": {
"200": {
"description": "List of ledger entries",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ledger": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LedgerEntry"
}
}
}
}
}
}
},
"400": {
"description": "Invalid manifest article id or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Manifest article or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/ledger/sku/{sku}": {
"get": {
"tags": [
"Ledger"
],
"summary": "Query ledger by article SKU",
"description": "Lists ledger entries for an article SKU (joined from manifestarticle), optionally filtered by transaction type.",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
},
{
"in": "query",
"name": "transaction_type",
"schema": {
"type": "string"
},
"description": "Optional transaction type filter"
}
],
"responses": {
"200": {
"description": "List of ledger entries",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ledger": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LedgerEntry"
}
}
}
}
}
}
},
"400": {
"description": "Invalid SKU or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/health": {
"get": {
"tags": [
"Health"
],
"summary": "Database health check",
"description": "Verifies the database client can establish a connection.",
"security": [],
"x-markdown": "health.md",
"responses": {
"200": {
"description": "Database reachable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean",
"example": true
}
}
}
}
}
},
"503": {
"description": "Database unreachable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean",
"example": false
},
"error": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/events": {
"get": {
"tags": [
"Events"
],
"summary": "Subscribe to update signals (SSE)",
"description": "Opens a Server-Sent Events stream for the authenticated user's\n**selected company**. Clients receive named signals when another\nrequest successfully mutates shared data for that company\n(including the same browser's other tabs).\n\nUse `EventSource` with credentials so the session cookie is sent:\n\n```js\nconst es = new EventSource('https://…/api/events', { withCredentials: true });\nes.addEventListener('category', () => { refetchCategoryTree(); });\nes.addEventListener('articles', () => { refetchArticleIndex(); });\nes.addEventListener('article-42000045', () => { refetchArticle('42000045'); });\n```\n\nKeep-alive comments (`: heartbeat`) are sent roughly every 20s.\nReconnect after changing the selected company (the stream is bound\nto the company at connect time).\n\n## Signals\n\n| Signal | Emitted after successful |\n|--------|--------------------------|\n| `category` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image` |\n| `articles` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}` |\n| `article-<sku>` | `PUT /article/{sku}/weight`, `PUT /article/{sku}/prices`, `PUT /article/{sku}/eans`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/brand`, `PUT /article/{sku}/images/order`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}` |\n\nSignals are only sent when the mutation actually succeeds (2xx).\nFailed or no-op validation errors do not emit. The `article-<sku>`\nevent name uses the concrete SKU (e.g. `article-42000045`).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "events.md",
"responses": {
"200": {
"description": "SSE stream (`text/event-stream`). Each signal is an SSE event\nwhose name is the signal id and whose data is `{}`.\n",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"example": "event: category\ndata: {}\n\nevent: articles\ndata: {}\n\nevent: article-42000045\ndata: {}\n"
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/entity/{entityId}/contact": {
"get": {
"tags": [
"Entity"
],
"summary": "List entity contacts",
"description": "Lists contacts for an entity of the selected company (`contactType` e.g. `email`, `phone`).",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/contact/list.md",
"parameters": [
{
"in": "path",
"name": "entityId",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Contact list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"contacts": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EntityContact"
}
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid entityId",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Entity or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Entity"
],
"summary": "Create entity contact",
"description": "Creates a contact for the entity. `contactType` and `contact` are required.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/contact/create.md",
"parameters": [
{
"in": "path",
"name": "entityId",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contactType",
"contact"
],
"properties": {
"contactType": {
"type": "string",
"description": "e.g. email, phone"
},
"contact": {
"type": "string"
}
},
"example": {
"contactType": "email",
"contact": "anna@example.com"
}
}
}
}
},
"responses": {
"201": {
"description": "Created contact",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"contact": {
"$ref": "#/components/schemas/EntityContact"
}
}
}
}
}
},
"400": {
"description": "Contact required, invalid entityId, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Entity or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/entity/{entityId}/contact/{id}": {
"patch": {
"tags": [
"Entity"
],
"summary": "Update entity contact",
"description": "Patches `contactType` and/or `contact`.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/contact/update.md",
"parameters": [
{
"in": "path",
"name": "entityId",
"required": true,
"schema": {
"type": "integer"
}
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"contactType": {
"type": "string"
},
"contact": {
"type": "string"
}
},
"example": {
"contact": "anna.mueller@example.com"
}
}
}
}
},
"responses": {
"200": {
"description": "Updated contact",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"contact": {
"$ref": "#/components/schemas/EntityContact"
}
}
}
}
}
},
"400": {
"description": "Contact required, invalid ids, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Contact, entity, or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Entity"
],
"summary": "Delete entity contact",
"description": "Deletes one contact of the entity.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/contact/delete.md",
"parameters": [
{
"in": "path",
"name": "entityId",
"required": true,
"schema": {
"type": "integer"
}
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected or invalid ids",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Contact or entity not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/entity/{entityId}/address": {
"get": {
"tags": [
"Entity"
],
"summary": "List entity addresses",
"description": "Lists addresses for an entity of the selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/address/list.md",
"parameters": [
{
"in": "path",
"name": "entityId",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Address list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"addresses": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EntityAddress"
}
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid entityId",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Entity or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Entity"
],
"summary": "Create entity address",
"description": "Creates an address for the entity. `addressType` is required.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/address/create.md",
"parameters": [
{
"in": "path",
"name": "entityId",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"addressType"
],
"properties": {
"addressType": {
"type": "string"
},
"firstName": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true
},
"street": {
"type": "string",
"nullable": true
},
"streetNumber": {
"type": "string",
"nullable": true
},
"zip": {
"type": "string",
"nullable": true
},
"city": {
"type": "string",
"nullable": true
},
"county": {
"type": "string",
"nullable": true
}
},
"example": {
"addressType": "billing",
"street": "Hauptstraße",
"streetNumber": "12",
"zip": "10115",
"city": "Berlin"
}
}
}
}
},
"responses": {
"201": {
"description": "Created address",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"address": {
"$ref": "#/components/schemas/EntityAddress"
}
}
}
}
}
},
"400": {
"description": "Address type required, invalid entityId, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Entity or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/entity/{entityId}/address/{id}": {
"patch": {
"tags": [
"Entity"
],
"summary": "Update entity address",
"description": "Patches address fields that are present.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/address/update.md",
"parameters": [
{
"in": "path",
"name": "entityId",
"required": true,
"schema": {
"type": "integer"
}
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"addressType": {
"type": "string"
},
"firstName": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true
},
"street": {
"type": "string",
"nullable": true
},
"streetNumber": {
"type": "string",
"nullable": true
},
"zip": {
"type": "string",
"nullable": true
},
"city": {
"type": "string",
"nullable": true
},
"county": {
"type": "string",
"nullable": true
}
},
"example": {
"city": "München",
"zip": "80331"
}
}
}
}
},
"responses": {
"200": {
"description": "Updated address",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"address": {
"$ref": "#/components/schemas/EntityAddress"
}
}
}
}
}
},
"400": {
"description": "Address type required, invalid ids, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Address, entity, or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Entity"
],
"summary": "Delete entity address",
"description": "Deletes one address of the entity.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/address/delete.md",
"parameters": [
{
"in": "path",
"name": "entityId",
"required": true,
"schema": {
"type": "integer"
}
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected or invalid ids",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Address or entity not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/entity": {
"get": {
"tags": [
"Entity"
],
"summary": "List entities",
"description": "Lists entities for the selected company. Optional `customergroupId`\nfilters to one group. Optional `role=customer` excludes Lieferanten/POS\ngroups (for quote/order pickers). `role` wins when both are set.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/list.md",
"parameters": [
{
"in": "query",
"name": "customergroupId",
"required": false,
"schema": {
"type": "integer"
}
},
{
"in": "query",
"name": "role",
"required": false,
"schema": {
"type": "string",
"enum": [
"customer"
]
}
}
],
"responses": {
"200": {
"description": "Entity list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"entities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Entity"
}
}
}
}
}
}
},
"400": {
"description": "No company selected, invalid customergroupId, or invalid role",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company or customer group not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Entity"
],
"summary": "Create entity",
"description": "Creates an entity in a company customer group and writes `entityhistory` (`Erstellt`).\nOptional `number`: omit to allocate the next customer number (`type` entity).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"customergroupId",
"name"
],
"properties": {
"customergroupId": {
"type": "integer"
},
"name": {
"type": "string"
},
"firstName": {
"type": "string",
"nullable": true
},
"number": {
"type": "string",
"description": "Customer number; omit to auto-allocate"
}
},
"example": {
"customergroupId": 1,
"name": "Müller",
"firstName": "Anna",
"number": "10042"
}
}
}
}
},
"responses": {
"201": {
"description": "Created entity",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"entity": {
"$ref": "#/components/schemas/Entity"
}
}
}
}
}
},
"400": {
"description": "Name/number required or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company or customer group not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Number already in use",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/entity/next-number": {
"get": {
"tags": [
"Entity"
],
"summary": "Peek next customer number",
"description": "Returns the next customer number that would be allocated (`type` entity)\nwithout consuming it. Use on the customer add form.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/next-number.md",
"responses": {
"200": {
"description": "Next number",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"number"
],
"properties": {
"number": {
"type": "string"
}
}
},
"example": {
"number": "1"
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/entity/{id}": {
"get": {
"tags": [
"Entity"
],
"summary": "Get entity",
"description": "Returns one entity for the selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/get.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Entity",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"entity": {
"$ref": "#/components/schemas/Entity"
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Entity or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"patch": {
"tags": [
"Entity"
],
"summary": "Update entity",
"description": "Patches fields that are present (`name`, `firstName`, `number`).\n`number` is only changed when the key is present (no auto-allocate).\nWrites `entityhistory` when values change.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"firstName": {
"type": "string",
"nullable": true
},
"number": {
"type": "string",
"nullable": true
}
},
"example": {
"name": "Müller",
"firstName": "Anna",
"number": "10042"
}
}
}
}
},
"responses": {
"200": {
"description": "Updated entity",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"entity": {
"$ref": "#/components/schemas/Entity"
}
}
}
}
}
},
"400": {
"description": "Name/number required, invalid id, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Entity or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Number already in use",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Entity"
],
"summary": "Delete entity",
"description": "Deletes the entity (`entityhistory` cascades; related addresses/contacts cascade).",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "entity/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Entity not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/delivery/parse": {
"post": {
"tags": [
"Delivery"
],
"summary": "Parse document (SSE)",
"description": "Accepts an invoice or delivery note as PDF or text file\n(`multipart/form-data`, field `file`) and streams extraction results as\nServer-Sent Events until parsing is complete.\n\nPDFs are read via `pdftotext -layout`; text files are decoded as UTF-8.\nPages are separated by the form feed character (`\\f`). If no form feed\nis present, the input is treated as a single page.\n\nThe extracted pages are fed to an LLM (OpenRouter) which fills a structured\nmanifest of header data and line items. The OpenRouter API key is read from\nthe authenticated user's company setting `keys.openrouter` (or the server's\n`OPENROUTER_FREE_KEY` if not configured); the endpoint\nreturns `400` if the key is not configured.\n\nThe stream ends after the final `result` event (or an `error` event on failure).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "delivery/parse.md",
"x-sse-events": [
{
"name": "status",
"description": "Document parse confirmation and LLM status messages",
"payload": "{ \"message\": \"…\" }"
},
{
"name": "llm_call",
"description": "Fired before each LLM request",
"payload": "{ \"model\": \"…\", \"promptSize\": 0, \"messages\": 0 }"
},
{
"name": "tool_call",
"description": "Fired when the output_manifest tool is invoked",
"payload": "{ \"name\": \"output_manifest\" }"
},
{
"name": "usage",
"description": "Token usage after each LLM response",
"payload": "{ \"input\": 0, \"cached\": 0, \"output\": 0, \"cost\": 0 }"
},
{
"name": "progress",
"description": "Intermediate accumulated extraction state (basedata / notes / items)",
"payload": "ManifestResult"
},
{
"name": "result",
"description": "Final accumulated extraction — emitted just before the stream closes",
"payload": "ManifestResult"
},
{
"name": "error",
"description": "Emitted on document parse or LLM failure; stream closes immediately after",
"payload": "{ \"message\": \"…\" }"
}
],
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"required": [
"file"
],
"properties": {
"file": {
"type": "string",
"format": "binary",
"description": "Document to parse (max 50 MB, application/pdf or text file)"
}
}
}
}
}
},
"responses": {
"200": {
"description": "`text/event-stream` — emits `status`, `llm_call`, `tool_call`, `usage`,\n`progress`, `result` (and `error` on failure). See SSE Events table below.\n\n**ManifestResult shape** (used by `progress` and `result`):\n\n```json\n{\n \"basedata\": {\n \"date\": \"2026-01-15\",\n \"lieferant_id\": 42,\n \"lieferantName\": \"Muster GmbH\",\n \"manifestNr\": \"LS-2026-001\",\n \"manifestType\": \"lieferschein\"\n },\n \"notes\": [\"Pos. 3: VPE-Angabe fehlte, Standardwert 1 angenommen.\"],\n \"items\": {\n \"pagesFrom\": 1,\n \"pagesTo\": 2,\n \"items\": [\n {\n \"position\": 1,\n \"count\": 10,\n \"vpe\": 1,\n \"unit\": \"Stk.\",\n \"articleName\": \"Einhängesieb Stahl\",\n \"liefArtNr\": \"ES-100\",\n \"mwst\": \"19\",\n \"eknetto\": 450,\n \"page\": 1\n }\n ]\n }\n}\n```\n",
"content": {
"text/event-stream": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "No file uploaded, unsupported file type, or OpenRouter key not configured",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/delivery": {
"get": {
"tags": [
"Delivery"
],
"summary": "List deliveries",
"description": "Lists goods receipts (`manifest` rows with `type = 'delivery'`)\nfor the authenticated user's selected company.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "delivery/list.md",
"responses": {
"200": {
"description": "Delivery list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"deliveries": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DeliverySummary"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Delivery"
],
"summary": "Create delivery",
"description": "Creates a goods receipt with header and lines in one transaction.\nThe manifest `number` is allocated automatically via\n`nextNumber('delivery')`; the user-supplied reference is stored as\n`externalNumber`. Each line requires `sku` or `name`, `amount`, `net`, and\n`taxrate` (rate). `pu` defaults to `1`; `rebate` defaults to `0`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "delivery/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"externalNumber",
"date",
"lines"
],
"properties": {
"externalNumber": {
"type": "string",
"description": "User-supplied reference number"
},
"date": {
"type": "string",
"format": "date-time"
},
"supplierId": {
"type": "integer",
"nullable": true
},
"lines": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DeliveryLineInput"
}
},
"files": {
"type": "array",
"description": "Attached documents (pdf or txt) stored with the delivery\nand returned in `files` on every delivery response.\nThumbnails (AVIF) are generated server-side.\n",
"items": {
"$ref": "#/components/schemas/DeliveryFileInput"
}
}
},
"example": {
"externalNumber": "DHL-123",
"date": "2026-08-10",
"supplierId": 1,
"lines": [
{
"sku": "1",
"name": "Sample Article",
"pu": 1,
"amount": 10,
"net": 99.5,
"taxrate": 19
}
],
"files": [
{
"mimetype": "application/pdf",
"name": "delivery-note.pdf",
"content": "<base64 encoded content>"
}
]
}
}
},
"multipart/form-data": {
"schema": {
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"type": "string",
"description": "JSON string containing manifest header and line items ({ externalNumber, date, supplierId, lines, filesMeta? }).\n"
},
"files": {
"type": "array",
"items": {
"type": "string",
"format": "binary"
},
"description": "Raw binary PDF or text files attached natively via standard HTTP multipart upload."
}
}
}
}
}
},
"responses": {
"201": {
"description": "Created delivery",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"delivery": {
"$ref": "#/components/schemas/Delivery"
}
}
}
}
}
},
"400": {
"description": "Invalid delivery, invalid lines or files, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company or supplier not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/delivery/{id}": {
"get": {
"tags": [
"Delivery"
],
"summary": "Get delivery",
"description": "Returns a goods receipt with lines for the selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "delivery/get.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Delivery",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"delivery": {
"$ref": "#/components/schemas/Delivery"
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Delivery or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"patch": {
"tags": [
"Delivery"
],
"summary": "Update delivery",
"description": "Patches header fields that are present. The manifest `number` is\nauto-allocated and cannot be patched; only the user-supplied\n`externalNumber` reference is editable. If `lines` is present,\nreplaces all lines; if omitted, leaves lines unchanged.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "delivery/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"externalNumber": {
"type": "string",
"nullable": true,
"description": "User-supplied reference number (empty/null clears it)"
},
"date": {
"type": "string",
"format": "date-time"
},
"supplierId": {
"type": "integer",
"nullable": true
},
"lines": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DeliveryLineInput"
}
},
"files": {
"type": "array",
"description": "Appends attachments (pdf or txt) to the delivery.\nOmit to leave existing files unchanged.\nThumbnails (AVIF) are generated server-side.\n",
"items": {
"$ref": "#/components/schemas/DeliveryFileInput"
}
}
},
"example": {
"externalNumber": "DHL-124",
"lines": []
}
}
},
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "string",
"description": "JSON string containing patch fields ({ externalNumber, date, supplierId, lines, filesMeta? }).\n"
},
"files": {
"type": "array",
"items": {
"type": "string",
"format": "binary"
},
"description": "Raw binary PDF or text files to append to the delivery."
}
}
}
}
}
},
"responses": {
"200": {
"description": "Updated delivery",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"delivery": {
"$ref": "#/components/schemas/Delivery"
}
}
}
}
}
},
"400": {
"description": "Invalid delivery, invalid lines, invalid id, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Delivery, supplier, or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Delivery"
],
"summary": "Delete delivery",
"description": "Deletes the goods receipt (`manifestarticle` rows cascade).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "delivery/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Delivery not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/delivery/{id}/file/{fileId}": {
"get": {
"tags": [
"Delivery"
],
"summary": "Get delivery attachment file content",
"description": "Returns the full binary content of an attached file (e.g. PDF or text document)\nfor the specified delivery. Supports HTTP caching via ETag (SHA-256 hash) and 304 Not Modified.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "delivery/file.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Delivery (manifest) ID"
},
{
"in": "path",
"name": "fileId",
"required": true,
"schema": {
"type": "integer"
},
"description": "File ID or attachment ID"
},
{
"in": "query",
"name": "download",
"required": false,
"schema": {
"type": "boolean"
},
"description": "When true, sets Content-Disposition to attachment instead of inline"
}
],
"responses": {
"200": {
"description": "Full binary file content",
"headers": {
"Content-Type": {
"schema": {
"type": "string",
"example": "application/pdf"
}
},
"Content-Disposition": {
"schema": {
"type": "string",
"example": "inline; filename=\"document.pdf\""
}
},
"ETag": {
"schema": {
"type": "string"
}
},
"Cache-Control": {
"schema": {
"type": "string",
"example": "private, max-age=86400"
}
}
},
"content": {
"application/pdf": {
"schema": {
"type": "string",
"format": "binary"
}
},
"text/plain": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"304": {
"description": "Not modified (If-None-Match matched ETag)"
},
"400": {
"description": "Invalid id, invalid fileId, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Delivery or file not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/customergroup": {
"get": {
"tags": [
"Customergroup"
],
"summary": "List customer groups",
"description": "Lists customer groups for the selected company, ordered by `sort`.\nEach item includes `editable`: `false` for implicit `POS` / `Lieferanten`\n(name locked), `true` otherwise. Reorder via `PUT /customergroup/order`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "customergroup/list.md",
"responses": {
"200": {
"description": "Customer group list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"customergroups": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CustomergroupListItem"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Customergroup"
],
"summary": "Create customer group",
"description": "Creates a customer group for the selected company.\nAppends with the next `sort` index. Reserved names `POS` and `Lieferanten`\nare not allowed (`Name not allowed`).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "customergroup/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
},
"example": {
"name": "Kunden"
}
}
}
}
},
"responses": {
"201": {
"description": "Created customer group",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"customergroup": {
"$ref": "#/components/schemas/Customergroup"
}
}
}
}
}
},
"400": {
"description": "Name required, name not allowed, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/customergroup/order": {
"put": {
"tags": [
"Customergroup"
],
"summary": "Reorder customer groups",
"description": "Sets `sort` to each id’s index `0…n-1` in one transaction.\n`customergroupIds` must be exactly the company’s groups (same set, no dups)\nelse `Invalid customer group order`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "customergroup/order.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"customergroupIds"
],
"properties": {
"customergroupIds": {
"type": "array",
"items": {
"type": "integer"
}
}
},
"example": {
"customergroupIds": [
4,
3,
1,
2
]
}
}
}
}
},
"responses": {
"200": {
"description": "New order",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"customergroupIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"400": {
"description": "Invalid customer group order or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/customergroup/{id}": {
"patch": {
"tags": [
"Customergroup"
],
"summary": "Update customer group",
"description": "Renames a customer group. Implicit `POS` / `Lieferanten` cannot be\nrenamed (`Name cannot be changed`). Reorder via `PUT /customergroup/order`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "customergroup/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
},
"example": {
"name": "Stammkunden"
}
}
}
}
},
"responses": {
"200": {
"description": "Updated customer group",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"customergroup": {
"$ref": "#/components/schemas/Customergroup"
}
}
}
}
}
},
"400": {
"description": "Name required, name cannot be changed, invalid id, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Customer group or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Customergroup"
],
"summary": "Delete customer group",
"description": "Deletes the customer group (cascades entities). Fails with\n`Customer group in use` for the POS group or other NO ACTION references.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "customergroup/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected or invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Customer group not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Customer group in use",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/company/setting": {
"get": {
"tags": [
"CompanySetting"
],
"summary": "List company settings",
"description": "Returns the visible settings of the authenticated user's selected\ncompany: the full set of allowed keys (with `value: null` when unset)\nplus any stored settings that are frontend-visible. The `frontend`\nflag is stripped from responses.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "companysetting/list.md",
"responses": {
"200": {
"description": "Company settings",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"settings": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanySetting"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/company/setting/{key}": {
"get": {
"tags": [
"CompanySetting"
],
"summary": "Get company setting by key",
"description": "Returns a single company setting for the authenticated user's selected\ncompany, or `404` if it does not exist.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "companysetting/get.md",
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company setting",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"setting": {
"$ref": "#/components/schemas/CompanySetting"
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company setting or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"put": {
"tags": [
"CompanySetting"
],
"summary": "Upsert company setting",
"description": "Sets (upserts) a company setting for the selected company.\nOnly a fixed allow-list of keys can be set: `ui.theme`, `keys.tavily`,\n`keys.openrouter`, `keys.serper`, `labelprinter.addr`, `labelprinter.type`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "companysetting/set.md",
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string"
},
"frontend": {
"type": "boolean"
}
},
"example": {
"value": "dark",
"frontend": true
}
}
}
}
},
"responses": {
"200": {
"description": "Upserted company setting",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"setting": {
"$ref": "#/components/schemas/CompanySetting"
}
}
}
}
}
},
"400": {
"description": "Invalid setting, invalid key, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/company": {
"get": {
"tags": [
"Company"
],
"summary": "List companies",
"description": "Lists companies for the authenticated user.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "company/list.md",
"responses": {
"200": {
"description": "Company list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Company"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Company"
],
"summary": "Create company",
"description": "Creates a company and selects it for the current user.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "company/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"iso639",
"name",
"taxrate",
"reducedtaxrate"
],
"properties": {
"iso639": {
"type": "string"
},
"name": {
"type": "string"
},
"taxrate": {
"type": "number"
},
"reducedtaxrate": {
"type": "number"
}
},
"example": {
"iso639": "de",
"name": "Acme GmbH",
"taxrate": 19,
"reducedtaxrate": 7
}
}
}
}
},
"responses": {
"201": {
"description": "Created; selected company updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"company": {
"$ref": "#/components/schemas/Company"
},
"user": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Company name not available",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/company/{id}": {
"put": {
"tags": [
"Company"
],
"summary": "Update company",
"description": "Updates a company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "company/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"iso639",
"name",
"taxrate",
"reducedtaxrate"
],
"properties": {
"iso639": {
"type": "string"
},
"name": {
"type": "string"
},
"taxrate": {
"type": "number"
},
"reducedtaxrate": {
"type": "number"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"company": {
"$ref": "#/components/schemas/Company"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Company name not available",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Company"
],
"summary": "Delete company",
"description": "Deletes a company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "company/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category/tree": {
"get": {
"tags": [
"Category"
],
"summary": "Category tree",
"description": "Returns the category tree for the authenticated user's selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "category/tree.md",
"responses": {
"200": {
"description": "Tree and articles for the selected company",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"languageId": {
"type": "integer"
},
"tree": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
},
"articles": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category/export.zip": {
"get": {
"tags": [
"Category"
],
"summary": "Export categories as ZIP",
"description": "Builds a `kategorien.zip` for the authenticated user's selected company.\nThe ZIP contains `kategorien.csv` (semicolon-separated, Windows-1252,\ndepth-first in sort order with an `Ebene N` name column per tree depth)\nplus one AVIF entry per category image, named `<fullHash>.avif`\n(content-addressed, so shared hashes are deduplicated).\n",
"security": [
{
"cookieAuth": []
}
],
"responses": {
"200": {
"description": "ZIP archive",
"content": {
"application/zip": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category": {
"post": {
"tags": [
"Category"
],
"summary": "Create category",
"description": "Creates a category under an authorized parent.",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"category"
],
"x-markdown": "category/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"parentId",
"name"
],
"properties": {
"parentId": {
"type": "integer"
},
"name": {
"type": "string"
}
},
"example": {
"parentId": 1,
"name": "New category"
}
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"category": {
"$ref": "#/components/schemas/Category"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Parent not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category/{id}/articles": {
"get": {
"tags": [
"Category"
],
"summary": "Articles in category",
"description": "Returns articles in a category the user is authorized for.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "category/articles.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Category id"
}
],
"responses": {
"200": {
"description": "Articles list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"articles": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
}
}
},
"400": {
"description": "Invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category/{id}": {
"patch": {
"tags": [
"Category"
],
"summary": "Update category (rename or move)",
"description": "Updates a category the user is authorized for.\n`name` renames the category (re-slugifies in the company default language).\n`parentId` moves it under a new parent (cycle/self → `Invalid parent`).\nEither `name` or `parentId` (or both) is required.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"category"
],
"x-markdown": "category/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Category id"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"parentId": {
"type": "integer",
"description": "New parent category id"
}
},
"example": {
"parentId": 3
}
}
}
}
},
"responses": {
"200": {
"description": "Updated category (renamed and/or moved)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"category": {
"type": "object",
"required": [
"id",
"name",
"slug",
"parentId",
"sort"
],
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"slug": {
"type": "string"
},
"parentId": {
"type": "integer"
},
"sort": {
"type": "integer"
}
}
}
}
}
}
}
},
"400": {
"description": "Validation error (name or parentId required, or invalid parent)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Category not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Category"
],
"summary": "Delete category",
"description": "Deletes a category the user is authorized for (not the company root).",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"category"
],
"x-markdown": "category/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Category id"
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Invalid id or cannot delete root",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category/order": {
"put": {
"tags": [
"Category"
],
"summary": "Reorder sibling categories",
"description": "Sets the sibling sort order of `categoryIds` under `parentId`.\n`categoryIds` must be exactly the current child set of `parentId`\n(same count, no foreign/duplicate ids). Each child's `sort` is set\nto its index `0..n-1` in one transaction.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"category"
],
"x-markdown": "category/order.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"parentId",
"categoryIds"
],
"properties": {
"parentId": {
"type": "integer",
"description": "Parent category under which sibling order applies"
},
"categoryIds": {
"type": "array",
"items": {
"type": "integer"
}
}
},
"example": {
"parentId": 1,
"categoryIds": [
2,
4,
3,
5
]
}
}
}
}
},
"responses": {
"200": {
"description": "New sibling order",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"categoryIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"400": {
"description": "Invalid category order",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Parent category not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category/{id}/image": {
"post": {
"tags": [
"Category"
],
"summary": "Add category image",
"description": "Accepts a full-size AVIF body (`Content-Type: image/avif`).\nFull: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.\nThumb always scaled to max 200×200. Stores hash/thumbhash, links `categorypic`.\nAuth like GET /category/tree — user must be authorized for the category's company.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"category"
],
"x-markdown": "category/add-image.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Category id"
}
],
"requestBody": {
"required": true,
"content": {
"image/avif": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"responses": {
"201": {
"description": "Image stored and linked",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"category": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"thumbW": {
"type": "integer"
},
"thumbH": {
"type": "integer"
},
"thumbHash": {
"type": "string"
}
}
}
}
}
}
}
},
"400": {
"description": "Missing body or invalid image",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Category not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Category"
],
"summary": "Delete category image",
"description": "Removes the category's image. Sets thumbW, thumbH, thumbHash back to null.\nAuth like GET /category/tree — user must be authorized for the category's company.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"category"
],
"x-markdown": "category/delete-image.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Category id"
}
],
"responses": {
"200": {
"description": "Image removed",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"category": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"thumbW": {
"type": "integer",
"nullable": true
},
"thumbH": {
"type": "integer",
"nullable": true
},
"thumbHash": {
"type": "string",
"nullable": true
}
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Category or image not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/brand": {
"get": {
"tags": [
"Brand"
],
"summary": "List brands",
"description": "Lists brands for the authenticated user's selected company, sorted by name.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "brand/list.md",
"responses": {
"200": {
"description": "Brand list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"brands": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Brand"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"put": {
"tags": [
"Brand"
],
"summary": "Upsert brand",
"description": "Insert or update brand by `name` for the selected company. `gsprpinfo` optional.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "brand/upsert.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"gsprpinfo": {
"type": "string"
}
},
"example": {
"name": "Acme",
"gsprpinfo": "Acme GmbH"
}
}
}
}
},
"responses": {
"200": {
"description": "Upserted brand",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"brand": {
"$ref": "#/components/schemas/Brand"
}
}
}
}
}
},
"400": {
"description": "Invalid brand or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/brand/{id}": {
"delete": {
"tags": [
"Brand"
],
"summary": "Delete brand",
"description": "Deletes the brand for the selected company. Fails with `Brand in use`\nif any article still references it.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "brand/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Brand not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Brand in use",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/supplier": {
"get": {
"tags": [
"Article"
],
"summary": "List article suppliers",
"description": "Fetches all suppliers linked to the specified article SKU.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/supplier/list.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"responses": {
"200": {
"description": "List of suppliers linked to the article",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"suppliers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ArticleSupplier"
}
}
}
}
}
}
},
"400": {
"description": "No company selected or invalid sku",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Article"
],
"summary": "Assign supplier to article",
"description": "Assigns a supplier to an article with optional purchase price, stock, and supplier SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/supplier/create.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"entityId"
],
"properties": {
"entityId": {
"type": "integer",
"description": "Supplier entity ID (in Lieferanten group)"
},
"sku": {
"type": "string",
"nullable": true,
"description": "Supplier's item number"
},
"pu": {
"type": "number",
"default": 1,
"description": "Packaging unit (VPE)"
},
"stock": {
"type": "number",
"nullable": true,
"description": "Supplier stock"
},
"sort": {
"type": "integer",
"nullable": true,
"description": "Display sort order (defaults to next sort)"
},
"netPrice": {
"type": "number",
"nullable": true,
"description": "Net purchase price"
},
"taxId": {
"type": "integer",
"nullable": true,
"description": "Tax ID for purchase price"
},
"taxRate": {
"type": "number",
"nullable": true,
"description": "Tax rate in percent"
}
},
"example": {
"entityId": 88,
"sku": "MET-44910",
"pu": 1,
"stock": 140,
"sort": 0,
"netPrice": 4.2,
"taxId": 1
}
}
}
}
},
"responses": {
"201": {
"description": "Created supplier assignment",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"supplier": {
"$ref": "#/components/schemas/ArticleSupplier"
}
}
}
}
}
},
"400": {
"description": "entityId required, invalid pu, invalid stock, invalid price, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article, supplier, or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Supplier already assigned",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/supplier/{id}": {
"patch": {
"tags": [
"Article"
],
"summary": "Update article supplier assignment",
"description": "Updates an existing article-supplier association (supplier SKU, packaging unit, stock, purchase price).\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/supplier/update.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "articlesupplier assignment ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"sku": {
"type": "string",
"nullable": true
},
"pu": {
"type": "number"
},
"stock": {
"type": "number",
"nullable": true
},
"sort": {
"type": "integer",
"description": "Display sort order"
},
"netPrice": {
"type": "number",
"nullable": true
},
"taxId": {
"type": "integer",
"nullable": true
},
"taxRate": {
"type": "number",
"nullable": true
}
},
"example": {
"sku": "MET-44910-B",
"pu": 6,
"stock": 95,
"netPrice": 22.5
}
}
}
}
},
"responses": {
"200": {
"description": "Updated supplier assignment",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"supplier": {
"$ref": "#/components/schemas/ArticleSupplier"
}
}
}
}
}
},
"400": {
"description": "Invalid pu, invalid stock, invalid price, invalid id, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Supplier assignment, article, or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Article"
],
"summary": "Remove supplier assignment from article",
"description": "Removes a supplier assignment from the article and cleans up its associated purchase price.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/supplier/delete.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "articlesupplier assignment ID"
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Invalid id, invalid sku, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Supplier assignment or article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/supplier/order": {
"put": {
"tags": [
"Article"
],
"summary": "Reorder article suppliers",
"description": "Sets the display sort order of supplier assignments for an article.\nExpects an array of all assigned `articlesupplier` IDs in the desired order.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/supplier/order.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ids"
],
"properties": {
"ids": {
"type": "array",
"items": {
"type": "integer"
},
"description": "Full list of articlesupplier ids in their new order"
}
},
"example": {
"ids": [
12,
10,
15
]
}
}
}
}
},
"responses": {
"200": {
"description": "New order applied",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"400": {
"description": "Invalid supplier order, ids is required, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article": {
"post": {
"tags": [
"Article"
],
"summary": "Create article",
"description": "Creates an article under an authorized category (`parentId`).",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"articles"
],
"x-markdown": "article/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"parentId",
"name"
],
"properties": {
"parentId": {
"type": "integer",
"description": "Category id"
},
"name": {
"type": "string"
}
},
"example": {
"parentId": 10,
"name": "New article"
}
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleSummary"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized for parent category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Parent not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "SKU already exists",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/export.zip": {
"get": {
"tags": [
"Article"
],
"summary": "Export articles as ZIP",
"description": "Builds an `artikel.zip` for the authenticated user's selected company.\n`categoryId` (optional) restricts the export to that category and below;\nit must be reachable via the company's root category. Defaults to the\nwhole company root tree.\nThe ZIP contains `artikel.csv` (semicolon-separated, Windows-1252)\nplus one AVIF entry per article image, named `<fullHash>.avif`\n(content-addressed, so shared hashes are deduplicated).\n",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "query",
"name": "categoryId",
"required": false,
"schema": {
"type": "integer"
},
"description": "Restrict export to this category subtree"
}
],
"responses": {
"200": {
"description": "ZIP archive",
"content": {
"application/zip": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}": {
"get": {
"tags": [
"Article"
],
"summary": "Get article by SKU",
"description": "Returns article detail by SKU if the user may access its category.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/get.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"responses": {
"200": {
"description": "Article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Missing sku",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"patch": {
"tags": [
"Article"
],
"summary": "Update article by SKU",
"description": "Patches article fields. At least one of `name`, `unitamount`, `unitSi` required.\n`unitSi` must already exist for the company. Returns the same detail shape as GET.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"articles"
],
"x-markdown": "article/update.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"unitamount": {
"type": "number",
"description": "Must be finite and `> 0`"
},
"unitSi": {
"type": "string",
"description": "Existing company unit `si`"
}
},
"example": {
"unitamount": 0.24,
"unitSi": "l"
}
}
}
}
},
"responses": {
"200": {
"description": "Updated article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Validation error (Name required / Invalid unitamount)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article or unit not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Article"
],
"summary": "Delete article by SKU",
"description": "Deletes the article when it has no child articles. Cascades related rows;\nnulls `manifestarticle.article_id` and appends a delete comment.\nAuth like GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"articles"
],
"x-markdown": "article/delete.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Article has children",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/history": {
"get": {
"tags": [
"Article"
],
"summary": "Get article history by SKU",
"description": "Returns `articlehistory` rows for the article, newest first (`date` DESC, `id` DESC).\nAuth like GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/history.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"responses": {
"200": {
"description": "Article history (newest first)",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"history"
],
"properties": {
"history": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ArticleHistoryEntry"
}
}
},
"example": {
"history": [
{
"id": 42,
"date": "2026-08-05T07:15:00.000Z",
"text": "Name geändert von \"Tee\" auf \"Grüner Tee\"\nEinheit geändert von \"500g\" auf \"1kg\""
},
{
"id": 41,
"date": "2026-08-01T09:00:00.000Z",
"text": "Name geändert von \"\" auf \"Tee\""
}
]
}
}
}
}
},
"400": {
"description": "Missing sku",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/prices": {
"put": {
"tags": [
"Article"
],
"summary": "Replace POS prices by SKU",
"description": "Replace-all prices for the company POS customer group.\nEach entry: `price` (net), `tax` (rate, ensured via tax.ensure), optional `minmultiplier` (default 1).\nEmpty `prices` clears all POS prices. Returns the same shape as GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/set-prices.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"prices"
],
"properties": {
"prices": {
"type": "array",
"items": {
"type": "object",
"required": [
"price",
"tax"
],
"properties": {
"price": {
"type": "number",
"description": "Net price"
},
"tax": {
"type": "number",
"description": "Tax rate (e.g. 19)"
},
"minmultiplier": {
"type": "number",
"description": "Defaults to 1; must be unique per request"
}
}
}
}
},
"example": {
"prices": [
{
"price": 10.5,
"tax": 19
},
{
"price": 9.9,
"tax": 19,
"minmultiplier": 10
}
]
}
}
}
}
},
"responses": {
"200": {
"description": "Updated article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Invalid prices or POS customer group required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/image": {
"post": {
"tags": [
"Article"
],
"summary": "Add article image",
"description": "Accepts a full-size AVIF body (`Content-Type: image/avif`).\nFull: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.\nThumb always scaled to max 200×200. Stores hash/thumbhash, links `articlepic`.\nAuth like GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/add-image.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"requestBody": {
"required": true,
"content": {
"image/avif": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"responses": {
"201": {
"description": "Image stored and linked",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"image": {
"$ref": "#/components/schemas/ArticleImage"
}
}
}
}
}
},
"400": {
"description": "Missing body or invalid image",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/image/{fullHash}": {
"delete": {
"tags": [
"Article"
],
"summary": "Delete article image",
"description": "Removes `articlepic` and the `pic` row for this article (`fullHash`). Auth like GET by SKU.",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/delete-image.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
},
{
"in": "path",
"name": "fullHash",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-fA-F0-9]{64}$"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Invalid hash",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article or image not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/images/order": {
"put": {
"tags": [
"Article"
],
"summary": "Reorder article images",
"description": "Sets `articlepic.sort` to `0..n-1` in the given order.\n`fullHashes` must list every image of the article exactly once.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/order-images.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"fullHashes"
],
"properties": {
"fullHashes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[a-fA-F0-9]{64}$"
}
}
},
"example": {
"fullHashes": [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
]
}
}
}
}
},
"responses": {
"200": {
"description": "New order",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"fullHashes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
},
"400": {
"description": "Invalid image order",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/eans": {
"put": {
"tags": [
"Article"
],
"summary": "Replace article EANs by SKU",
"description": "Replace-all EANs for the article. EANs must be non-empty unique strings;\nempty array clears all EANs. Returns the same shape as GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/set-eans.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"eans"
],
"properties": {
"eans": {
"type": "array",
"items": {
"type": "string"
}
}
},
"example": {
"eans": [
"4001234567890"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Updated article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Invalid eans",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/brand": {
"put": {
"tags": [
"Article"
],
"summary": "Set or clear article brand by SKU",
"description": "Sets the article's `brandId` (must belong to the article's company).\nPass `null` to clear the brand. Returns the same shape as GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/set-brand.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"brandId"
],
"properties": {
"brandId": {
"type": "integer",
"nullable": true
}
},
"example": {
"brandId": 3
}
}
}
}
},
"responses": {
"200": {
"description": "Updated article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article or brand not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/weight": {
"put": {
"tags": [
"Article"
],
"summary": "Set article weight by SKU",
"description": "Sets the article's `weight`. Must be finite and `>= 0`.\nReturns the same shape as GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/set-weight.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"weight"
],
"properties": {
"weight": {
"type": "number"
}
},
"example": {
"weight": 1.5
}
}
}
}
},
"responses": {
"200": {
"description": "Updated article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Invalid weight",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/categories": {
"put": {
"tags": [
"Article"
],
"summary": "Replace article categories by SKU",
"description": "Replace-all the article's category list. Each category must belong to\nthe article's company; ids must be unique. Empty array removes the\narticle from all categories. Returns the same shape as GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-signals": [
"article-<sku>"
],
"x-markdown": "article/set-categories.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"categoryIds"
],
"properties": {
"categoryIds": {
"type": "array",
"items": {
"type": "integer"
}
}
},
"example": {
"categoryIds": [
4,
7
]
}
}
}
}
},
"responses": {
"200": {
"description": "Updated article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Invalid categoryIds",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/articleIndex": {
"get": {
"tags": [
"Article"
],
"summary": "List article search index",
"description": "Returns every article reachable via the company's category tree.\nDesigned for client-side search / autocomplete preload.\nAuth via company access.\n",
"security": [
{
"cookieAuth": []
}
],
"responses": {
"200": {
"description": "Search index",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"articles": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"sku": {
"type": "string"
},
"name": {
"type": "string",
"nullable": true
},
"eans": {
"type": "array",
"items": {
"type": "string"
}
},
"categoryIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/auth/selected-company": {
"put": {
"tags": [
"Auth"
],
"summary": "Set selected company",
"description": "Sets the authenticated user's selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "auth/selected-company.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"companyId"
],
"properties": {
"companyId": {
"type": "integer",
"minimum": 1
}
},
"example": {
"companyId": 1
}
}
}
}
},
"responses": {
"200": {
"description": "Updated user",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
},
"400": {
"description": "Invalid companyId",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/auth/me": {
"get": {
"tags": [
"Auth"
],
"summary": "Current user",
"description": "Returns the authenticated user from the session cookie and the Google client ID.\n`user.picture` is the stored profile image as a base64 string when present.\nUnauthenticated calls still receive `googleClientId` with 401.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "auth/me.md",
"responses": {
"200": {
"description": "Authenticated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/User"
},
"googleClientId": {
"type": "string"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — still includes `googleClientId` for the frontend",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"googleClientId": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/auth/logout": {
"post": {
"tags": [
"Auth"
],
"summary": "Logout",
"description": "Clears the HTTP-only `token` cookie set at login.",
"security": [],
"x-markdown": "auth/logout.md",
"responses": {
"200": {
"description": "Logged out",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "Logged out successfully"
}
}
}
}
}
}
}
}
},
"/auth/login": {
"post": {
"tags": [
"Auth"
],
"summary": "Login with Google ID token",
"description": "Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`),\nstores Google `sub` and profile image bytes, and sets an HTTP-only `token` cookie.\n`user.picture` is the profile image as base64 (same as GET /auth/me).\n",
"security": [],
"x-markdown": "auth/login.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"idToken"
],
"properties": {
"idToken": {
"type": "string",
"description": "Google ID token from Google Identity Services"
}
},
"example": {
"idToken": "<google-id-token>"
}
}
}
}
},
"responses": {
"200": {
"description": "Login successful; `token` cookie set",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "Login successful"
},
"user": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
},
"400": {
"description": "Missing token or Google client not configured",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid Google token or account",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Email not on allowlist",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"cookieAuth": {
"type": "apiKey",
"in": "cookie",
"name": "token",
"description": "HTTP-only session cookie set by POST /auth/login"
}
},
"schemas": {
"Error": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "string"
}
}
},
"User": {
"type": "object",
"required": [
"id",
"email",
"selectedCompanyId"
],
"properties": {
"id": {
"type": "integer"
},
"email": {
"type": "string",
"format": "email"
},
"selectedCompanyId": {
"type": "integer",
"nullable": true
},
"picture": {
"type": "string",
"description": "Profile image as base64 (from stored picturebinary); omitted when unset"
}
}
},
"Company": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"iso639": {
"type": "string"
},
"name": {
"type": "string"
},
"taxrate": {
"type": "number"
},
"reducedtaxrate": {
"type": "number"
}
}
},
"CompanySetting": {
"type": "object",
"required": [
"key",
"value"
],
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string",
"nullable": true
}
}
},
"Category": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"parentId": {
"type": "integer",
"nullable": true
},
"sort": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"Unit": {
"type": "object",
"required": [
"si",
"basesi",
"basesifactor"
],
"properties": {
"si": {
"type": "string"
},
"basesi": {
"type": "string"
},
"basesifactor": {
"type": "number"
},
"symbol": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true
}
}
},
"ArticleSummary": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"parentId": {
"type": "integer"
},
"name": {
"type": "string"
},
"sku": {
"type": "string"
}
}
},
"ArticleDetail": {
"type": "object",
"description": "Full article payload (unit, brand, categoryIds, photos, eans, prices, …)",
"additionalProperties": true
},
"ArticleHistoryEntry": {
"type": "object",
"required": [
"id",
"date",
"text"
],
"properties": {
"id": {
"type": "integer"
},
"date": {
"type": "string",
"format": "date-time"
},
"text": {
"type": "string"
}
}
},
"ArticleSupplierPrice": {
"type": "object",
"required": [
"net",
"taxId"
],
"properties": {
"net": {
"type": "number",
"description": "Net purchase price"
},
"taxId": {
"type": "integer",
"description": "Tax ID"
},
"taxRate": {
"type": "number",
"nullable": true,
"description": "Tax rate in percent"
}
}
},
"ArticleSupplier": {
"type": "object",
"required": [
"id",
"articleId",
"entityId",
"pu",
"sort"
],
"properties": {
"id": {
"type": "integer"
},
"articleId": {
"type": "integer"
},
"entityId": {
"type": "integer"
},
"supplierName": {
"type": "string",
"nullable": true
},
"sku": {
"type": "string",
"nullable": true,
"description": "Supplier-specific SKU (Lieferanten-Art.-Nr.)"
},
"pu": {
"type": "number",
"description": "Packaging unit (Verpackungseinheit / VPE)"
},
"stock": {
"type": "number",
"nullable": true,
"description": "Supplier stock"
},
"sort": {
"type": "integer",
"description": "Display sort order"
},
"priceId": {
"type": "integer",
"nullable": true
},
"price": {
"$ref": "#/components/schemas/ArticleSupplierPrice",
"nullable": true
}
}
},
"SupplierArticlePhoto": {
"type": "object",
"required": [
"sort",
"thumbhash",
"hash",
"thumbw",
"thumbh"
],
"properties": {
"sort": {
"type": "integer"
},
"thumbhash": {
"type": "string"
},
"hash": {
"type": "string"
},
"thumbw": {
"type": "integer"
},
"thumbh": {
"type": "integer"
}
}
},
"SupplierArticleInfo": {
"type": "object",
"required": [
"id",
"sku"
],
"properties": {
"id": {
"type": "integer"
},
"sku": {
"type": "string"
},
"name": {
"type": "string",
"nullable": true
},
"slug": {
"type": "string",
"nullable": true
},
"photos": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SupplierArticlePhoto"
}
},
"articlepic": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SupplierArticlePhoto"
}
},
"eans": {
"type": "array",
"items": {
"type": "string"
}
},
"articleean": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"SupplierArticle": {
"allOf": [
{
"$ref": "#/components/schemas/ArticleSupplier"
},
{
"type": "object",
"required": [
"article"
],
"properties": {
"article": {
"$ref": "#/components/schemas/SupplierArticleInfo"
}
}
}
]
},
"BrandPic": {
"type": "object",
"required": [
"id",
"sort",
"fullHash",
"thumbW",
"thumbH",
"thumbHash"
],
"properties": {
"id": {
"type": "integer"
},
"sort": {
"type": "integer"
},
"fullHash": {
"type": "string"
},
"thumbW": {
"type": "integer"
},
"thumbH": {
"type": "integer"
},
"thumbHash": {
"type": "string"
}
}
},
"Brand": {
"type": "object",
"required": [
"id",
"name",
"pics"
],
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"gsprpinfo": {
"type": "string",
"nullable": true
},
"pics": {
"type": "array",
"description": "Brand pictures sorted by sort",
"items": {
"$ref": "#/components/schemas/BrandPic"
}
}
}
},
"NumberSequence": {
"type": "object",
"required": [
"id",
"type",
"prefix",
"padding",
"next"
],
"properties": {
"id": {
"type": "integer"
},
"type": {
"type": "string",
"description": "Sequence type (e.g. entity, article, delivery)"
},
"prefix": {
"type": "string"
},
"padding": {
"type": "integer",
"description": "Zero-padding length for the numeric part"
},
"next": {
"type": "integer",
"description": "Next value to allocate"
}
}
},
"Supplier": {
"type": "object",
"required": [
"id",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"Customergroup": {
"type": "object",
"required": [
"id",
"name",
"sort"
],
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"sort": {
"type": "integer"
}
}
},
"CustomergroupListItem": {
"allOf": [
{
"$ref": "#/components/schemas/Customergroup"
},
{
"type": "object",
"required": [
"editable"
],
"properties": {
"editable": {
"type": "boolean",
"description": "`false` for implicit groups `POS` and `Lieferanten` (name locked).\n`true` for user-created groups. Reorder all groups via\n`PUT /customergroup/order`.\n"
}
}
}
]
},
"Entity": {
"type": "object",
"required": [
"id",
"customergroupId",
"firstName",
"name",
"number",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "integer"
},
"customergroupId": {
"type": "integer"
},
"firstName": {
"type": "string",
"nullable": true
},
"name": {
"type": "string"
},
"number": {
"type": "string",
"nullable": true,
"description": "Customer number (null for suppliers)"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EntityAddress": {
"type": "object",
"required": [
"id",
"entityId",
"addressType"
],
"properties": {
"id": {
"type": "integer"
},
"entityId": {
"type": "integer"
},
"firstName": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true
},
"street": {
"type": "string",
"nullable": true
},
"streetNumber": {
"type": "string",
"nullable": true
},
"zip": {
"type": "string",
"nullable": true
},
"city": {
"type": "string",
"nullable": true
},
"county": {
"type": "string",
"nullable": true
},
"addressType": {
"type": "string"
}
}
},
"EntityContact": {
"type": "object",
"required": [
"id",
"entityId",
"contactType",
"contact"
],
"properties": {
"id": {
"type": "integer"
},
"entityId": {
"type": "integer"
},
"contactType": {
"type": "string",
"description": "e.g. email, phone"
},
"contact": {
"type": "string"
}
}
},
"DeliverySummary": {
"type": "object",
"required": [
"id",
"number",
"type",
"supplierId",
"date"
],
"properties": {
"id": {
"type": "integer"
},
"number": {
"type": "string",
"description": "Auto-allocated via nextNumber('delivery')"
},
"type": {
"type": "string",
"enum": [
"delivery"
]
},
"supplierId": {
"type": "integer",
"nullable": true,
"description": "manifest.entity_id"
},
"externalNumber": {
"type": "string",
"nullable": true,
"description": "User-supplied reference number"
},
"date": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"ledger": {
"type": "object",
"description": "Ledger booking summary for this delivery",
"properties": {
"totalPositions": {
"type": "integer",
"description": "Total number of position lines on this manifest"
},
"completelyOnLedgerPositions": {
"type": "integer",
"description": "Number of positions whose quantity is fully transacted in the ledger"
}
}
}
}
},
"DeliveryLine": {
"type": "object",
"required": [
"id",
"sku",
"name",
"pu",
"amount",
"net",
"rebate",
"taxrate",
"comment"
],
"properties": {
"id": {
"type": "integer"
},
"sku": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true,
"description": "Line item name or custom description"
},
"pu": {
"type": "number",
"description": "Packaging unit (defaults to 1)"
},
"amount": {
"type": "number"
},
"net": {
"type": "number"
},
"rebate": {
"type": "number"
},
"taxrate": {
"type": "number",
"description": "Tax rate in percent (e.g. 19)"
},
"comment": {
"type": "string",
"nullable": true
}
}
},
"DeliveryLineInput": {
"type": "object",
"required": [
"amount",
"net",
"taxrate"
],
"description": "Line item input. Requires `sku` or `name`.",
"properties": {
"sku": {
"type": "string",
"nullable": true,
"description": "SKU reference (sku or name is required)"
},
"name": {
"type": "string",
"nullable": true,
"description": "Line item name (sku or name is required)"
},
"pu": {
"type": "number",
"description": "Packaging unit (defaults to 1)"
},
"amount": {
"type": "number"
},
"net": {
"type": "number"
},
"taxrate": {
"type": "number",
"description": "Tax rate in percent (resolved via tax.ensure)"
},
"rebate": {
"type": "number",
"description": "Defaults to 0"
},
"comment": {
"type": "string",
"nullable": true
}
}
},
"Delivery": {
"allOf": [
{
"$ref": "#/components/schemas/DeliverySummary"
},
{
"type": "object",
"required": [
"lines",
"files"
],
"properties": {
"lines": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DeliveryLine"
}
},
"files": {
"type": "array",
"description": "Attached documents (pdf/txt) for the delivery",
"items": {
"$ref": "#/components/schemas/DeliveryFile"
}
}
}
}
]
},
"DeliveryFile": {
"type": "object",
"description": "Attachment metadata (full binary content is served via `GET /delivery/{id}/file/{fileId}`). Thumbnails (AVIF) are generated server-side.",
"required": [
"id",
"manifestId",
"fileId",
"sort",
"hash",
"mimetype",
"name",
"size"
],
"properties": {
"id": {
"type": "integer",
"description": "manifestattachment id"
},
"manifestId": {
"type": "integer"
},
"fileId": {
"type": "integer"
},
"sort": {
"type": "integer"
},
"hash": {
"type": "string",
"description": "SHA-256 of the file content"
},
"mimetype": {
"type": "string",
"description": "application/pdf or text/plain"
},
"name": {
"type": "string"
},
"size": {
"type": "integer"
},
"created": {
"type": "string",
"format": "date-time"
},
"modified": {
"type": "string",
"format": "date-time"
},
"thumbavif": {
"type": "string",
"format": "byte",
"nullable": true,
"description": "Base64-encoded AVIF thumbnail generated server-side (null for text files)"
},
"thumbx": {
"type": "integer",
"nullable": true
},
"thumby": {
"type": "integer",
"nullable": true
},
"meta": {
"type": "object",
"nullable": true,
"description": "String key/value metadata from the file row",
"additionalProperties": {
"type": "string"
}
}
}
},
"DeliveryFileInput": {
"type": "object",
"required": [
"mimetype",
"name",
"content"
],
"description": "Attachment input. Only application/pdf and text/plain are accepted. Thumbnails are generated server-side and must not be supplied by the client.",
"properties": {
"mimetype": {
"type": "string",
"enum": [
"application/pdf",
"text/plain"
]
},
"name": {
"type": "string"
},
"content": {
"type": "string",
"format": "byte",
"description": "File content (base64-encoded in JSON)"
},
"sort": {
"type": "integer",
"description": "Optional sort order (defaults to appending after existing files)"
},
"size": {
"type": "integer",
"description": "Optional size in bytes (defaults to content length)"
},
"hash": {
"type": "string",
"description": "Optional SHA-256 (auto-computed if omitted)"
},
"created": {
"type": "string",
"format": "date-time"
},
"modified": {
"type": "string",
"format": "date-time"
},
"meta": {
"type": "object",
"description": "Optional string key/value metadata stored with the file",
"additionalProperties": {
"type": "string"
}
}
}
},
"ManifestLine": {
"type": "object",
"required": [
"id",
"sku",
"name",
"pu",
"amount",
"net",
"rebate",
"taxrate",
"comment"
],
"description": "Line shape for quote and order",
"properties": {
"id": {
"type": "integer"
},
"sku": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true,
"description": "Line item name or custom description"
},
"pu": {
"type": "number",
"description": "Packaging unit (defaults to 1)"
},
"amount": {
"type": "number"
},
"net": {
"type": "number"
},
"rebate": {
"type": "number"
},
"taxrate": {
"type": "number",
"description": "Tax rate in percent (e.g. 19)"
},
"comment": {
"type": "string",
"nullable": true
}
}
},
"ManifestLineInput": {
"type": "object",
"required": [
"amount",
"net",
"taxrate"
],
"description": "Line item input. Requires `sku` or `name`.",
"properties": {
"sku": {
"type": "string",
"nullable": true,
"description": "SKU reference (sku or name is required)"
},
"name": {
"type": "string",
"nullable": true,
"description": "Line item name (sku or name is required)"
},
"pu": {
"type": "number",
"description": "Packaging unit (defaults to 1)"
},
"amount": {
"type": "number"
},
"net": {
"type": "number"
},
"taxrate": {
"type": "number",
"description": "Tax rate in percent (resolved via tax.ensure)"
},
"rebate": {
"type": "number",
"description": "Defaults to 0"
},
"comment": {
"type": "string",
"nullable": true
}
}
},
"QuoteSummary": {
"type": "object",
"required": [
"id",
"number",
"type",
"entityId",
"date"
],
"properties": {
"id": {
"type": "integer"
},
"number": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"quote"
]
},
"entityId": {
"type": "integer",
"nullable": true,
"description": "Customer (manifest.entity_id)"
},
"date": {
"type": "string",
"format": "date-time",
"description": "Document date / valid-until"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"Quote": {
"allOf": [
{
"$ref": "#/components/schemas/QuoteSummary"
},
{
"type": "object",
"required": [
"lines"
],
"properties": {
"lines": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ManifestLine"
}
}
}
}
]
},
"OrderSummary": {
"type": "object",
"required": [
"id",
"number",
"type",
"entityId",
"date"
],
"properties": {
"id": {
"type": "integer"
},
"number": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"order"
]
},
"entityId": {
"type": "integer",
"nullable": true,
"description": "Customer (manifest.entity_id)"
},
"date": {
"type": "string",
"format": "date-time",
"description": "Document date / expected delivery"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"Order": {
"allOf": [
{
"$ref": "#/components/schemas/OrderSummary"
},
{
"type": "object",
"required": [
"lines"
],
"properties": {
"lines": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ManifestLine"
}
}
}
}
]
},
"ArticleImage": {
"type": "object",
"required": [
"picId",
"sort",
"full",
"thumb",
"hash",
"thumbHash"
],
"properties": {
"picId": {
"type": "integer"
},
"sort": {
"type": "integer"
},
"full": {
"type": "object",
"required": [
"w",
"h",
"size"
],
"properties": {
"w": {
"type": "integer"
},
"h": {
"type": "integer"
},
"size": {
"type": "integer",
"description": "Byte length of full AVIF (as-is when already AVIF ≤1000×1000)"
}
}
},
"thumb": {
"type": "object",
"required": [
"w",
"h"
],
"properties": {
"w": {
"type": "integer"
},
"h": {
"type": "integer"
}
}
},
"hash": {
"type": "string",
"description": "SHA-256 hex of full image bytes"
},
"thumbHash": {
"type": "string",
"description": "Base64 thumbhash of thumb preview"
}
}
},
"LedgerEntry": {
"type": "object",
"required": [
"id",
"manifestarticle_id",
"transaction_type",
"amount",
"transaction_text",
"hash_version"
],
"properties": {
"id": {
"type": "integer"
},
"manifestarticle_id": {
"type": "integer",
"description": "References manifestarticle.id"
},
"transaction_type": {
"type": "string",
"description": "Transaction identifier / type"
},
"amount": {
"type": "number",
"description": "Movement amount"
},
"transaction_text": {
"type": "string",
"description": "Transaction memo / details"
},
"hash_version": {
"type": "integer",
"description": "Algorithm version (1)"
},
"prev_hash": {
"type": "string",
"description": "Previous entry SHA-512 hex or initial seed hash"
},
"sku": {
"type": "string",
"nullable": true,
"description": "SKU from referenced manifestarticle (present in query views)"
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the row was created"
}
}
},
"LedgerEntryInput": {
"type": "object",
"required": [
"manifestarticle_id",
"transaction_type",
"amount",
"transaction_text"
],
"properties": {
"manifestarticle_id": {
"type": "integer",
"description": "References manifestarticle.id"
},
"transaction_type": {
"type": "string",
"description": "Transaction identifier / type (max 100 chars)"
},
"amount": {
"type": "number",
"description": "Movement amount"
},
"transaction_text": {
"type": "string",
"description": "Transaction memo / details"
}
}
},
"Printer": {
"type": "object",
"required": [
"id",
"company_id",
"address",
"type",
"sort"
],
"properties": {
"id": {
"type": "integer"
},
"company_id": {
"type": "integer"
},
"address": {
"type": "string",
"description": "Printer network address or hostname"
},
"type": {
"type": "string",
"enum": [
"esc/pos",
"zpl",
"ipp"
],
"description": "Printer type (\"esc/pos\" for lines, \"zpl\" for pages, or \"ipp\")"
},
"model": {
"type": "string",
"nullable": true,
"description": "Printer model name"
},
"serial": {
"type": "string",
"nullable": true,
"description": "Printer serial number"
},
"sort": {
"type": "integer",
"description": "Sort order"
},
"desc": {
"type": "string",
"nullable": true,
"description": "Printer description"
}
}
},
"PrinterCreateInput": {
"type": "object",
"required": [
"address",
"type"
],
"properties": {
"address": {
"type": "string",
"description": "Printer network address or hostname"
},
"type": {
"type": "string",
"enum": [
"esc/pos",
"zpl",
"ipp"
],
"description": "Printer type (\"esc/pos\" for lines, \"zpl\" for pages, or \"ipp\")"
},
"model": {
"type": "string",
"nullable": true
},
"serial": {
"type": "string",
"nullable": true
},
"sort": {
"type": "integer"
},
"desc": {
"type": "string",
"nullable": true
}
}
},
"PrinterUpdateInput": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"esc/pos",
"zpl",
"ipp"
]
},
"model": {
"type": "string",
"nullable": true
},
"serial": {
"type": "string",
"nullable": true
},
"sort": {
"type": "integer"
},
"desc": {
"type": "string",
"nullable": true
}
}
},
"StockAdjustmentInput": {
"type": "object",
"properties": {
"sku": {
"type": "string",
"description": "Article SKU (for single-item adjustment)"
},
"warehouseId": {
"type": "integer",
"description": "Target warehouse ID"
},
"amount": {
"type": "number",
"description": "Signed adjustment quantity (+ to add, - to remove)"
},
"text": {
"type": "string",
"nullable": true,
"description": "Reason or memo"
},
"lines": {
"type": "array",
"description": "Batch adjustment lines",
"items": {
"type": "object",
"required": [
"sku",
"amount"
],
"properties": {
"sku": {
"type": "string"
},
"warehouseId": {
"type": "integer"
},
"amount": {
"type": "number"
},
"text": {
"type": "string"
}
}
}
}
}
},
"StockAdjustmentResult": {
"type": "object",
"properties": {
"manifest": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"number": {
"type": "string"
},
"type": {
"type": "string",
"example": "adjustment"
},
"date": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"lines": {
"type": "array",
"items": {
"type": "object"
}
},
"ledger": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LedgerEntry"
}
}
}
}
}
}
}