Skip to content

Concepts

EuroPlaybook turns two kinds of input into filings: what you sell (sales facts per country) and how it is packed (packaging per product). Rules from the knowledge pack decide where you have obligations.

EuroPlaybook data modelThree columns. Catalogue: Account, LegalEntity, ProductGroup and Product, packaging layer, packaging line, PackagingItem, materials. Sources: Connection, SyncRun, facts. Compliance: Obligation, FilingObligation, ReportingPeriod, LedgerEntry, Pack and PackLine. Lineage links facts and packaging lines to ledger entries.CatalogueSourcesComplianceAccounttenant root, API tokensLegalEntitycompany, country, VATProductGroup › Productvariants inherit packagingPackaging layersales · grouped · transportPackaging linequantity, household, statePackagingIteme.g. “Box S”Materialscode, mass_mgConnection (source)shopify, csv, odoo, picqerSyncRun / ImportBatchone fetch or uploadFactsSalesFact, ParcelFactObligationcountry × regime, statusFilingObligationscheme, cadenceReportingPeriodopen › closed › filedLedgerEntrymass_mg per materialPack › PackLinethe filing, approved by a personobligations per companylineage: which facts and lines fed a total

The same structure as text:

Account tenant root; every record belongs to one account
├─ LegalEntity a company (country, VAT); obligations are per entity
├─ ProductGroup ─┐ e.g. one Shopify product with several variants
├─ Product ──────┘ (group optional) one SKU
│ └─ packaging layer: sales | grouped | transport
│ └─ packaging line quantity per sales unit, household, state, source
│ └─ PackagingItem reusable item, e.g. "Box S"
│ └─ materials material code + mass_mg
├─ Connection (shopify | csv | odoo | picqer)
│ └─ SyncRun / ImportBatch
│ └─ facts: SalesFact (units per country), ParcelFact
└─ Obligation (entity × country × regime)
└─ FilingObligation (scheme, cadence)
└─ ReportingPeriod
├─ LedgerEntry facts × effective packaging, per material
└─ Pack › PackLine the filing; lineage points back to entries

Tenancy. Every record belongs to one account. An API token belongs to one account, so you never pass an account id.

Effective packaging. A product’s packaging is read per layer: if the product has its own lines on a layer, they apply; otherwise it inherits the lines of its group. API responses mark inherited lines with inherited: true and group_id; list groups with GET /product-groups.

What the API writes. Product data (PUT /products/by-sku/{sku}) and sales lines (POST /sales) are written directly. Packaging lines and regime profiles are only ever proposals (state: proposed, source: api): a person confirms them in the app. A product’s confirmation.state is none, confirmed or changed (confirmed before, but its packaging changed since). Packaging proposals clear that confirmation.

Provenance. Packaging lines and materials carry source (manual, csv, odoo, shopify, picqer, supplier_spec, photo, api, ai) and a free-text source_ref. Evidence (photos, spec sheets) can be attached to a product, item or line; read them (with a signed download URL) at GET /products/{id}/evidence.

Regimes. A regime is a type of EPR, such as epr.packaging, epr.weee or epr.textiles. Regime-specific product data lives in regime profiles (GET /products/{id}/profiles), each with valid_from / valid_to.

Periods and gates. Figures for a period are built into ledger entries, then locked (closed) by a person, then a pack is approved by an owner or admin, then marked filed. The API reads periods, totals and packs; it does not lock, approve or file.

Lineage. Every figure of a period can be traced back: GET /periods/{id}/totals/{key}/explain (MCP: explain_total) returns the ledger entries behind a pack line, the products, the packaging lines used (own or inherited, with source and evidence count) and the sales per month and channel. The trail is recorded when the ledger is built and kept when the period is locked.

List endpoints return a page of items and a cursor for the next page:

{
"items": [ { "id": "…", "sku": "DG-HOSE-25" } ],
"next_cursor": "eyJpZCI6IjAxOTIifQ"
}
  • Pass ?cursor=<next_cursor> to get the next page. Keep the other query parameters the same.
  • next_cursor: null means this was the last page.
  • ?limit= sets the page size: 50 by default, at most 200.
  • Cursors are opaque. Do not build or parse them; a broken cursor is 400 invalid_cursor.
  • Paging is by key, so items added while you page do not shift the pages. There are no total counts.

The full list of fields and enums is in the data model.