Skip to content

Data model

This page is written for machines as well as people: plain Markdown, no components. The same text is served raw at /data-model.md. The field tables are generated from the OpenAPI document, so they match the API.

  • IDs: UUID strings.
  • Weights: integer milligrams, field names end in _mg. 1 kg = 1000000 mg.
  • Money: integer cents, field names end in _cents, plus currency (ISO 4217, e.g. EUR).
  • Countries: ISO 3166-1 alpha-2 (NL, BE, DE, FR, AT).
  • Dates: ISO 8601 (2026-10-03); timestamps with time zone.
  • Validity: valid_from is the first day (inclusive), valid_to the end day (exclusive); null means open-ended.
  • Packaging quantities: decimal strings ("1", "0.5"), parts per sales unit.
  • Codes from the knowledge pack (materials, layers, regimes, countries) are short text. Their lists come from GET /reference/{materials|layers|regimes|countries}.
  • Tenancy: every record belongs to exactly one account. An API key belongs to one account.
  • Errors: {detail, code, params?}; see errors.

GET /account returns the account of the key, the key’s name and prefix, and the scopes in force now.

Fields of AccountOut:

Field Type Notes
id uuid The account (brand) this API key belongs to.
name string Account name.
plan string Subscription plan.
token_name string Name of the API key making this call.
token_prefix string Public prefix of the API key (ep_<prefix>_…).
scopes string[] Scopes in force for this key now: its own scopes limited by its creator’s current role.

A legal entity is a company inside the account. Obligations, periods and sales are per legal entity.

Fields of LegalEntityOut:

Field Type Notes
id uuid
name string Registered company name.
legal_form string Legal form.
country string Country of registration (ISO 3166-1 alpha-2).
registry_type string Company registry (kvk, kbo, …).
registry_id string Number in that registry.
vat_number string VAT number.
is_default boolean The company used when a sale names none.
archived_at date-time | null Set when the company was archived.

Team roles, low to high: readonly < accountant < member < admin < owner. Owners, admins and members can use every API scope; accountants and readers only the :read scopes. Keys: ep_<prefix8>_<secret32>, shown once, effective rights = scopes ∩ the creator’s current role.

A product is one SKU. confirmation.state is none, confirmed or changed (confirmed before, packaging differs now).

Fields of ProductOut:

Field Type Notes
id uuid
sku string Your stock keeping unit; unique per account.
title string Product name.
brand_name string Brand on the product.
gtin string | null GTIN/EAN (8-14 digits, check digit validated).
hs_code string Customs tariff code.
country_of_origin string ISO 3166-1 alpha-2.
net_weight_mg integer | null Net product weight in milligrams.
status “active” | “archived” Archived products are kept for history.
is_kit boolean A bundle of other products (bill of materials).
is_electrical boolean Electrical/electronic equipment (WEEE).
is_textile boolean Textile product (textiles EPR).
packaging_confirmed boolean Gate 1: a person confirmed this product’s packaging. Changing packaging (also through a proposal) clears it until someone confirms again in the portal.
confirmed_at date-time | null When the packaging was confirmed.
confirmation ConfirmationOut Gate 1 in detail (complements packaging_confirmed, which equals state confirmed when the packaging is unchanged).
group_id uuid | null The product group whose packaging this product inherits (per layer), if any.
archived_at date-time | null
created_at date-time
updated_at date-time

Fields of ConfirmationOut:

Field Type Notes
state “none” | “confirmed” | “changed” none = never confirmed; confirmed = the packaging is what a person confirmed; changed = it was confirmed, but the packaging (own or inherited) differs now.
at date-time | null When the current confirmation was given.
by string | null Who confirmed (display name).

PUT /products/by-sku/{sku} creates or updates a product directly (201 / 200):

Fields of ProductUpsertIn:

Field Type Notes
title string Required. Product name.
brand_name string | null Brand on the product.
gtin string | null GTIN/EAN; null clears it.
hs_code string | null Customs tariff code.
country_of_origin string | null ISO 3166-1 alpha-2.
net_weight_mg integer | null Net weight in milligrams.
is_electrical boolean | null Electrical/electronic equipment (WEEE).
is_textile boolean | null Textile product.

