EPCIS 2.0 Capture Interface for Edge RFID: REST Binding, Capture Jobs, Limits and Event IDs
The EPCIS capture interface is where edge RFID software stops being a reader app and becomes part of a traceability system. This guide sets out the EPCIS 2.0 contract between an edge buffer and a repository, clause by clause: the all-or-nothing capture rule, the asynchronous POST /capture that answers 202 with a capture job, the GS1-* headers that advertise limits, rollback as the default, and the choice of who generates the eventID. It is written for engineers building that link, and every rule is tied to the section of the standard it comes from.
Scope and version pin
This post covers one hop: from software at the read point (on the reader itself or on an edge box beside it) to an EPCIS repository. The contract is the same whether the read point is a fixed reader on a dock door or an integrated reader at a yard gate.
Versions are taken from the documents’ cover pages: the EPCIS Standard, Release 2.0, Ratified, Jun 2022, and the CBV Standard, Release 2.0, Ratified, Jun 2022. The transport clauses are EPCIS section 8.1 (the capture operation), section 11 (message-queue and HTTP bindings) and section 12 (REST). Section 12.1 names ref.gs1.org/standards/epcis/2.0.0/openapi.json as the complete REST specification. It is an OpenAPI 3.0.3 file, “EPCIS 2.0 REST Bindings”, version 2.0.0, so generate client stubs from it. EPCIS 2.0 is also published as ISO/IEC 19987:2024, dated 22 March 2024.
Which fields a reader can fill is a separate topic. Here the edge already holds a valid event and has to get it into the repository exactly once, in order, under an ID both sides agree on.
Watch item: in September 2026, GS1’s EPCIS GitHub repository began hosting public-review draft files for version 2.1. A draft is not a ratified standard, so keep sending 2.0.0 in your version headers until 2.1 is ratified.
One operation, all or nothing
EPCIS section 8.1.2 defines capture as a single method, capture, that takes a list of events. Three rules in that clause shape every edge design:
- Atomic. The repository captures every event in the operation or none of them; partial success is ruled out. The listed failure causes include malformed input, an unauthorised client, implementation-specific limits on events per call or on capture frequency, content rules that check against earlier events, and temporary unavailability of a server or the network.
recordTimeis the repository’s. The edge may omit it. Section 7.4.1 says the field is ignored at capture and becomes the time of capture.- A supplied
eventIDis stored unchanged. Only when the field is empty may the capture interface add one.
The bindings then decide how the result reaches the client. The message-queue binding (section 11.1) leaves the broker, addressing and QoS to the implementer. Each message is one capture operation, and an acknowledgement may signal the outcome. The HTTP binding (section 11.2, XML payloads) returns 200 for a completed capture and any 3xx, 4xx or 5xx for a capture that did not happen. The REST binding (section 12) works differently. A client written for 11.2 that counts only 200 as success will misread a REST repository’s 202.
Section 8.1.1 expects trading partners who exchange data across organisational boundaries to use authenticated bindings. In the REST binding, section 12.4 requires the client to authenticate on every call; the server returns 401 when authentication fails and 403 when an authenticated client lacks permission.
The REST sequence: OPTIONS, POST /capture, 202, poll
Section 12.6 defines two capture endpoints. POST /capture takes an EPCISDocument with one or more events and runs asynchronously. POST /events takes one bare event and is synchronous: the OpenAPI returns 201 Created with the event’s URL, or 409 if that identifier already exists. Batches from an edge buffer go to /capture, in five steps:
- Discover.
OPTIONS /capture. Section 12.3 requires every endpoint to return its allowed verbs and its supported headers with their defaults. In the OpenAPI this is a204carrying both capture limits andGS1-Capture-Error-Behaviour. - Submit.
POST /capturewith the document, the version headers and the error behaviour you want. - Accept. The server returns
202 Accepted, with the capture job’s URL inLocation; its last path segment is thecaptureID. Section 12.6.2 says a 202 does not guarantee that the events were stored. - Poll.
GET /capture/{captureID}untilrunningisfalse. - Settle. Read
success. Only then mark the batch done in the buffer.
On the wire, for one shipping read at a Delhi dock door (GS1’s example company prefix, our identifiers):
POST /capture HTTP/1.1
Host: epcis.example.com
Authorization: Bearer <token>
Content-Type: application/ld+json
GS1-EPCIS-Version: 2.0.0
GS1-CBV-Version: 2.0.0
GS1-Capture-Error-Behaviour: rollback
{
"@context": ["https://ref.gs1.org/standards/epcis/2.0.0/epcis-context.jsonld"],
"type": "EPCISDocument",
"schemaVersion": "2.0",
"creationDate": "2026-10-22T09:15:02.000+05:30",
"epcisBody": {
"eventList": [
{
"type": "ObjectEvent",
"eventID": "urn:uuid:3f2b6c1e-8d4a-4c3e-9b7f-2a1d5e6f7a80",
"eventTime": "2026-10-22T09:14:58.412+05:30",
"eventTimeZoneOffset": "+05:30",
"epcList": ["urn:epc:id:sgtin:0614141.107346.2017"],
"action": "OBSERVE",
"bizStep": "shipping",
"disposition": "in_transit",
"readPoint": {"id": "urn:epc:id:sgln:0614141.07346.1234"}
}
]
}
}
HTTP/1.1 202 Accepted
Location: /capture/7f3a91c2
GET /capture/7f3a91c2 HTTP/1.1
HTTP/1.1 200 OK
{
"captureID": "7f3a91c2",
"running": false,
"success": true,
"captureErrorBehaviour": "rollback",
"createdAt": "2026-10-22T03:45:02Z",
"finishedAt": "2026-10-22T03:45:03Z",
"errors": []
}
The document validates against the published 2.0.0 JSON Schema. Section 12.6.2 lists the job’s minimum fields: running, success, errors or errorFile (a URL to an error log), captureErrorBehaviour, createdAt and finishedAt. The two flags mean different things depending on the job’s error behaviour:
running | success | Under rollback | Under proceed | Edge action |
|---|---|---|---|---|
| true | true | Capturing; no errors so far | Capturing; no errors so far | Keep polling |
| true | false | An error occurred; rollback in progress | An error occurred; capture continues | Keep polling; prepare the error path |
| false | true | All events captured | All events captured, no errors | Mark the batch done |
| false | false | All events rejected | Some events captured, with errors | Rollback: split and resend. Proceed: reconcile by eventID |
Source: EPCIS 2.0 section 12.6.2, Tables 12-3 and 12-4. The edge-action column is our implementation advice.
GS1 headers, status codes and content negotiation
Section 12.3 (Table 12-2) defines the custom headers. These are the ones an edge capture client deals with:
| Header | Sent by | Meaning for the edge | Value shown by GS1 |
|---|---|---|---|
GS1-EPCIS-Version | Both | EPCIS version. If the client omits it, the server uses its own. | 2.0.0 |
GS1-EPCIS-Min / -Max | Both | Range of supported versions | 1.0.0 / 2.0.0 (OpenAPI) |
GS1-CBV-Version | Both | CBV version | 2.0.0 |
GS1-EPC-Format | Both | Whether EPCs are given as Digital Link URIs or as URNs; the OpenAPI applies it to GET endpoints | Standard: Always_DL. OpenAPI: Always_GS1_Digital_Link (default), Always_EPC_URN, No_Preference, Never_Translates |
GS1-EPCIS-Capture-Limit | Server | Maximum events per call | 500 (example) |
GS1-EPCIS-Capture-File-Size-Limit | Server | Maximum document size in octets | 1024 (example) |
GS1-Capture-Error-Behaviour | Both | rollback (all or nothing; the required default) or proceed (capture as many as possible). In the OpenAPI, a server can also send all to show it supports both. | rollback |
The 500 and 1024 only illustrate the header format; the real numbers are whatever your repository returns. The standard’s Always_DL example does not match the OpenAPI enum, so send what the server’s OPTIONS response advertises. Section 12.3 requires the server to support both capture limits on /capture.
Every endpoint must support JSON and JSON-LD; XML is optional. An unsupported Accept type gets 406, and an unsupported request body gets 415. Section 12.10 requires errors in RFC 7807 Problem Details format and maps exceptions to status codes:
| Status | Endpoint | Meaning for the edge | Source |
|---|---|---|---|
| 202 | POST /capture | Job created; storage not yet confirmed | 12.6.1, 12.6.2 |
| 201 | POST /events | Single event captured | OpenAPI |
| 400 | Both | ValidationException; on /capture all events are rejected | 12.10, OpenAPI |
| 401 / 403 | All | Not authenticated / not permitted | 12.4 |
| 406 / 415 | All | Unsupported response / request media type | 12.3, 12.10 |
| 409 | POST /events | Identifier already exists | 12.10, OpenAPI |
| 413 | Both | CaptureLimitExceededException | 12.6.1, 12.10 |
| 500 / 501 | All | ImplementationException | 12.10 |
On a 413, the server may report the most restrictive limit, and the OpenAPI’s 413 response carries both limit headers. Resize the batch and resend. For any other /capture failure, section 12.6.1 requires a 4xx status.
Designing the edge buffer
Batch to the advertised limit, whichever binds first
Read both limits from OPTIONS /capture at start-up and after any 413. Cut each batch at whichever limit it reaches first; the OpenAPI notes that splitting large payloads avoids timeouts. Here is the arithmetic with GS1’s illustrative values (500 events, 1,024 octets). Minified, the sample document above is 529 bytes: a 200-byte envelope plus a 329-byte event. Two events come to 859 bytes, and a third takes the document to 1,189, over the limit. So the batch stops at two events, not 500. A carton read is heavier still: the same ObjectEvent with 24 SGTIN URNs is 1,226 bytes before the envelope, and each additional URN adds 39 bytes.
When a single ObjectEvent is over the size limit, split its EPC list across several events with the same time, place and business step, each with its own eventID. Section 7.4.2 says EPCs that share an ObjectEvent have no relationship beyond having been through the same event, so the split loses nothing. Check the semantics before splitting other event types.
Keep rollback, and plan the split
Under rollback, one bad event rejects the whole batch and nothing is left half-captured. The edge then recovers: read errors or errorFile, bisect the batch, resend the halves, and send the event that is finally isolated to a quarantine queue for someone to fix. Use proceed only when the business has agreed that partial capture is acceptable. Even then, success: false says that something failed, not which events failed, so reconcile with GET /events/{eventID}.
Make retries recognisable
Assign the eventID when the event enters the buffer, and reuse it on every retry. If a POST times out with no 202, a job may still exist. Before resending, list jobs with GET /capture and look the events up with GET /events/{eventID}; a 404 means the event has not landed. Whether /capture rejects a duplicate eventID is up to the implementation (section 8.1.2 permits such checks), while POST /events answers a duplicate with 409. Once you hold a 202, poll the job with back-off and do not post the batch again.
Throughput depends on the repository’s limits and how fast its jobs finish, so measure it in staging. On the readers we ship, ReaderSense Edge already provides this buffer. Every read goes to SQLite on the board’s eMMC, the queue survives network loss and reboots, and it drains in order through batched HTTP or WebSocket uploads. Wrapping those batches as EPCISDocuments and settling them against capture jobs is the adapter this post describes.
Who generates the eventID: UUID or Event Hash ID
CBV 2.0 section 8.9 makes eventID optional. When it is present it must be globally unique, and a CBV-Compliant document must use either a UUID URI (section 8.9.1) or an EPCIS Event Hash ID (section 8.9.2). Section 12.6.1 lets the repository fill an empty eventID in one of those two forms. That gives four options:
| Option | Edge implements | Gain | Watch |
|---|---|---|---|
| UUID v4 at the edge | Random urn:uuid:… | Known before sending; retries and lookups work; no canonicalisation | Create it once and store it; a fresh UUID on retry creates a new event |
| UUID v1 at the edge | Time-based UUID | Same as v4 | RFC 9562 says the v1 node field holds an IEEE 802 MAC address or a random value, so it can expose the reader’s hardware address |
| Event Hash ID at the edge | The CBV 8.9.2 canonicaliser, then a hash | Any party can recompute it; a re-sent copy of the event gets the same ID | Most code to get right; test it against GS1’s vectors |
| Left empty | Nothing | Simplest edge | The edge never learns the ID, so it cannot look the event up after a lost response |
Our default is a UUIDv4 created when the read enters the buffer. Switch to an edge-computed hash when the business needs content-based duplicate detection or notarisation, which are the uses the CBV gives for it. CBV 2.0 cites RFC 4122 and allows only UUID versions 1 and 4. RFC 9562 (May 2024) replaced RFC 4122 and added the time-ordered version 7, but a v7 UUID is not one of the two forms a CBV-Compliant document may use.
What the Event Hash ID requires
- A canonical pre-hash string, built under 23 rules with a fixed property order that begins
eventType,eventTime,eventTimeZoneOffset. Fields are written askey=valuewith no separators, lists are sorted, and timestamps are in UTC, ending inZ, at millisecond precision. - EPC URNs are converted to canonical GS1 Digital Link URIs on
https://id.gs1.org. CBV values, whether URNs or JSON bare strings such asin_transit, are expanded to theirhttps://ref.gs1.org/cbv/URIs. - The digest goes into an RFC 6920
niURI. GS1’s examples use the formni:///sha-256;<digest>?ver=CBV2.0.
The CBV sets three limits on the hash. A redacted copy of an event never reproduces the original’s hash. The hash does not prove authorship, since anyone who alters an event can recompute it, so the CBV points to digital signatures for tamper protection. And an error declaration carries the original event’s eventID instead of a new hash. To test an implementation, use GS1’s event_with_identical_hash_id_1 to _7. They express one event several ways (shuffled EPC and quantity lists, a different namespace prefix, a JSON rendering), and every version must produce the same ID.
Clocks, time zones and event order
Section 7.4.1 defines eventTime as the time the capturing application says the event happened, which at an RFID read point means the reader’s clock. The 2.0.0 JSON Schema requires eventTime and eventTimeZoneOffset on every event. The offset is written as ±hh:mm, with hours up to 14 and only 14:00 allowed at 14. The standard’s own example is +05:30. In our test, +0530, the form strftime’s %z produces, failed validation, as did +14:30.
The offset records where the event happened; it does not fix the moment (section 7.4.1.1). A Delhi read at 09:14:58.412 local time can be sent as 09:14:58.412+05:30 or as 03:44:58.412Z, and in both cases the offset field stays +05:30.
EPCIS mentions clock synchronisation only for sensor timestamps: section 7.3.7.1.1 says capture applications should use an established protocol such as IEEE 1588-2008. The same applies to eventTime. If a reader’s clock drifts, the repository’s order no longer matches what happened on the floor, which matters most across the many sites of a multi-hub supply chain. Keep each reader on a disciplined time source. Record with each buffered event whether the clock was synchronised when it was stamped, and hold events stamped before sync is confirmed after a cold boot.
The EPCIS and CBV Implementation Guideline, Release 2.0, Ratified, Mar 2023, section 4.6.2, covers events that happen together. Its example is a machine that commissions twelve items and a case and packs them in one step. The guideline advises giving the packing event a later eventTime than the commissioning events, even if only one millisecond later, so a sort by time gives a logical sequence. Encode-and-pack RFID stations need exactly this.
Validate before go-live
Section 10.3 makes two validation files normative: epcis-json-schema.json (JSON Schema draft-07) for JSON and epcis-shacl.ttl for JSON-LD, both under ref.gs1.org/standards/epcis/2.0.0/. Both take an open-shape approach: terms from other namespaces are ignored. That is what allows extension fields, and it is also why a misspelled extension key passes without complaint, so test your extensions yourself.
A second trap comes from the tooling. The schema accepts any property name that is a URI, and draft-07 makes enforcement of format optional. With Python’s jsonschema, our sample with an extra lower-case bizstep key passed when format checking was off and failed when it was on. An eventTime with no time zone behaved the same way. Turn format assertion on in CI.
GS1 publishes examples at ref.gs1.org/docs/epcis/examples/ (section 10.4). These include capture-job documents (running, success, with errors, with an error file), an error declaration with its corrective event, and the identical-hash set. Run them through both the edge and the staging repository.
Go-live checklist
OPTIONS /captureis read at start-up and after every 413, and both limits are stored.- Batches are cut at the tighter of the two limits, and oversized
ObjectEvents are split. GS1-EPCIS-VersionandGS1-CBV-Versionare pinned to2.0.0.- Rollback has been tested: one invalid event rejects its batch, and the bisect sends that event to quarantine.
- A batch is marked done only when
runningisfalseandsuccessistrue. eventIDs are stored with buffered events, and the lost-response path has been tested.- Reader clocks are synchronised, offsets are written as
+hh:mm, and packing events are stamped after their commissioning events. - CI runs schema validation with format assertion on, and SHACL validation for JSON-LD.
The same list applies whether events come from dock doors or from shelf readers in an RFID inventory system.
Frequently asked questions
What is the EPCIS capture interface?
It is the interface through which a capturing application, such as reader middleware, an edge gateway or an ERP, delivers events to an EPCIS repository. EPCIS 2.0 defines it in section 8.1.2 as a single capture operation that stores every event in a list or none of them. Section 11 binds that operation to message queues and HTTP, and section 12 to REST endpoints: POST /capture for documents and POST /events for single events.
What does HTTP 202 mean when capturing EPCIS events?
On the REST /capture endpoint, 202 Accepted means the document was syntactically acceptable and the client was allowed to call the endpoint. A capture job now exists, and its URL is in the Location header. It does not mean the events are stored. Poll GET /capture/{captureID} until running is false, then read success. The older XML HTTP binding in section 11.2 uses 200 for a completed capture instead.
What is GS1-Capture-Error-Behaviour?
It is a header defined in EPCIS 2.0 section 12.3 that sets what /capture does when some events fail. With rollback, which the standard requires as the default, either every event is captured or all are rejected. With proceed, the server captures as many valid events as it can. The OpenAPI file adds a third value, all, which only a server uses, to announce that it supports both.
Should an RFID reader generate the EPCIS eventID?
In most edge designs, yes. An ID created when the event enters the edge buffer, and stored with it, lets the edge check whether the event landed after a lost response (GET /events/{eventID}) and makes retries recognisable. CBV 2.0 allows a UUID version 1 or 4, or an EPCIS Event Hash ID. A random UUIDv4 is the simpler choice. The hash suits teams that need content-derived duplicate detection or notarisation and can implement the full canonicalisation.
How many events can I send in one EPCIS capture request?
As many as the repository advertises. For /capture, EPCIS 2.0 requires the server to support GS1-EPCIS-Capture-Limit (events per call) and GS1-EPCIS-Capture-File-Size-Limit (document size in octets); read both with OPTIONS /capture and cut batches at whichever binds first. The 500 shown in the standard is an example value, not a default. A request over either limit may get 413 Payload Too Large.
Does EPCIS 2.0 support message queues as well as REST?
Yes. Section 11.1 defines a message-queue binding. The server provides one or more queue or topic endpoints, each message carries an EPCIS document and counts as one capture operation, and positive or negative acknowledgements may signal the outcome. The choice of broker, addressing and quality of service is left to the implementer, and the all-or-nothing rule still applies to every message.
Sources
- GS1 EPCIS Standard, Release 2.0, Ratified, Jun 2022 (sections 7.4.1, 7.3.7.1.1, 8.1, 10.3, 10.4, 11, 12)
- GS1 EPCIS 2.0 REST Bindings, OpenAPI 3.0.3, version 2.0.0
- Archived copy of the EPCIS 2.0.0 OpenAPI file (Wayback Machine, 12 Feb 2026)
- GS1 EPCIS 2.0.0 JSON Schema (epcis-json-schema.json)
- GS1 Core Business Vocabulary (CBV) Standard, Release 2.0, Ratified, Jun 2022 (section 8.9 Event identifiers)
- GS1 EPCIS and CBV Implementation Guideline, Release 2.0, Ratified, Mar 2023 (section 4.6.2), archived 18 Sep 2026
- GS1 example data for EPCIS / CBV 2.0
- GS1 EPCIS GitHub: Event Hash ID test events (event_with_identical_hash_id_1 to _7)
- GS1 EPCIS GitHub: public review draft v2.1 of epcis-context.jsonld (commit, Sep 2026)
- RFC 9562, Universally Unique IDentifiers (UUIDs), May 2024
- JSON Schema Validation, draft-07 (format keyword)
- ISO/IEC 19987:2024, EPC Information Services (EPCIS), IEC Webstore
- Identium ReaderSense Edge + MDM