# `GET /article/search` Protected. Requires a valid session cookie (`credentials: 'include'`). Fuzzy article search. Use this to build a search box / result list with category facets. ## Query parameters | Param | Required | Description | |-------|----------|-------------| | `target` | yes | Search string. Split on spaces; each word must match (AND). Each word is matched with `LIKE %word%` against EAN, supplier article number, name, article number, or barcode. | | `filter` | no | Comma-separated category IDs. Omit or leave empty to search all categories. See [Category filter](#category-filter). | | `offset` | no | Pagination offset (default `0`). | | `maxCount` | no | Page size (default `10`). | ## Search behaviour - Multiple spaces are collapsed to one. - Example: `target=bizz allmix` → both `bizz` **and** `allmix` must match somewhere on the article. - By default only **active** articles (`cAktiv = 'Y'`) are returned. - Without a category filter, at most **2000** rows are returned (before pagination). - Results are ordered by product name ascending. - `length` is the total match count (before `offset` / `maxCount`); `recordset` is the current page. ## Category filter | `filter` value | Effect | |----------------|--------| | *(omitted or empty)* | All categories; `TOP 2000` | | `751` | Include inactive articles (no `cAktiv = 'Y'` restriction). Still uses category branch logic if other IDs are present. | | `16` or `16,39` | Restrict to articles in those categories (OR). No row limit. | Pass category IDs as a comma-separated list: `filter=16,39`. Whenever `filter` is present, **`categories` always includes every id from `filter`**, even when `length` is `0` or a filtered id has no hits. Those entries use master-data `name` / `parent` and may have `count: 0`. Other facet categories with hits are still returned as usual. ## Response `200` ```json { "length": 42, "recordset": [ { "categories": "16,39", "available": 0, "id": 49, "stock": null, "sku": "42000044", "name": "BioBizz AllMix Peat Free 20L", "price": 13.5, "tax": 19 } ], "categories": { "16": { "name": "Dünger", "count": 42, "parent": 0 }, "39": { "name": "Erde", "count": 18, "parent": 16 } } } ``` Empty result with an active filter still returns the applied category: ```json { "length": 0, "recordset": [], "categories": { "16": { "name": "Dünger", "count": 0, "parent": 0 } } } ``` ### Top-level fields | Field | Type | Notes | |-------|------|-------| | `length` | number | Total matching articles (unpaginated). | | `recordset` | array | Current page of shaped article rows. | | `categories` | object | Map keyed by category id (string). Facet counts for the current search (+ filters). | ### `recordset[]` fields | Field | Type | Notes | |-------|------|-------| | `id` | number | Internal article ID (`kArtikel`) | | `sku` | string | Article number — pass to [`GET /article/get`](get.md) | | `name` | string | Display name | | `stock` | number \| null | Stock in warehouse 1 | | `available` | number \| null | Available stock | | `price` | number \| null | Brutto VK, rounded to 1 decimal | | `tax` | number \| null | Tax rate percent for the article’s Steuerklasse | | `categories` | string \| null | Comma-separated category IDs on the article | ### `categories` map entries | Field | Type | Notes | |-------|------|-------| | `name` | string \| null | Display label from category master data | | `count` | number | Hit count under the current search (+ other filters); may be `0` | | `parent` | number \| null | Parent category id, or `0` if root | Counts include parent categories rolled up from each article’s category path. Use for category sidebar / facet UI. Resolve labels via `categories[id].name` (skip ids without a name). ## Errors | Status | Body | |--------|------| | 400 | `{ "error": "Query parameter \"target\" is required" }` | | 401 | `{ "error": "Unauthorized" }` | | 500 | `{ "error": "..." }` | ## Example ```js const res = await fetch( 'http://localhost:4208/api/article/search?target=bizz%20allmix&filter=16&maxCount=10', { credentials: 'include' } ); const { categories, length, recordset } = await res.json(); ```