article
An article is an item kept in stock: a product, a tool, a spare part. Stock is not a field of the article; it is the sum of the movements that book it.
- Mutable: the version with the greater
hlcwins. - Ids: random UUIDs.
- Deleting: an article that a movement references is archived (
archived: true) instead of deleted, so that history keeps its name. The reference app deletes (tombstone) only articles without movements and refuses to delete or archive an article that has stock.
Fields
Section titled “Fields”| Field | Type | Writer | Default | Meaning |
|---|---|---|---|---|
sku |
string | server | Item number. Not unique across the inventory (see below). | |
name |
string | server | Display name | |
description |
string? | optional | none | Longer text |
category |
string? | optional | none | Free text, used for grouping and report filters |
manufacturer |
string? | optional | none | |
unit |
string? | optional | Stk |
Unit of all quantities of this article, free text (Stk, m, kg, Pkg) |
track_serials |
boolean? | optional | false |
Every unit has a serial number; movements carry one serial per unit |
min_stock |
number? | optional | none | The article is low on stock while its total stock is below this value |
purchase_price |
number? | optional | none | Net purchase price per unit in the inventory currency, at least 0 |
sale_price |
number? | optional | none | Net sales price per unit in the inventory currency, at least 0 |
accessories |
array of string? | optional | [] |
What belongs to one unit (charger, case); offered when the article is lent |
barcodes |
array? | optional | [] |
Codes printed on the article, see below |
image |
sha256? | optional | none | Attachment: JPEG photo |
erp_reference |
string? | optional | none | Key of the article in another system |
archived |
boolean? | optional | false |
Hidden from lists and pickers |
created_at |
date-time? | optional | none | When the first device created the record; display only |
barcodes
Section titled “barcodes”| Field | Type | Writer | Meaning |
|---|---|---|---|
code |
string | server | Content of the code exactly as scanned |
symbology |
string? | optional | Kind of code, lower case, for example ean13, ean8, upce, code128, code39, qr, datamatrix |
Barcodes are compared exactly. How the reference app resolves a scanned code (barcodes, GS1 codes with serial numbers, item numbers, its own label URLs) is described under Scanning codes.
Rules for writers
Section titled “Rules for writers”skuandnameare trimmed and must not be empty.- Item numbers and barcodes SHOULD be unique. The reference app refuses an item number that another article on the device already has (compared case-insensitively) and a barcode another article already uses. Two devices offline can still create the same number; servers and readers MUST accept that.
track_serialsSHOULD NOT change while the article has stock. The reference app refuses the change in that case, because existing movements would no longer match the rule "one serial per unit".- Prices are not negative.
accessoriesare trimmed, without blanks, without duplicates (case-insensitive), in the order the user typed them.created_atis set once when the record is created and kept on every later version.
Derived values
Section titled “Derived values”| Value | Rule |
|---|---|
| Stock per location | Sum of movements, see Derived state |
| Total stock | Sum of the stock rows |
| Low on stock | min_stock set and total stock below it |
| Serial numbers | State from the latest movement containing each serial |
| Last changed | Time part of hlc |
Server validation
Section titled “Server validation”| Field | Rules |
|---|---|
sku, name |
required, string |
description, category, manufacturer, erp_reference, unit |
nullable, string |
archived, track_serials |
nullable, boolean |
min_stock |
nullable, numeric |
purchase_price, sale_price |
nullable, numeric, at least 0 |
accessories |
nullable, list; each element a string |
barcodes |
nullable, list; each element an object with code (required, string) and symbology (nullable, string) |
image |
nullable, string matching ^[0-9a-f]{64}$ |
Example
Section titled “Example”{ "entity": "article", "id": "4A7C2E91-0B3D-4F6A-8E15-7C9B2D4F6A80", "op": "upsert", "hlc": "001791792000123-0000-3F2A9C1B", "data": { "sku": "200310", "name": "Cordless drill 18 V", "category": "Tools", "manufacturer": "Example Tools", "unit": "Stk", "track_serials": true, "min_stock": 2, "purchase_price": 189.9, "sale_price": 249, "accessories": ["Charger", "Battery 4 Ah", "Case"], "barcodes": [{ "code": "4006381333931", "symbology": "ean13" }], "image": "35a12c5c6f0150bef53f609d70846b1a7568a44d5565b903d2f536b0c4ac5fdf", "created_at": "2026-10-12T08:00:00Z" }}A minimal article that every reader accepts: { "sku": "100101", "name": "Wireless access point AP-7" }.
Schema
Section titled “Schema”{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Article", "description": "Item master data. Mutable: the version with the greater hlc wins. Articles that movements reference are archived instead of deleted.", "type": "object", "required": ["sku", "name"], "properties": { "sku": { "description": "Item number. Not unique across the inventory: two devices may create the same number offline. The reference app refuses a duplicate (case-insensitive) on the device that types it.", "type": "string", "minLength": 1 }, "name": { "description": "Display name.", "type": "string", "minLength": 1 }, "description": { "type": ["string", "null"] }, "category": { "description": "Free text, used for grouping and report filters.", "type": ["string", "null"] }, "manufacturer": { "type": ["string", "null"] }, "erp_reference": { "description": "Key of the article in another system (an ERP, for example).", "type": ["string", "null"] }, "archived": { "description": "Hidden from lists and pickers; used instead of deleting once movements reference the record. Absent or null means false.", "type": ["boolean", "null"] }, "unit": { "description": "Unit of quantities, free text. Absent or null means \"Stk\" (German for pieces).", "type": ["string", "null"], "default": "Stk" }, "track_serials": { "description": "Movements of this article carry one serial number per unit. Absent or null means false.", "type": ["boolean", "null"], "default": false }, "min_stock": { "description": "The article counts as low on stock while its total stock is below this value.", "type": ["number", "null"] }, "purchase_price": { "description": "Net purchase price per unit in the inventory currency (company_profile.currency).", "type": ["number", "null"], "minimum": 0 }, "sale_price": { "description": "Net sales price per unit in the inventory currency.", "type": ["number", "null"], "minimum": 0 }, "accessories": { "description": "What belongs to the article (power supply, bag, ...); suggested when it is lent. Absent or null means an empty list.", "type": ["array", "null"], "items": { "type": "string", "minLength": 1 }, "default": [] }, "barcodes": { "description": "Codes printed on the article. Absent or null means an empty list.", "type": ["array", "null"], "default": [], "items": { "type": "object", "required": ["code"], "properties": { "code": { "description": "Content of the code exactly as scanned.", "type": "string", "minLength": 1 }, "symbology": { "description": "Kind of code as reported by the scanner, lower case.", "type": ["string", "null"], "examples": ["ean13", "ean8", "upce", "code128", "code39", "qr", "datamatrix"] } }, "additionalProperties": true } }, "image": { "description": "SHA-256 of a JPEG attachment showing the article.", "type": ["string", "null"], "pattern": "^[0-9a-f]{64}$" }, "created_at": { "description": "When the record was first created, set once by the creating device. Display only.", "type": ["string", "null"], "format": "date-time" } }, "additionalProperties": true, "examples": [ { "sku": "200310", "name": "Cordless drill 18 V", "category": "Tools", "manufacturer": "Example Tools", "unit": "Stk", "track_serials": true, "min_stock": 2, "purchase_price": 189.9, "sale_price": 249, "accessories": ["Charger", "Battery 4 Ah", "Case"], "barcodes": [{ "code": "4006381333931", "symbology": "ean13" }], "image": "35a12c5c6f0150bef53f609d70846b1a7568a44d5565b903d2f536b0c4ac5fdf", "created_at": "2026-10-12T08:00:00Z" } ]}