A product group holds variants that share packaging, e.g. one Shopify product with several variants. GET /product-groups/{id} adds the members (with the layers they override) and the group’s packaging.

Fields of ProductGroupOut:

Field Type Notes
id uuid
name string
code string Unique per account: a Shopify product gid or a SKU stem.
source “manual” | “csv” | “odoo” | “shopify” | “picqer” | “supplier_spec” | “photo” | “api” | “ai”
source_ref string
member_count integer Products in the group.
packaging_count integer Packaging lines of the group (all versions).
archived_at date-time | null
created_at date-time
updated_at date-time

Layers: sales (Dutch app: Productverpakking; what the customer receives), grouped (Omverpakking; around several sales units), transport (Transportverpakking; pallets, stretch film). Packaging of web-shop parcels is kept apart, as parcel profiles per size class (lt40, 40to90, gt90 litres); it is not in the public API.

Effective packaging, per layer: if the product has its own lines on a layer they apply; otherwise the group’s lines apply (inherited: true, group_id).

GET /products/{id}/packaging returns the tree layers[] > lines[] > item > materials[]:

Fields of PackagingTreeOut:

Field Type Notes
product_id uuid
sku string
group_id uuid | null The product’s group, if any.
on date The day the totals are for (query parameter on, default today).
packaging_confirmed boolean Gate 1 passed (see ProductOut.packaging_confirmed).
confirmed_at date-time | null
confirmation ConfirmationOut
layers PackagingLayerOut[]
totals_by_material MaterialMassOut[] Packaging per sales unit by material on on (lines with in_totals), heaviest first.
total_mass_mg integer Sum of totals_by_material.
findings FindingOut[] Automatic plausibility checks on today’s lines.

Fields of PackagingLayerOut:

Field Type Notes
layer “sales” | “grouped” | “transport” sales (primary), grouped (secondary) or transport (tertiary).
lines PackagingLineOut[]
mass_mg integer Mass per sales unit of this layer’s lines that count in the totals.

Fields of PackagingLineOut:

Field Type Notes
id uuid
state “proposed” | “confirmed” proposed = not yet checked by a person; confirmed = part of a gate-1 confirmation.
quantity string Parts per sales unit (may be a fraction, e.g. 1 film per 1440).
household boolean Ends up with households (counts as household packaging).
scope_channel string | null Only for sales in this channel; null = all.
scope_fulfilment string | null Only for this fulfilment; null = all.
valid_from date | null First day (inclusive); null = from the start.
valid_to date | null End day (exclusive); null = open-ended.
mass_mg integer Packaging mass per sales unit for this line (item mass x quantity).
in_totals boolean Counted in totals_by_material: valid on on and not limited to a channel/fulfilment.
source “manual” | “csv” | “odoo” | “shopify” | “picqer” | “supplier_spec” | “photo” | “api” | “ai” Where the line came from (provenance).
source_ref string Which record of that source, e.g. api:token:<prefix>.
inherited boolean The line is the product group’s: the product has no own line on that layer.
group_id uuid | null The group the line belongs to (inherited lines).
item PackagingItemOut

Fields of PackagingItemOut:

Field Type Notes
id uuid
name string Name of the packaging part.
code string Your code for the part (unique per account when set).
role_default string Usual role: sales, grouped, transport, shipping or filler.
household_default boolean Usually ends up with households (vs. businesses).
separable boolean Can be separated from the other parts by hand.
rigidity “rigid” | “flexible”
recycled_content_pct string | null Recycled content in percent.
recyclability_grade string | null A-E, when known.
custom_for_product_id uuid | null Set for a part made for one product only (not in the shared library).
materials MaterialMassOut[] Material breakdown of ONE part.
total_mass_mg integer Sum of the materials, milligrams.
archived_at date-time | null
created_at date-time
updated_at date-time

Fields of MaterialMassOut:

Field Type Notes
material string Material code from the knowledge pack (see /reference/materials).
mass_mg integer Mass in milligrams.

Fields of FindingOut:

Field Type Notes
code string Stable check code.
severity string info, warning or error.
params object Values for the message, per code.
line_id uuid | null The packaging line the finding is about, if any.
dismissed boolean A person marked this finding as right (‘klopt zo’).

POST /products/{id}/packaging/proposals adds lines with state: proposed; a person confirms them in the app. Each line names an existing item_id or a new_item:

