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 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 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.