Skip to content

Entity reference

Version 1 defines eight entity types. Each page lists every field with its type, whether a writer must send it, the default a reader applies when it is absent or null, its meaning, the checks of the reference server, and an example.

Entity Mutable Ids Written by the reference app when
article yes random an article is created, edited, archived, gets a photo
location yes random a location is created, edited, archived
recipient yes random a customer, site or employee is created or edited
movement no random stock is booked: receipt, issue, transfer, count, and by delivery notes and loans
delivery_note yes random a delivery note is issued, a signature is added later
company_profile yes fixed 00000000-0000-4000-8000-000000000001 the company details are saved
document_template yes fixed ...0002 (delivery notes), ...0003 (loans) document settings or loan conditions are saved
loan yes random equipment is handed out, a return is recorded

company_profile and the two document_template records exist once per inventory and use fixed ids, so every device writes the same record instead of creating its own:

Record Entity Id
Company profile company_profile 00000000-0000-4000-8000-000000000001
Delivery note template document_template 00000000-0000-4000-8000-000000000002
Loan template document_template 00000000-0000-4000-8000-000000000003

A client that finds no record under a fixed id uses the defaults (an empty company profile, a template with all defaults).

erDiagram
  location ||--o{ location : "parent_id"
  article ||--o{ movement : "article_id"
  location ||--o{ movement : "from_location_id / to_location_id"
  delivery_note ||--o{ movement : "delivery_note_id (issue)"
  loan ||--o{ movement : "loan_id (issue, receipt)"
  recipient ||--o{ delivery_note : "recipient_id"
  recipient ||--o{ loan : "borrower_id"
  article ||--o{ delivery_note : "items[].article_id"
  article ||--o{ loan : "items[].article_id"

References are UUIDs without integrity guarantees. Records arrive in any order and may refer to records a client has not received yet, or to records that were deleted. Clients show what they can and keep the reference; documents carry copies of what they print so that they stay readable.

Column Meaning
Field JSON key in data
Type JSON type. string? means string or null. uuid is a UUID string, sha256 a lowercase hexadecimal SHA-256 string, date-time an RFC 3339 timestamp, hlc a stamp
Writer required: a writer MUST send it, readers (the reference app) do not show the record without it. server: the reference server also rejects a change without it. optional: may be left out
Default Value a reader applies when the field is absent or null

Every payload may contain further fields; they are kept and relayed (see Unknown fields).

The reference server checks pushed payloads of known entity types with the rules listed on each page and rejects a failing change with invalid_payload. The rules are deliberately weaker than the schemas: they check what the server needs to store and project a record, not everything a client needs to show it. Laravel's rules apply, which means:

  • required fails for a missing field, null, an empty string, a string of only spaces and an empty array.
  • string needs a JSON string; nullable allows null.
  • numeric accepts JSON numbers and numeric strings ("5"); integer accepts integers and integer strings; boolean accepts true, false, 0, 1, "0", "1". Writers MUST still send strict JSON types, because clients may not accept the looser forms.
  • uuid accepts the 8-4-4-4-12 form in any case; date accepts any value PHP can parse as a date.
  • list needs a JSON array; array accepts a JSON object or array.
  • Rules on items.*.field apply to every element of an array.
schemas/change.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Change",
"description": "Transport form of one record version, used in push requests, pull responses and the iCloud mapping. A delete carries data null and acts as a tombstone. The data of known entity types follows the schema of that entity; unknown entity types are stored and relayed unchanged.",
"type": "object",
"required": ["entity", "id", "op", "hlc"],
"properties": {
"entity": {
"description": "Record type. Lower-case letters, digits and underscores, starting with a letter, at most 64 characters. Clients and servers store and relay types they do not know.",
"type": "string",
"pattern": "^[a-z][a-z0-9_]*$",
"maxLength": 64,
"examples": ["article", "location", "recipient", "movement", "delivery_note", "company_profile", "document_template", "loan"]
},
"id": {
"description": "UUID of the record, unique per entity type. Writers send the canonical upper-case form; servers compare case-insensitively and return upper case.",
"type": "string",
"format": "uuid",
"pattern": "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$"
},
"op": {
"description": "upsert stores data as the new version; delete stores a tombstone.",
"type": "string",
"enum": ["upsert", "delete"]
},
"hlc": {
"description": "Version stamp, a hybrid logical clock: 15 decimal digits of milliseconds since 1970-01-01T00:00:00Z, a 4 digit hexadecimal counter and the 8 character hexadecimal node id of the device, joined with '-'. Writers use upper-case hexadecimal digits; byte-wise string comparison then orders stamps.",
"type": "string",
"pattern": "^[0-9]{15}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{8}$",
"examples": ["001791792000123-0000-3F2A9C1B"]
},
"data": {
"description": "Record payload for upsert (a JSON object), null for delete. Keys are snake_case; unknown keys are kept.",
"type": ["object", "null"]
}
},
"additionalProperties": true,
"allOf": [
{
"if": { "properties": { "op": { "const": "delete" } }, "required": ["op"] },
"then": { "properties": { "data": { "type": "null" } } }
},
{
"if": { "properties": { "op": { "const": "upsert" } }, "required": ["op"] },
"then": { "required": ["data"], "properties": { "data": { "type": "object" } } }
},
{
"if": { "properties": { "entity": { "const": "article" }, "op": { "const": "upsert" } }, "required": ["entity", "op"] },
"then": { "properties": { "data": { "$ref": "article.json" } } }
},
{
"if": { "properties": { "entity": { "const": "location" }, "op": { "const": "upsert" } }, "required": ["entity", "op"] },
"then": { "properties": { "data": { "$ref": "location.json" } } }
},
{
"if": { "properties": { "entity": { "const": "recipient" }, "op": { "const": "upsert" } }, "required": ["entity", "op"] },
"then": { "properties": { "data": { "$ref": "recipient.json" } } }
},
{
"if": { "properties": { "entity": { "const": "movement" }, "op": { "const": "upsert" } }, "required": ["entity", "op"] },
"then": { "properties": { "data": { "$ref": "movement.json" } } }
},
{
"if": { "properties": { "entity": { "const": "delivery_note" }, "op": { "const": "upsert" } }, "required": ["entity", "op"] },
"then": { "properties": { "data": { "$ref": "delivery_note.json" } } }
},
{
"if": { "properties": { "entity": { "const": "company_profile" }, "op": { "const": "upsert" } }, "required": ["entity", "op"] },
"then": { "properties": { "data": { "$ref": "company_profile.json" } } }
},
{
"if": { "properties": { "entity": { "const": "document_template" }, "op": { "const": "upsert" } }, "required": ["entity", "op"] },
"then": { "properties": { "data": { "$ref": "document_template.json" } } }
},
{
"if": { "properties": { "entity": { "const": "loan" }, "op": { "const": "upsert" } }, "required": ["entity", "op"] },
"then": { "properties": { "data": { "$ref": "loan.json" } } }
}
],
"examples": [
{
"entity": "article",
"id": "8C1F3D2E-5B7A-4A61-9F0E-2B4C6D8E0A13",
"op": "upsert",
"hlc": "001791792000123-0000-3F2A9C1B",
"data": { "sku": "100101", "name": "Wireless access point AP-7", "unit": "Stk" }
},
{
"entity": "location",
"id": "6F3D9A20-1C5B-4E77-8A2D-3B9C0E4F5A61",
"op": "delete",
"hlc": "001791792060000-0002-9D04E6A2",
"data": null
}
]
}