This commit is contained in:
seb
2026-10-04 02:14:03 +02:00
parent 9a7f345d8b
commit e5ff4ef307
15 changed files with 1178 additions and 62 deletions

View File

@@ -202,6 +202,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/pos/client.md`](routes/pos/client.md) | `GET /v1/client` |
| [`routes/pos/init.md`](routes/pos/init.md) | `GET /v1/init` |
| [`routes/v1/product.md`](routes/v1/product.md) | `GET /v1/product` |
| [`routes/vfs/_*path.md`](routes/vfs/_*path.md) | `GET /vfs/{*path}` |
| [`routes/warehouse/list.md`](routes/warehouse/list.md) | `GET /warehouse` |
| [`routes/warehouse/create.md`](routes/warehouse/create.md) | `POST /warehouse` |
| [`routes/warehouse/order.md`](routes/warehouse/order.md) | `PUT /warehouse/order` |

View File

@@ -708,6 +708,105 @@
}
}
},
"/vfs/{*path}": {
"get": {
"tags": [
"Vfs"
],
"summary": "Get a virtual filesystem file",
"description": "Returns a file the model produced in the `javascript_execute` sandbox\n(virtual filesystem), for the authenticated user's selected company.\n\nEmbed the returned URL in chat markdown to display a generated image:\n`![title](/api/vfs/example.png)`. *\nImages are ALWAYS returned as a preview: scaled to fit inside a 250x250 box with the\naspect ratio preserved (never stretched), and anything smaller than 100 px on its larger\nedge is upscaled to 100 px. The preview carries a translucent strip along the bottom\nedge listing the ORIGINAL width, height, file size, MIME type and filename. *\nOnly image MIME types the sandbox produces are rendered inline; every other file\ntype is sent with `Content-Disposition: attachment` so it downloads rather than\nbeing interpreted as a document in the app's origin.\n",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "VFS file path (e.g. `example.png`, `labels/a.png`)"
},
{
"in": "query",
"name": "download",
"required": false,
"schema": {
"type": "boolean"
},
"description": "Return the original, unmodified file instead of the standard preview.\nWithout this parameter images are always served as a 250x250 aspect-preserving\npreview with a metadata strip. Ignored for non-image files, which are always\nserved as-is.\n"
}
],
"responses": {
"200": {
"description": "File bytes",
"headers": {
"Content-Disposition": {
"schema": {
"type": "string"
}
},
"Cache-Control": {
"schema": {
"type": "string"
}
}
},
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"400": {
"description": "Invalid path, 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": "File not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/unit": {
"get": {
"tags": [
@@ -13888,12 +13987,25 @@
"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",
"description": "Returns every article reachable via the company's category tree.\nDesigned for client-side search / autocomplete preload.\nAuth via company access.\n\nREQUIRES `windowSize`. Without it the response is always an empty list — the full\nindex can be very large and is not useful to a caller that did not ask for a\nspecific window. Pass the number of entries the client actually intends to preload.\n",
"security": [
{
"cookieAuth": []
}
],
"parameters": [
{
"in": "query",
"name": "windowSize",
"required": true,
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 1000
},
"description": "Number of articles to return (window size). Required; omitting it returns an\nempty `articles` array. `truncated` is true when more entries exist than were\nreturned, signalling the client should request a larger window or narrow its\nquery.\n"
}
],
"responses": {
"200": {
"description": "Search index",
@@ -13931,6 +14043,10 @@
}
}
}
},
"truncated": {
"type": "boolean",
"description": "True when the window was smaller than the total number of matching articles."
}
}
}

View File

@@ -1,6 +1,6 @@
# Dash API Routes Overview (All)
Overview of all 177 API endpoints across all modules.
Overview of all 178 API endpoints across all modules.
### Parameter Notation
@@ -46,7 +46,7 @@ Overview of all 177 API endpoints across all modules.
| `PATCH` | `/article/{sku}/supplier/{id}` | `sku:string, id:integer, body: { sku?: string, pu?, stock?, sort?, netPrice?, taxId?, taxRate? }` | `200 { supplier: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price } }` | Update article supplier assignment |
| `PUT` | `/article/{sku}/track_stock` | `sku:string, body: { track_stock: boolean }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | Set article track_stock flag by SKU |
| `PUT` | `/article/{sku}/weight` | `sku:string, body: { weight: number }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | Set article weight by SKU |
| `GET` | `/articleIndex` | — | `200 { articles: { id, sku, name, eans: string[], categoryIds: integer[] }[] }` | List article search index |
| `GET` | `/articleIndex` | `query: windowSize:integer` | `200 { articles: { id, sku, name, eans: string[], categoryIds: integer[] }[], truncated }` | List article search index |
| `GET` | `/attributename` | — | `200 { attributenames: { id, name, sort, exclusive, values: { id, attributeId, value, sort }[] }[] }` | List attribute names |
| `POST` | `/attributename` | `body: { name, sort?, exclusive? }` | `201 { attributename: { id, name, sort, exclusive, values: { id, attributeId, value, sort }[] } }` | Create attribute name |
| `PUT` | `/attributename/order` | `body: { attributenameIds: integer[] }` | `200` | Reorder attribute names |
@@ -182,6 +182,7 @@ Overview of all 177 API endpoints across all modules.
| `GET` | `/v1/client` | `query: authCode:string, name?:string` | `200 { authCode, authToken, certificateFingerprint, certificateSerialNumber, mandantId, mandantName, mandantDatabase, serverFingerprint, name, serverTimestamp }` | POS discovery and pairing |
| `GET` | `/v1/init` | `query: lastChangedProduct?:integer, lastChangedCategory?:integer, lastChangedCustomer?:integer, lastChangedCustomerGroup?:integer, lastChangedCompositeProduct?:integer, lastChangedDeletedEntity?:integer` | `200 { version, product_count, category_count, customer_count, customerGroup_count, compositeProduct_count, configurationGroup_count, configurationItem_count, deletedEntity_count, max_orderId_count }` | POS sync initialization counts |
| `GET` | `/v1/product` | `query: lastChangedProduct?:string, limit?:integer` | `200 object[]` | JTL-POS product catalog synchronization |
| `GET` | `/vfs/{*path}` | `path:string, query: download?:boolean` | `200 string` | Get a virtual filesystem file |
| `GET` | `/warehouse` | — | `200 { warehouses: { id, name, sort }[] }` | List warehouses |
| `POST` | `/warehouse` | `body: { name, sort? }` | `201 { warehouse: { id, name, sort } }` | Create warehouse |
| `PUT` | `/warehouse/order` | `body: { warehouseIds: integer[] }` | `200 { warehouseIds: integer[] }` | Reorder warehouses |

View File

@@ -1,6 +1,6 @@
# Dash API Routes Overview
Overview of all 141 API endpoints across all modules.
Overview of all 142 API endpoints across all modules.
### Parameter Notation
@@ -44,7 +44,7 @@ Overview of all 141 API endpoints across all modules.
| `PATCH` | `/article/{sku}/supplier/{id}` | `sku:string, id:integer, body: { sku?: string, pu?, stock?, sort?, netPrice?, taxId?, taxRate? }` | `200 { supplier: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price } }` | Update article supplier assignment |
| `PUT` | `/article/{sku}/track_stock` | `sku:string, body: { track_stock: boolean }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | Set article track_stock flag by SKU |
| `PUT` | `/article/{sku}/weight` | `sku:string, body: { weight: number }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | Set article weight by SKU |
| `GET` | `/articleIndex` | — | `200 { articles: { id, sku, name, eans: string[], categoryIds: integer[] }[] }` | List article search index |
| `GET` | `/articleIndex` | `query: windowSize:integer` | `200 { articles: { id, sku, name, eans: string[], categoryIds: integer[] }[], truncated }` | List article search index |
| `GET` | `/attributename` | — | `200 { attributenames: { id, name, sort, exclusive, values: { id, attributeId, value, sort }[] }[] }` | List attribute names |
| `POST` | `/attributename` | `body: { name, sort?, exclusive? }` | `201 { attributename: { id, name, sort, exclusive, values: { id, attributeId, value, sort }[] } }` | Create attribute name |
| `PUT` | `/attributename/order` | `body: { attributenameIds: integer[] }` | `200` | Reorder attribute names |
@@ -146,6 +146,7 @@ Overview of all 141 API endpoints across all modules.
| `DELETE` | `/unit` | `query: si:string` | `204 void` | Delete unit |
| `GET` | `/unit` | — | `200 { units: { si, basesi, basesifactor, symbol, name }[] }` | List units |
| `PUT` | `/unit` | `body: { si, basesi, basesifactor, symbol, name }` | `200 { unit: { si, basesi, basesifactor, symbol, name } }` | Upsert unit |
| `GET` | `/vfs/{*path}` | `path:string, query: download?:boolean` | `200 string` | Get a virtual filesystem file |
| `GET` | `/warehouse` | — | `200 { warehouses: { id, name, sort }[] }` | List warehouses |
| `POST` | `/warehouse` | `body: { name, sort? }` | `201 { warehouse: { id, name, sort } }` | Create warehouse |
| `PUT` | `/warehouse/order` | `body: { warehouseIds: integer[] }` | `200 { warehouseIds: integer[] }` | Reorder warehouses |
@@ -153,3 +154,26 @@ Overview of all 141 API endpoints across all modules.
| `GET` | `/warehouse/{id}` | `id:integer` | `200 { warehouse: { id, name, sort } }` | Get warehouse by ID |
| `PATCH` | `/warehouse/{id}` | `id:integer, body: { name?, sort? }` | `200 { warehouse: { id, name, sort } }` | Update warehouse (partial) |
| `PUT` | `/warehouse/{id}` | `id:integer, body: { name?, sort? }` | `200 { warehouse: { id, name, sort } }` | Update warehouse |
### Binary & streaming routes (reachable only via `fetch()` in javascript_execute)
These routes are NOT in the table above because their responses are binary or long-lived streams, which the `dashback_request` tool cannot return usefully. They ARE reachable from inside `javascript_execute` with `fetch("/route")`, where you can decode the bytes, write them to the VFS and embed them in your reply:
| Method | Route | Returns | Notes |
|:---|:---|:---|:---|
| `GET` | `/image/{fullHash}` | `image/avif` | Full-size article/brand/category image by SHA-256 hash. Use for inspecting or re-encoding an existing image.
| `GET` | `/thumb/{fullHash}` | `image/avif` | Thumb AVIF by SHA-256 hash — cheaper than the full image. Takes the same `fullHash` as `/image/{fullHash}`.
| `POST` | `/serper/image` | `image/avif` | Crawl an image from a URL, auto-crop transparent/white borders. Body: `{ url }`.
Save the bytes to the VFS, then embed them in your reply as `![title](artifacts/thumb.avif)`:
```js
module.exports = (async () => {
const hash = "…64-char sha-256…";
const res = await fetch(`/thumb/${hash}`); // authenticated automatically
const buf = Buffer.from(await res.arrayBuffer());
fs.writeFileSync("artifacts/thumb.avif", buf);
return { bytes: buf.length };
})();
```
`fullHash` appears on articles, brands and categories. `thumbHash` is a base64 blur-up placeholder, not a content hash, and is not accepted here.

View File

@@ -8,6 +8,20 @@ Returns every article reachable via the company's category tree.
Designed for client-side search / autocomplete preload.
Auth via company access.
REQUIRES `windowSize`. Without it the response is always an empty list — the full
index can be very large and is not useful to a caller that did not ask for a
specific window. Pass the number of entries the client actually intends to preload.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `windowSize` | yes | integer | Number of articles to return (window size). Required; omitting it returns an
empty `articles` array. `truncated` is true when more entries exist than were
returned, signalling the client should request a larger window or narrow its
query.
|
## Response `200`
@@ -27,7 +41,8 @@ Search index
1
]
}
]
],
"truncated": true
}
```

View File

@@ -0,0 +1,52 @@
# `GET /vfs/{*path}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Get a virtual filesystem file
Returns a file the model produced in the `javascript_execute` sandbox
(virtual filesystem), for the authenticated user's selected company.
Embed the returned URL in chat markdown to display a generated image:
`![title](/api/vfs/example.png)`. *
Images are ALWAYS returned as a preview: scaled to fit inside a 250x250 box with the
aspect ratio preserved (never stretched), and anything smaller than 100 px on its larger
edge is upscaled to 100 px. The preview carries a translucent strip along the bottom
edge listing the ORIGINAL width, height, file size, MIME type and filename. *
Only image MIME types the sandbox produces are rendered inline; every other file
type is sent with `Content-Disposition: attachment` so it downloads rather than
being interpreted as a document in the app's origin.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `path` | yes | string | VFS file path (e.g. `example.png`, `labels/a.png`) |
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `download` | no | boolean | Return the original, unmodified file instead of the standard preview.
Without this parameter images are always served as a 250x250 aspect-preserving
preview with a metadata strip. Ignored for non-image files, which are always
served as-is.
|
## Response `200`
File bytes
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid path, or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | File not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._