Fields of ProposalLineIn:

Field Type Notes
item_id uuid | null An existing packaging item.
new_item NewPartIn | null A new part for this product only.
layer “sales” | “grouped” | “transport”
quantity number | string Parts per per_units sales units.
per_units integer | null E.g. 1 film per 1440 jars: quantity 1, per_units 1440.
household boolean
scope_channel “webshop” | “bol” | “amazon” | “odoo” | “other” | null Only for this sales channel.
scope_fulfilment “own” | “fba” | “lvb” | “3pl” | null Only for this fulfilment.
valid_from date | null First day (inclusive); null = from the start.
valid_to date | null End (exclusive); null = open-ended.

Fields of NewPartIn:

Field Type Notes
name string Required.
materials MaterialMassIn[] Required. Material breakdown of one part.
rigidity “rigid” | “flexible”
separable boolean

Files that back up packaging data (photos, spec sheets): jpeg/png/webp/heic/pdf, max 15 MB, uploaded in the app. GET /products/{id}/evidence lists them with a short-lived signed download_url (GET it without the Authorization header).

Fields of EvidenceOut:

Field Type Notes
id uuid
kind “photo” | “spec_sheet” | “invoice” | “other”
note string
target “product” | “item” | “packaging_line” What the file is about.
product_id uuid | null
item_id uuid | null
packaging_line_id uuid | null
filename string
mime string
size_bytes integer
sha256 string
created_at date-time
download_url string Short-lived signed URL; GET it without the Authorization header.

Product data for a regime other than packaging, validated by the knowledge pack’s JSON Schema. epr.weee data: in_scope, category (1-6), weight_mg, has_battery, battery_weight_mg, notes. epr.textiles data: in_scope, weight_mg, notes. GET /products/{id}/profiles lists own and inherited profiles, all versions, with in_force per regime today. PUT /products/{id}/profiles/{regime} stores a proposal (state: proposed, source: api).

Fields of RegimeProfileOut:

Field Type Notes
id uuid
regime string
schema_version string
data object
state “proposed” | “confirmed” proposed (e.g. from the API) or confirmed by a person.
inherited boolean The group’s profile (the product has no own one for the regime).
group_id uuid | null
valid_from date | null
valid_to date | null Exclusive; null = open-ended.
source “manual” | “csv” | “odoo” | “shopify” | “picqer” | “supplier_spec” | “photo” | “api” | “ai”
source_ref string
confirmed_at date-time | null

Fields of ProfileProposalIn:

Field Type Notes
data object Required. Profile data; must match the regime’s JSON Schema.
valid_from date | null Start a new version on this day; omit to change the version valid today.

Fields of ReferenceOut:

Field Type Notes
code string
name object Name per language (nl, en).
verified boolean False until checked against the cited sources.
sources string[] URLs of the sources the fact comes from.
parent string | null Parent material code (materials only).
scheme_categories object Scheme code -> that scheme’s category.

POST /sales takes up to 1000 lines and writes them directly, idempotent on external_id. No consumer personal data is ever stored: a country is all EuroPlaybook needs.

Fields of SalesLineIn:

Field Type Notes
external_id string Required. Your unique id for this line; sending it again updates the line.
sku string Required. Your SKU; matched to a product.
sold_on date Required. Sale (or return) date.
quantity integer Required. Units (positive; use kind=return for returns).
kind “sale” | “return”
ship_from_country string Required. ISO 3166-1 alpha-2.
ship_to_country string Required. ISO 3166-1 alpha-2.
legal_entity_id uuid | null Selling company; default: the account’s default.
channel “webshop” | “bol” | “amazon” | “odoo” | “other”
marketplace string | null Marketplace code, e.g. amazon.de.
buyer_type “consumer” | “business_end_user” | “reseller” | “unknown”
fulfilment “own” | “fba” | “lvb” | “3pl”
net_amount_cents integer | null Net line amount in cents (optional).
currency string

Fields of SalesPushOut:

Field Type Notes
created integer New lines.
updated integer Lines whose values changed.
unchanged integer Lines sent before with the same values.
held string[] external_ids not stored because a closed or filed period covers them (reopen it first).
unknown_skus string[] SKUs without a product (stored; map or create the product).

GET /sales lists stored lines, from every source:

