The launch journal

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:

ParameterMatchesExampleGotcha
eventTypeevent type name(s)eventType=ObjectEventlist-valued; pipe-delimited in URLs
GE_eventTime / LT_eventTimewhen it happenedGE_eventTime=2026-07-30T06:00:00.000ZGE inclusive, LT exclusive — half-open window
GE_recordTime / LT_recordTimewhen the door stamped itpoll ledgers with this, not eventTimelate-arriving events reorder eventTime reads
MATCH_epcepcList + childEPCsone EPC, instance graindoes not match parentID
MATCH_parentIDaggregation parentsthe SSCC on the palletthe pallet's own hops need this, not MATCH_epc
MATCH_anyEPCany EPC role on the eventthe trace workhorsestill 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_outputEPCtransformation sidesrecall scope, one callpair with EQ_transformationID
EQ_bizStep / EQ_dispositionCBV vocabularyEQ_bizStep=receivingbare CBV words in 2.0 JSON, not the 1.x URN forms
EQ_readPoint / WD_readPointwhere, exact / with-descendantsWD_ walks the location hierarchyEQ_ on a site GLN misses sub-locations
EQ_bizLocation / WD_bizLocationbusiness locationsame semanticssame gotcha
EQ_bizTransaction typesthe why-dimension refsPO/despatch-advice joinsthe join to /business-transactions/
EQ_errorReason / EQ_correctiveEventIDdeclared correctionsaudit viewsURL spelling alias: GE_errorDeclarationTime
EQ_eventIDexplicit IDspoint readsprefer GET /events/{eventID}
eventCountLimit, orderBy, orderDirectionresult shapingorderBy=eventTimedistinct 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=…&LT_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.