# epcis.dev — Post an EPCIS event. Get back a validated, hashed, append-only record.

> EPCIS 2.0 capture gateway Send an EPCIS 2.0 document. It is checked against GS1's pinned schema, stamped, hashed by CBV 2.0 §8.9 and appended — or refused with the standard's own exception type and a pointer to the field. Translate, validate and hash run here with no key. Capture, query and MCP open with one. EPCIS records the company. epcis.dev also records who performed the event.

`npx epcis.dev` is the one-line local door — translate, validate, hash, capture, MCP — the same engine this origin hosts at POST /translate, /validate and /hash.

## One specimen, three verbs

A receiving event in EPCIS 1.2 XML, run through the three calculators.

| # | verb | against | answer |
|---|---|---|---|
| 01 | translate | EPCIS 1.2 XML → 2.0 JSON-LD | round-trip-clean · 0 lossy paths |
| 02 | validate | GS1's official 2.0.1 JSON Schema, pinned | conforms · sha256 0f46ff694efffd8d8ce840a33dfde84228add11b516b8b258f3200740ae210af |
| 03 | hash | CBV 2.0 §8.9 event identity | ni:///sha-256;d2f4ce56044660b70ca86883f1ce4b61f4c3ae13c25a5378373dd4cff1b203da?ver=CBV2.0 |

Reproduce row 03. No key, no account:

```sh
curl -sS https://epcis.dev/hash \
  -H 'content-type: application/json' \
  -d '{"type":"ObjectEvent","eventTime":"2026-08-06T14:20:07.000Z","eventTimeZoneOffset":"+00:00","epcList":["urn:epc:id:sgtin:0614141.107346.2018"],"action":"OBSERVE","bizStep":"receiving","disposition":"in_progress","readPoint":{"id":"urn:epc:id:sgln:0614141.00777.0"},"bizLocation":{"id":"urn:epc:id:sgln:0614141.00888.0"}}'

{"hashes":["ni:///sha-256;d2f4ce56044660b70ca86883f1ce4b61f4c3ae13c25a5378373dd4cff1b203da?ver=CBV2.0"]}
```

## The eight laws

1. **Validated on every capture.** Each event is machine-checked against the official GS1 EPCIS 2.0.1 JSON schema before anything else happens. The schema is pinned by digest, and every verdict names the digest that judged it.
2. **Refused in the standard's own words.** A capture that does not validate comes back as RFC 7807 problem+json carrying the standard's own exception type and a JSON Pointer at the field that caused it. Never a vendor error string.
3. **Stamped by the gateway, not the sender.** recordTime, capturedBy and the attestation grade are applied at the door. Whatever you sent in those fields is stripped. The scriber is not the part being scribed.
4. **Two grains, never collapsed.** who is the attested observer — a human, an agent, or an embodied agent — asserted by the caller and graded by the door. capturedBy is the warrantor: the account whose key opened the door. Never the same thing, never merged.
5. **Identified by the standard's own hash.** The EPCIS event hash from CBV 2.0 §8.9, with GS1 Digital Link normalization, checked against the pinned reference vectors — so the same event captured twice is the same event, and a changed event is a different one.
6. **Append-only, by construction.** No service identity anywhere holds an UPDATE or DELETE grant; the write path has exactly one writer. You can add an event. You cannot un-write one. Corrections are declared events, never edits.
7. **Minimally scoped on read.** You see your own scope. What is outside it is absent — not grayed out and not redacted. There is no shape left behind to argue about.
8. **Conforms to EPCIS 2.0 and CBV 2.0.** Implemented against GS1's official specifications, pinned by digest. The spine speaks What/Who/When/Where/Why/How natively; EPCIS 2.0 is its projection, and the projection always validates.

What the laws add up to: Everyone in this market has built a network that is authoritative because you joined it. The spine makes a record that is verifiable whether or not you joined anything — and it carries, in the record itself, an attested answer to who observed the event, including when the observer was an agent.

## A refusal is a result

There is no error banner in this interface. A document that fails the schema comes back as a verdict naming the pinned digest that judged it and a JSON Pointer at the field:

```json
{"valid": false, "errors": [{"path": "/epcisBody/eventList/0/eventTime", "keyword": "format", "message": "must match format \"date-time\""}]}
```

A malformed body — broken JSON, the wrong media type, a payload over the cap — comes back as RFC 7807 application/problem+json carrying the standard's own exception type (epcisException:ValidationException, epcisException:UnsupportedMediaTypeException, epcisException:CaptureLimitExceededException). Every `type` URI dereferences to a page under /errors/.

## Where it stops

| limit | value |
|---|---|
| payload | 6 MiB (6,291,456 bytes) per document |
| auth | none for /translate, /validate, /hash; a capture key for capture, query and MCP |
| rate limit | none published; when one is set it is printed here and in /openapi.json first |
| retention | the calculators store nothing; /what-we-log is the disclosure |

## The second Who

EPCIS answers what, when, where, why and how, and its Who is the GLN of a legal entity — org grain, and correct. A company has no hands, so id.org.ai (Agent. Human. Thing.) names the performer beneath the party. That is an extension, not a fork, and it is checkable: the same event carrying `spine:who` validates `true` against the same pinned GS1 schema and hashes to ni:///sha-256;05d248fa2169e6bc47247bb1b51518f288c87df5d875dc32ea147d4806ed9312?ver=CBV2.0.

capturedBy is the warrantor — the account whose key opened the door — and the gateway STAMPS it, together with recordTime and the attestation grade: caller-supplied values in those three fields are stripped and replaced. who is the attested observer — a human, an agent, or an embodied agent — asserted by the caller and GRADED by the door, reaching only the grade `claimed` until identity attestation lands. The two never collapse. Party and org grain are derived at read time from grant chains, never stamped on an event.

## The MCP door (one door for people and agents)

| tool | what it does | readOnlyHint | state |
|---|---|---|---|
| capture | POST an EPCIS document through the gateway laws: strip → validate → project-check → stamp → append | false | shipped |
| query | SimpleEventQuery over whatever was captured | true | shipped |
| get_event | one event, by its CBV §8.9 hash identity | true | shipped |
| translate | EPCIS 1.1 / 1.2 / 2.0 XML → 2.0 JSON-LD, with a per-job fidelity report | true | shipped |
| resolve | identity resolution, via id.org.ai (Agent. Human. Thing.) | true | Coming soon |
| subscribe | push subscriptions | false | Coming soon |

The endpoint is POST /mcp on this origin, and npx epcis.dev mcp runs the same server on your bench.

## $0 per event

Recording an event never costs money and is never shown as a meter. Revenue attaches to answers on the executive side (visibility.cloud).

## Doors

- /translate/ — EPCIS 1.1/1.2/2.0 XML to 2.0 JSON-LD with a per-job fidelity report
- /conformance/ — the pinned GS1 digests and the advisory suite (no attestation has ever been issued)
- /mcp/ — the agent door
- /run-it-yourself/ — the five-minute local capture transcript
- /docs/ — the reference
- /get-a-key/ — the capture-key waitlist (machine twin of the interview: /flow.json)
- /llms.txt · /icp.json · /agent-classes.json · /.well-known/agents.json — the machine face