Fields of SalesLineOut:

Field Type Notes
id uuid
external_id string | null Your id (lines pushed through the API).
source string Where the line came from: api, csv, odoo, shopify, picqer.
sku string
product_id uuid | null Matched product; null = unknown SKU.
legal_entity_id uuid
sold_on date
kind “sale” | “return”
quantity integer
ship_from_country string
ship_to_country string
channel string
marketplace string | null
buyer_type string
fulfilment string

Per legal entity × jurisdiction (country) × regime (× scope), derived by the knowledge pack’s rules unless a person overrode the status.

Fields of ObligationOut:

Field Type Notes
id uuid
legal_entity_id uuid
jurisdiction string
regime string
scope string
status string Shown status (see years for every derived year).
status_source string rule = derived; user = overridden by a person.
status_reason string
status_year integer | null
since date | null
pack_version string
years ObligationYearOut[]

Fields of ObligationYearOut:

Field Type Notes
year integer
status string needed, not_needed, unknown, … for that year.
reason_code string Stable code of the rule outcome.
reason_params object
status_reason string The reason in English.
rule_key string Knowledge-pack rule the status rests on.
verified boolean The rule and its figures are verified against sources.

A period is built from sales (and parcel) facts × effective packaging into ledger entries per material. A person locks it (closed); later uploads do not change a locked period.

Fields of PeriodOut:

Field Type Notes
id uuid
legal_entity_id uuid
jurisdiction string
scheme string
report_type string
cadence string
starts_on date
ends_on date
status string open, review, closed, filed or reopened.
closed_at date-time | null

Fields of PeriodTotalsOut:

Field Type Notes
period PeriodOut
built_at date-time | null When the ledger was last built; null = never.
stale boolean Facts or packaging changed since the last build.
blockers string[] Why the period could not be closed right now (codes).
unknown_skus integer
unconfirmed_products integer
totals TotalOut[]
declared any What a pack made from this build would declare (scheme lines).

Fields of TotalOut:

Field Type Notes
kind string product, packaging, parcel_packaging or adjustment.
material string | null
household boolean
obligated_party string
exclusion string
fact_kind string
quantity integer
mass_mg integer
size_class string | null
parcels integer

Lineage. GET /periods/{id}/totals/{key}/explain (MCP explain_total; key from declared.lines[].key of the totals) traces one figure back to its ledger entries, the products with the packaging lines used (own or inherited, with source and evidence count), and the sales per month and channel. traced: false means the period was built before lineage was recorded: rebuild it.

Fields of ExplainTotalOut:

Field Type Notes
period_id uuid
period_status “open” | “review” | “closed” | “filed” | “reopened”
line ExplainLineOut
entry_match string How entries were matched to the line: indexes (by the calculator) or material.
traced boolean Lineage was recorded at the build (false: built before; rebuild the period).
entries ExplainEntryOut[]
products ExplainProductOut[] Products behind the line, largest share first.
parcels ExplainParcelsOut[] Parcel facts behind parcel packaging entries.

The filing for a period and scheme, approved by a person.

Fields of PackOut:

Field Type Notes
id uuid
period_id uuid
scheme string
report_type string
kind string original or amendment.
status string draft, ready, approved or filed.
format string
has_file boolean
approved_at date-time | null
filed_at date-time | null
filing_ref string
created_at date-time

Fields of PackLineOut:

Field Type Notes
line_no integer
key string
label string
value string kg with three decimals; counts as whole numbers.
unit “kg” | “units” | “parcels”

GET /packs/{id}/download returns a short-lived URL to the file:

Fields of DownloadOut:

Field Type Notes
url string Short-lived signed URL; GET it without the Authorization header.
filename string
expires_in integer Seconds until the URL stops working.
  1. Sources (Shopify, Picqer, Odoo, CSV uploads, the API) produce sales facts and parcel counts.
  2. Products have effective packaging (own or inherited lines), confirmed by a person.
  3. The knowledge pack derives obligations per legal entity.
  4. A period build multiplies facts by effective packaging into ledger entries, per material.
  5. A person locks the period, an owner or admin approves the pack, someone marks it filed with the scheme’s reference.

Confirm packaging or regime profiles, lock a period, approve a pack or mark it filed. Those are human gates.