# Validate & hash

Two calculators, one discipline. `/validate` answers a verdict against the
**pinned official GS1 EPCIS 2.0.1 JSON schema** — the response names the
artifact and its sha256, so two parties disagreeing about validity are
disagreeing about bytes, not versions. `/hash` computes the CBV 2.0 §8.9
standardized event hash — identity is computed from the event's content,
never assigned by a database.

## POST /validate

| contract | value |
|---|---|
| Method, path | `POST https://epcis.dev/validate` |
| Auth | none — no key, no account |
| Price class | free |
| Request | `application/json` or `application/ld+json`: an EPCISDocument or a bare EPCIS event object |
| 200 response | `{ valid, schema: { artifact, version, sha256 }, errors: [{ path, keyword, message }] }` |
| Response headers | `GS1-EPCIS-Version`, `GS1-CBV-Version`, `GS1-Extensions` |
| CLI twin | `npx epcis.dev validate <file.json> [--project]` |
| Library twin | `import { validateEpcis, getEpcisValidator, getQueryValidator, project } from "epcis.dev"` |

A negative verdict is a **200 with `valid: false`** and per-path errors —
the calculator worked; the document failed. The typed refusals below are
reserved for requests the door cannot judge at all.

| status | type | meaning |
|---|---|---|
| 400 | `epcisException:ValidationException` | malformed JSON |
| 413 | `epcisException:CaptureLimitExceededException` | payload exceeds the file-size limit |
| 415 | `epcisException:UnsupportedMediaTypeException` | this door accepts JSON; POST XML to `/translate` |

## POST /hash

| contract | value |
|---|---|
| Method, path | `POST https://epcis.dev/hash` |
| Auth | none — no key, no account |
| Price class | free |
| Request | `application/json` or `application/ld+json`: a single event, an array of events, or an EPCISDocument with `epcisBody.eventList` |
| 200 response | `{ algorithm, count, hashes: ["ni:///sha-256;…?ver=CBV2.0"] }` |
| Response headers | `GS1-EPCIS-Version`, `GS1-CBV-Version`, `GS1-Extensions` |
| CLI twin | `npx epcis.dev hash <file.json>` |
| Library twin | `import { eventHash, canonicalJson, preHashString } from "epcis.dev"` |
| Typed refusals | 400 `ValidationException` (malformed JSON, or no EPCIS events found) · 413 `CaptureLimitExceededException` · 415 `UnsupportedMediaTypeException` |

The §8.9 algorithm itself excludes `eventID`, `recordTime` and
`errorDeclaration`, so a gateway filling those in does not change the hash.
Send events as they are — do not pre-strip anything.

## Proof — both doors, one fixture

Executed against production with the shipped shipping fixture:

```
$ CORPUS=node_modules/epcis.dev/golden-corpus
$ curl -sS -X POST https://epcis.dev/validate \
    -H 'content-type: application/json' \
    --data-binary @$CORPUS/valid-standard/object-event-shipping.json
{
  "valid": true,
  "schema": {
    "artefact": "epcis-json-schema.json",
    "version": "2.0.1",
    "sha256": "0f46ff694efffd8d8ce840a33dfde84228add11b516b8b258f3200740ae210af"
  },
  "errors": []
}

$ curl -sS -X POST https://epcis.dev/hash \
    -H 'content-type: application/json' \
    --data-binary @$CORPUS/valid-standard/object-event-shipping.json
{
  "algorithm": "CBV 2.0 §8.9 event hash",
  "count": 1,
  "hashes": [
    "ni:///sha-256;e5624abcce4b2fc226743e8891f69c12a07ad508953749d9b9ede6771424f91e?ver=CBV2.0"
  ]
}
```

### The canonicalization trace

The hash is sha-256 over a deterministic pre-hash string — §8.9's
canonical ordering of the event's fields with CBV values expanded to their
web-URI forms. The library exposes the intermediate steps so you can audit
any hash to the byte. The opening of the pre-hash string for the same
event, from `preHashString(event)`:

```
eventType=ObjectEventeventTime=2026-07-19T10:30:00.000Z
eventTimeZoneOffset=+00:00epcListepc=https://id.gs1.org/01/1061414107346
4/21/2018action=OBSERVEbizStep=https://ref.gs1.org/cbv/BizStep-shipping…
```

(One unbroken string on the wire; wrapped here for the page.) Same event,
same bytes, same `ni` URI — on this door, in `npx epcis.dev hash`, and in
any other §8.9 implementation, including the OpenEPCIS vectors.

---

**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/.
