In EPCIS, the Who is a company. Every event has a performer.
Post the event.
The door does the rest.
The barcode that could not say which one, and the record that cannot say who, are the same defect at two different grains.
Grain is the size of the thing a record is about. A company is one grain. A batch is finer. A single unit — this case, and not the one behind it — is the finest there is.
EPCIS is GS1's standard for supply-chain events — what, when, where, why, how — and its Who is a company: the GLN of the legal entity involved. This door is the capture gateway for it: three calculators answer on this origin with no key, and the write half validates, stamps, hashes and appends.
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. npx epcis.dev runs the same engine on your bench — translate, validate, hash, capture, MCP — and npm i epcis.dev fetches it MIT-licensed with no runtime dependencies, so every verb on this page runs offline before you believe a sentence of it. The dated ledger is What ships today.
<?xml version="1.0" encoding="UTF-8"?>
<epcis:EPCISDocument xmlns:epcis="urn:epcglobal:epcis:xsd:1"
schemaVersion="1.2" creationDate="2026-08-06T14:20:07Z">
<EPCISBody>
<EventList>
<ObjectEvent>
<eventTime>2026-08-06T14:20:07.000Z</eventTime>
<eventTimeZoneOffset>+00:00</eventTimeZoneOffset>
<epcList><epc>urn:epc:id:sgtin:0614141.107346.2018</epc></epcList>
<action>OBSERVE</action>
<bizStep>urn:epcglobal:cbv:bizstep:receiving</bizStep>
<disposition>urn:epcglobal:cbv:disp:in_progress</disposition>
<readPoint><id>urn:epc:id:sgln:0614141.00777.0</id></readPoint>
<bizLocation><id>urn:epc:id:sgln:0614141.00888.0</id></bizLocation>
</ObjectEvent>
</EventList>
</EPCISBody>
</epcis:EPCISDocument>
- 01 translateEPCIS 1.2 XML → 2.0 JSON-LD round-trip-clean · 0 lossy paths
- 02 validateGS1's official 2.0.1 JSON Schema, pinned conforms · sha256 0f46ff694efffd8d8ce840a33dfde84228add11b516b8b258f3200740ae210af
- 03 hashCBV 2.0 §8.9 event identity
The same event, with a performer in it, returns the same verdict from the same schema. The second Who ↓
Reproduce row 03. No key, no account.
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"]}
A refusal is a result.
Send a document that is wrong and you learn more about this door than you do by sending one that is right. There is no error banner anywhere in the interface, because a refusal is not an interruption — it is the answer, in the standard's own vocabulary, with a JSON Pointer at the field that caused it.
The specimen here is wrong in the way real documents are wrong: a date a person can read where the standard requires an instant a machine can. One field, and the verdict names it.
"eventTime": "last Tuesday",
→ POST /validate
{
"valid": false,
"schema": {
"version": "2.0.1",
"sha256": "0f46ff694eff…"
},
"errors": [
{
"path": "/epcisBody/eventList/0/eventTime",
"keyword": "format",
"message": "must match format \"date-time\""
}
]
}
That verdict was computed by this build, on that document, against the schema whose digest it names. A malformed body — broken JSON, the wrong media type, a payload over the cap — comes back as RFC 7807 application/problem+json instead, carrying the standard's own exception type: epcisException:ValidationException, epcisException:UnsupportedMediaTypeException, epcisException:CaptureLimitExceededException. Never a vendor error string, and every type URI this door emits dereferences to a page: /errors/.
Where it stops.
You should not have to discover a limit by hitting it. Everything this door refuses on size, on money and on authority is printed here. The machine faces carry the same facts in their own form: the typed refusals in /openapi.json, the price and its binding state at /pricing.
| ceiling | value | what happens at it |
|---|---|---|
| payload | 6 MiB · 6,291,456 bytes | 413 · epcisException:CaptureLimitExceededException, and the refusal states the byte figure. That number is read from the engine's own constant at build time — the page does not hold a second one. |
| auth, calculators | none | /translate, /validate and /hash take no key and no account. A calculator is not a store; there is nothing to sign in to. |
| auth, the write half | capture key | Capture, query and MCP run on api.epcis.dev and need a key. Issuance is open in the ledger (P0-18). |
| rate limit | none published | No published request ceiling stands on the three calculators today. Edge abuse governance is not a published limit and this page will not describe one it cannot show you; when a ceiling is set, it is printed here and in /openapi.json before it is enforced. |
| price | $0 per event | Machine-readable at /pricing, declared binding: false — an agent reads it as stated intent, not as terms. Binding waits on published terms (P0-15). |
| retention | nothing retained | The three calculators store nothing. What the site logs is a page of its own: what we log. |
“The rest,” row by row.
Six things happen to every event you post, and the useful way to read them is as six things the door will not do. They are stated here before you send anything, because a contract you find out about on rejection is not a contract.
- 01It will not accept an event GS1's schema rejects.
Every event is machine-checked against the official pinned 2.0.1 schema before anything else happens. The digests are on the pins page, and every verdict names the digest that judged it.
- 02It will not hand you a vendor error string.
Non-conformance comes back as RFC 7807 problem+json carrying the standard's own exception types, and every type URI dereferences to a page at /errors/.
- 03It will not keep what you sent in recordTime, capturedBy or the attestation grade.
Those three are stamped at the door and whatever you put in them is stripped. The scriber is not the part being scribed.
- 04It will not upgrade an asserted observer to an attested one.
who stays your assertion, graded — and never collapsed into capturedBy, which is the account that stands behind the capture. One is stamped, the other is graded.
- 05It will not update or delete anything.
No service identity anywhere holds an UPDATE or a DELETE grant. Corrections are declared events, never edits.
- 06It will not give an agent a different door.
Same interface, same key, over MCP — and the two tools that cannot answer honestly refuse, typed, rather than fabricate.
Identity is CBV 2.0 §8.9, checked against the pinned reference vectors: the same event captured twice is the same event, and a changed event is a different one. That is the hash in the rig above, and it is the one number here you can compute yourself.
The second Who.
EPCIS answers what, when, where, why and how, and its Who is the GLN of a legal entity. That is org grain, and it is correct: the standard says what it means and means what it says.
A company has no hands. So id.org.ai — Agent. Human. Thing. — names the performer beneath the party: the human, the agent, or the robot that took the action.
Two attributions, and the door owns a different half of each. capturedBy is the warrantor, the account whose key opened the door, and the gateway stamps it. who is the observer, and it is yours to assert and the door's to grade.
The question a reader who has got this far is already asking is whether that is an extension or a fork. It is an extension, and you settle it without us. Add the performer to the specimen from the rig above and POST it to the same validator, against the same pinned GS1 schema:
// asserted by the caller, graded
"spine:who": "did:agent:router-7",
// stamped: the key that posted
"spine:capturedBy": "acct:acme-3pl",
// the door's grade of that assertion
"spine:attestationGrade": "claimed"
→ POST /validate
{"valid": true, "errors": []}
→ POST /hash
ni:///sha-256;05d248fa2169e6bc47247bb1b51518f288c87df5d875dc32ea147d4806ed9312?ver=CBV2.0
Computed by this build, and reproducible against this origin: the event carrying a performer validates against GS1's own schema and hashes by the standard's own algorithm. Nothing about the party fields moved — org grain stays exactly where EPCIS puts it, derived at read time from grant chains, never stamped on an event.
The grade is claimed and it stays there. An asserted observer is not an attested one, identity attestation is open in the ledger, and the resolve tool refuses rather than returns an identity it cannot stand behind.
Ten scenarios, in the grain.
Authored runs of the event model — a cold chain watched across an ocean, a co-manufacturer's line, a cross-dock exception, a deputization ceremony — printed as the log a capture actually produces. It is a simulation and the frame says so: no customers, no live data, nothing measured.
- sim · authored scenarios of the event model
scenario 01 · Cold chain, watched across an ocean
- commissioning · sscc:…5501 · 2–8°C · who: human/line-lead · capturedBy: account/creamery
- sensorReport ×3 · temp in_range · who: code/logger ·id.org.ai · capturedBy: account/carrier
- receiving · sensor: in_range · who: human/dock-4 · capturedBy: account/distributor
Sealed, watched, received — and at every hop the record carries who, the attested observer, distinct from capturedBy, the warrantor account.
sim · authored scenarios · event model · not live data · no customers
who resolves via id.org.ai · Agent. Human. Thing.
The interface, and what the door does to what you send.
You POST an EPCIS 2.0 document. The gateway validates it against GS1's official JSON schema, strips the fields it is going to stamp, stamps them, computes the standard event hash, and appends. A capture that does not validate is refused as problem+json carrying the standard's own exception types.
npx epcis.dev capture \
golden-corpus/valid-standard/object-event-shipping.json
accepted captureID={captureID} events=1
eventID urn:uuid:6f9b62a2-5c1e-4c39-9e2b-1a2b3c4d5e6f
The whole run is on Run it yourself. The write half opens with a capture key; the calculators — /translate, /validate, /hash — answer on this origin without one.
Eight laws, not eight features.
The gateway underneath epcis.dev is written, and these are the parts that cannot be renegotiated later. The first two carry the Who past company grain, at the door — the part the standard leaves to the GLN. The rest are conformance you can check without asking us.
-
01
Stamped by the gateway, not the sender.
recordTime, spine:capturedBy and spine:attestationGrade are applied at the door. Whatever you sent in those fields is stripped. The scriber is not the part being scribed.
-
02
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, stamped by the gateway. Never the same thing, never merged; one is stamped, the other is graded. Party and org grain are derived at read time from grant chains, never stamped on an event — the record survives your reorg and your revocations.
-
03
Conforms to EPCIS 2.0 and CBV 2.0.
Implemented against GS1's official OpenAPI description, pinned by digest and re-checked in CI. The spine speaks What/Who/When/Where/Why/How natively; EPCIS 2.0 is its projection, and the projection always validates.
-
04
Validated on every capture.
Each event is machine-checked against the official GS1 EPCIS 2.0 JSON schema before it is accepted. A capture that does not validate is refused as RFC 7807 problem+json, carrying the standard's own exception types — not a vendor error string. The digests are on the pins page.
-
05
Identified by the standard's own hash.
The standardized 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.
-
06
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.
-
07
Minimally scoped on read.
You see your own scope. What is outside it is absent, not greyed out and not redacted — there is no shape left behind to argue about.
-
08
Ten years of vendor XML, translated.
EPCIS 1.1, 1.2 and 2.0 XML in; EPCIS 2.0 JSON-LD out; a round-trip fidelity report per job, so the translation is reviewable rather than trusted. The translation door is a page of its own.
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.
Every law above is enforced in code and verified by the spine's own test run this build. The executive lens on the same spine is visibility.cloud.
One door for people and for agents.
An agent gets the same door as a person: the same interface, the same key, over MCP — JSON-RPC re-dispatched through the same worker fetch. A door, not a second path. Two tools refuse loudly instead of faking, because an honest tool error is a feature.
| 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 | refuses honestly: returns a typed tool error, never a fabricated resolution — identity resolution lives at id.org.ai (Agent. Human. Thing.) | true | honest refusal |
| subscribe | refuses honestly: a typed tool error, never a silent downgrade to a one-shot pull | false | honest refusal |
The machine face of this site is first-class: /llms.txt for summarizers, /icp.json and /agent-classes.json as the derivation contract an arriving agent reads — built from the same frozen enumeration a runtime selector must use, so the published contract and the selection cannot drift. The door is POST /mcp on this origin, and npx epcis.dev mcp runs the same server on your bench.
Four ways people arrive at this door.
Each of these is a different evaluation, and the site does not pretend they are one audience. None of them requires a conversation; each ends at an artifact you can check.
-
ISV
The build is cheap. The conformance tax is forever.
Every engineer spent tracking schema and CBV revisions is a feature not shipped. Run your own decade of 1.1/1.2 XML through the translation engine — POST /translate on this origin, no key, no account. The round-trip fidelity report is the evaluation. For the RFP checkbox, not “we support EPCIS” but evidence an auditor can recompute: the pinned digests and the advisory suite.
-
SI
“And where does the data go?” Stop losing the account to whoever answers it.
Every reader deployment ends with that question, and today a software vendor answers it and the account follows. This is the layer you build with and put in a bid — MIT-licensed and installable, a reader-feed → validated-event path you can run against a deployment you already did. There is no channel program today; the record layer ships as a package you can install, read and vendor, and it is the product.
-
PLT
You were handed “make traceability work” and a date. Not a year to build a conformant repository.
You will disqualify on a broken command faster than on a bad price, so the message is the working command and the defensibility kit for the architecture review: pinned EPCIS 2.0.1 with sha256 provenance, OpenEPCIS-gated hashes, RFC 7807 bodies, the append-only law, and an escape hatch you already hold — the MIT-licensed tarball carries the pinned GS1 artifacts and the reference vectors, so you can vendor the exact bytes you shipped and stop depending on us. The security reviewer's questionnaire, answered before it is sent.
-
AGT
Equip your agent to capture, verify, and transact under a mandate you wrote.
The agent classes are matched, never persuaded: typed verbs, complete descriptions, deterministic answers, readOnlyHint set where true. The free zone is structural — translate, validate, hash, and capture at $0/event (intent, until published terms bind it) — and the gate lands on durable state, authority and licensed data, never on compute. Start at the agent door.
$0 per event. It stays that way.
Not a trial. Not a tier. Not a meter you watch. The allowance is abuse governance, never a bill.
Recording an event will never cost money. That is a policy, not an introductory price, and it holds because the cost structure underneath capture is object storage and a catalogue, which is cheap enough to carry permanently. It will never appear in the interface as a counter.
Revenue attaches to answers, at the human door on the executive side: traces, custody evidence, exception review, partner grants, seats, retention. The capture door stays free, because an event stream that charges per event is one you stop feeding the month the bill grows with volume.
Our intent is that recording an event never costs money. Until published terms bind it, that is a commitment we make, not a contract you hold. When prices are published, this page carries them — or says we changed our mind.
Ten years of vendor XML, translated.
EPCIS 1.1, 1.2 and 2.0 XML goes in; EPCIS 2.0 JSON-LD comes out; and a round-trip fidelity report comes with every job, so the translation is something you review rather than something you trust. CBV migration and extension unwrapping are handled by a zero-dependency, namespace-aware parser. The door is on this origin — no key, no account:
curl -X POST https://epcis.dev/translate \
-H 'content-type: application/xml' \
--data-binary @legacy-1.2.xml
200 OK
Spine-Translation-Fidelity: round-trip-clean
→ EPCIS 2.0 JSON-LD + the per-job fidelity report
What ships today, and what does not.
Shipped
- Hosted on this origin: POST /translate, POST /validate and POST /hash — the stateless engine doors, no key required
- Validation against the official GS1 EPCIS 2.0 JSON schema, on every capture
- Gateway stamping, with caller-supplied values stripped
- An MCP door: capture / query / get_event / translate as tools
Open in the launch ledger
- Issued capture keys — the key list is open and the hosted door now answers it; issuance waits on the notify path and a named sender (P0-18)
- The public repository host — the clone URL ships on this site the day the publication ruling lands (P0-24)
- The Iceberg write path (Pipelines → R2 Data Catalog, R2 SQL reads) — the hosted spine writes the same rows, on the same daily partitions, straight to R2 today; the stream that would carry them is refused by the account's Pipelines quota (code 1017, 20 of 20 streams used), so the cutover is a backfill behind a limit increase (P0-14)
- The business-transaction layer’s EDIFACT track — parse of EDIFACT answers a typed SYNTAX_UNSUPPORTED refusal while this row is open (biztx ledger, EDIFACT P6)
Four lines from each column. The ledger in full, dated, is a page of its own.
Who builds this.
epcis.dev is built to be judged by the artifact, not by a biography. The artifact is on npm: npm i epcis.dev fetches an MIT-licensed tarball with no runtime dependencies, a bundle shipped unminified so it reads, and the sha256-pinned GS1 artifacts and the reference vectors carried inside it — so every verb described on this page runs on your own machine, offline, before you believe a sentence of this. Every provenance claim here is one a reader can check that way, or against the live doors on this origin. Where a claim cannot be checked, it does not ship.
GS1 has not reviewed, certified or approved this project, and nothing here should be read as a GS1 position. The contract is straightforward: the standard is implemented as written, its exception types and its event hash included, and where we have made a choice the standard leaves open, the conformance suite records it.
epcis.dev is stewarded under The Org.AI Foundation.
Get a capture key.
A capture key opens the write half of the door — capture, query, MCP. Say what you will capture, and we email you when your key is ready.