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_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=…&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_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.