# 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](/conformance/)) — 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/](/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](/mcp/) 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](/mcp/) has the one-block config, and [run it yourself](/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](/get-a-key/?segment=query) — query keys are minimally scoped, append-only reads.

---

**Proof, not adjectives.** This build could not verify the gateway's test suite green, so no sentence on this page claims a passing run — the last verified run is recorded in the repository's own CI, not here. Live on this origin, no key: POST /translate, /validate, /hash. No conformance attestation has ever been issued. The dated ledger is /what-ships-today/.
