The typed exceptions
A refusal from any door on this origin is an RFC 7807 application/problem+json document whose type is one of the EPCIS standard's own epcisException:* identifiers. The type and status are the contract; title and detail are worded for humans and may be rephrased between releases. Match on type, never on prose.
The taxonomy the shipped surfaces emit
| type | status | meaning | remediation |
|---|---|---|---|
| epcisException:ValidationException | 400 | malformed JSON, a document the pinned schema rejects, or a failed translation | fix the payload; the per-path errors array names every violation |
| epcisException:QueryParameterException | 400 | a SimpleEventQuery the pinned query-schema rejects | fix the named parameter; CBV values are bare words (shipping, not a URN) |
| epcisException:SecurityException | 401 | a keyed door reached without a key | send Authorization: Bearer <capture-scoped key>; ask for one at /get-a-key/ |
| epcisException:NoSuchResourceException | 404 | an eventID that is not in the caller's scope — absent, never redacted | nothing to retry; an out-of-scope event and a nonexistent one answer identically |
| epcisException:ResourceAlreadyExistsException | 400, nested per event | a capture that collides with a stored event of the same eventID but different content — the job refuses whole as a 400 ValidationException envelope ("no partial acceptance") and the collision arrives per event inside its errors[] | a byte-identical replay is a benign ack; different bytes under the same identity are refused whole — submit a correction as a new event carrying an errorDeclaration |
| epcisException:CaptureLimitExceededException | 413 | payload exceeds the file-size limit | split the document |
| epcisException:UnsupportedMediaTypeException | 415 | the right payload at the wrong door | the detail names the right one: XML goes to /translate, JSON to /validate and /hash |
| epcisException:ImplementationException | 501 | a capability this door does not serve: the resolve and subscribe MCP tools | resolve: identity resolution is homed at id.org.ai; subscribe: use the query tool — this deployment serves pull queries only |
The CLI mirrors the same discipline on stderr with four stable code strings — USAGE, PARSE, NOT_FOUND, INTERNAL — documented on the CLI reference.
Real refusals, cut from real runs
415 — the wrong door names the right one. JSON posted to the XML door:
$ curl -s -X POST https://epcis.dev/translate \
-H 'Content-Type: application/json' -d '{"not":"xml"}'
{
"type": "epcisException:UnsupportedMediaTypeException",
"title": "Unsupported Media Type",
"status": 415,
"detail": "the translation door accepts application/xml or
text/xml (got \"application/json\"); POST JSON to /validate
or /hash"
}
400 — malformed bytes are a ValidationException, not a 500.
$ curl -s -X POST https://epcis.dev/validate \
-H 'Content-Type: application/json' -d '{"broken'
{
"type": "epcisException:ValidationException",
"title": "malformed JSON",
"status": 400,
"detail": "SyntaxError: Unterminated string in JSON at
position 8 (line 1 column 9)"
}
401 — the keyed ceremony, stated by the door itself. On the apex MCP door a keyed tool refuses as a tool-level error (isError: true) carrying the same problem document — the HTTP envelope stays 200 because in MCP a refusal is a result, not a transport failure:
$ curl -s -X POST https://epcis.dev/mcp \
-H 'Content-Type: application/json' -d '{
"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"capture","arguments":{"document":{}}}}'
{
"jsonrpc": "2.0", "id": 1,
"result": {
"structuredContent": {
"type": "epcisException:SecurityException",
"title": "Unauthorised request",
"status": 401,
"detail": "/capture is a keyed door: send Authorization:
Bearer <capture-scoped key>. Ask for one at /get-a-key/;
we email you when it is ready."
},
"isError": true
}
}
501 — the capability this door does not serve, refused typed. The apex resolve tool answers the same problem-document grammar — cut live from POST https://epcis.dev/mcp on 2026-07-31:
$ curl -sS -X POST https://epcis.dev/mcp \
-H 'Content-Type: application/json' -d '{
"jsonrpc":"2.0","id":4,"method":"tools/call",
"params":{"name":"resolve",
"arguments":{"reference":"urn:epc:id:sgtin:0614141.107346.2021"}}}'
{
"jsonrpc": "2.0", "id": 4,
"result": {
"structuredContent": {
"type": "epcisException:ImplementationException",
"title": "resolve is not served on this door",
"status": 501,
"detail": "identity resolution is homed at id.org.ai — one
resolver, one home; this door answers a typed refusal,
never a fabricated resolution."
},
"isError": true
}
}
400 with the collision nested — the capture rejection envelope. The capture door never answers a partial acceptance: a job that collides refuses whole as a ValidationException problem document, and ResourceAlreadyExistsException names each colliding event inside errors[]. Executed 2026-07-31 against epcis.dev@0.1.0 — the same document captured twice with different bytes under one eventID, over the stdio MCP door:
{
"structuredContent": {
"type": "epcisException:ValidationException",
"title": "capture job rejected — no partial acceptance",
"status": 400,
"detail": "every EPCIS event in the payload is rejected;
see errors[]",
"captureID": "074b4c8a-4ebe-4561-8ca5-3859611f7f1c",
"errors": [
{
"type": "epcisException:ResourceAlreadyExistsException",
"title": "eventID urn:uuid:2f24f00d-0000-4000-8000-
000000000001 was already captured with different
content; events are append-only — submit a correction
as a new event carrying an errorDeclaration",
"status": 400,
"instance": "/epcisBody/eventList/0/eventID"
}
]
},
"isError": true
}
Match the envelope on type, then walk errors[] and match each entry on its own type — the collision is per event, the refusal is whole.
(The detail and long title strings above are wrapped for width; type, status and every other byte are verbatim from the real responses. Each full result also carries the same document as a content text block.)
A refusal is one of three honest answer shapes — the other two, the result and the truthful empty, are on the refusal grammar.