Query like you mean it.
31 July 2026 · the launch journal
This entry is a reference, not an argument. It states which SimpleEventQuery parameters do the real work on this surface, what each matches, what surprises callers, and what the pagination contract guarantees — with executed queries and their exact result sets. A verifier agent can consume the tables and skip the prose.
The working subset
Query documents (POST /queries) and URL parameters (GET /events) pass through one validator — the sha256-pinned official query-schema.json (pin published on the conformance page) — and one engine. A parameter cannot validate on one door and not the other. The subset that answers real questions:
| Parameter | Matches | Example | Gotcha |
|---|---|---|---|
| eventType | event type name(s) | eventType=ObjectEvent | list-valued; pipe-delimited in URLs |
| GE_eventTime / LT_eventTime | when it happened | GE_eventTime=2026-07-30T06:00:00.000Z | GE inclusive, LT exclusive — half-open window |
| GE_recordTime / LT_recordTime | when the door stamped it | poll ledgers with this, not eventTime | late-arriving events reorder eventTime reads |
| MATCH_epc | epcList + childEPCs | one EPC, instance grain | does not match parentID |
| MATCH_parentID | aggregation parents | the SSCC on the pallet | the pallet's own hops need this, not MATCH_epc |
| MATCH_anyEPC | any EPC role on the event | the trace workhorse | still instance grain — see the corpus run below |
| MATCH_epcClass (+any/input/output) | class grain (LGTIN lots) | urn:epc:class:lgtin:… | instance and class are disjoint match spaces |
| MATCH_inputEPC / MATCH_outputEPC | transformation sides | recall scope, one call | pair with EQ_transformationID |
| EQ_bizStep / EQ_disposition | CBV vocabulary | EQ_bizStep=receiving | bare CBV words in 2.0 JSON, not the 1.x URN forms |
| EQ_readPoint / WD_readPoint | where, exact / with-descendants | WD_ walks the location hierarchy | EQ_ on a site GLN misses sub-locations |
| EQ_bizLocation / WD_bizLocation | business location | same semantics | same gotcha |
| EQ_bizTransaction types | the why-dimension refs | PO/despatch-advice joins | the join to /business-transactions/ |
| EQ_errorReason / EQ_correctiveEventID | declared corrections | audit views | URL spelling alias: GE_errorDeclarationTime |
| EQ_eventID | explicit IDs | point reads | prefer GET /events/{eventID} |
| eventCountLimit, orderBy, orderDirection | result shaping | orderBy=eventTime | distinct from HTTP paging (below) |
Sensor-payload predicates (EQ_deviceID, GE_time/LT_time, EQ_stringValue, quantity comparators GT_/GE_/LT_/LE_quantity, percentile-rank bounds) exist in the same schema and engine for sensor-carrying events.
Two structural facts complete the contract. Unknown parameters are refused with epcisException:QueryParameterException — a dropped predicate silently widens results, and a widened result set is a wrong answer with a confident face. Every failure is typed: RFC 7807 problem+json, epcisException:* in type, branchable without parsing prose.
Executed queries, exact results
The corpus's four-hop trace sequence — commission, pack onto an SSCC, receive the SSCC at the DC, receive the item at the store — captured into the local gateway, then queried. Transcript, unedited:
MATCH_anyEPC=sgtin...2017 -> 3 events: ObjectEvent/commissioning AggregationEvent/packing ObjectEvent/receiving
EQ_bizStep=receiving -> 2 events: ObjectEvent/receiving@06:04 ObjectEvent/receiving@06:12
GE_eventTime=2026-07-30T06:00:00Z -> 2 events: ObjectEvent/receiving@06:04 ObjectEvent/receiving@06:12
MATCH_parentID=sscc...5501 -> 1 event: AggregationEvent/packing
Read the first line twice, because it is the mistake every integrator makes once. Four events touch this item's journey; MATCH_anyEPC on the item returns three. The DC receiving hop observed the pallet — the SSCC in its epcList — and the item appears nowhere on that event. Instance-grain matching is exact, not transitive. Answering "where has this item been" requires walking containment: match the item, find the AggregationEvent that put it on sscc…5501, then match the SSCC across the containment window. The trace_epc tool on the MCP door does precisely that walk and returns the completed chain — same engine, same key, containment resolved.
The second and third lines are ordinary but worth their row in the table: CBV values in 2.0 JSON are bare words (receiving), and time windows are half-open (GE inclusive, LT exclusive), so contiguous windows never double-count an event on the boundary.
The pagination contract
- perPage caps the page; nextPageToken continues it; the Link: …; rel="next" header carries the continuation. Absence of the header is the last page — no sentinel values.
- Both are HTTP machinery, not query semantics: they never change which events match, only how many arrive per response.
- Named queries (POST /queries, then GET /queries/{name}/events) page identically. A stored query is a stored definition; results are computed at read time against the append-only record.
- On the MCP door, paginated tools lift the continuation into structuredContent.nextPageToken — pass it back to page; absence is completion.
What the standard leaves open — cursor stability under concurrent writes — this surface answers with the strongest property an append-only store permits: events are never updated or deleted, so a page you have read never changes underneath you; new captures append beyond your cursor.
The queries a verifier actually runs
GET /events?MATCH_anyEPC=urn:epc:id:sgtin:0614141.107346.2017 # everything touching this instance
GET /events?EQ_bizStep=receiving&GE_eventTime=…<_eventTime=… # a dock's day, half-open window
GET /events?MATCH_parentID=urn:epc:id:sscc:0614141.0000005501 # the pallet's own custody hops
GET /events?EQ_errorReason=incorrect_data # what this party has declared wrong
GET /epcs/urn:epc:id:sgtin:0614141.107346.2017/events # the per-EPC HATEOAS spelling
Every one of these runs today against the local store: npx epcis.dev mcp starts the stdio server, capture then query/trace_epc within the session — the MCP page has the one-block config, and run it yourself has the rest of the local surface. Reads are scoped to the caller's own account before the engine sees an event; out-of-scope is absent, not redacted — a result set never advertises what it is hiding.
For a durable hosted store to point these queries at, say what you will read — query keys are minimally scoped, append-only reads.
We answer in writing. We take at most five conversations a month, only when you ask for one, and only after you already have the written read.