> ## Documentation Index
> Fetch the complete documentation index at: https://docs.closient.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Capture EPCIS Event

> Record a GS1 EPCIS 2.0 supply-chain event (Object / Aggregation / Transaction / Transformation / Association) against the authenticated organization, optionally materializing an FSMA 204 Critical Tracking Event in the same transaction.

Closient implements GS1 EPCIS 2.0 (ISO/IEC 19987:2024) end-to-end:
events captured by `POST /epcis/api/2.0/capture` are persisted as
JSON-LD `EPCISDocument`s and — when the `bizStep` is FSMA 204-relevant
— materialized into a `CriticalTrackingEvent` row in the same DB
transaction. This skill walks an agent through composing a correct
event for each of the five EPCIS event types and posting it.

## When to use

* A brand owner or 3PL needs to record a real-world supply-chain
  observation (harvest, pack, ship, receive, transform).
* An EDI 856 ASN or other source needs to be translated into an EPCIS
  event for FSMA 204 traceability.
* An agent is building chain-of-custody for a GTIN + batch and needs
  to seed the ledger.

For querying existing events, see the sibling skill `query-epcis-events`.

## Auth

OAuth token (or `X-API-Key` header) where the caller holds `OWNER` or
`MANAGER` membership on the target organization. The endpoint will
return `403` for `VIEWER` and for users with no membership.

The API key format is `csb_<token>_<crc32>`. Rate limit: 300 req/min,
10,000 req/day per key (same as the rest of Closient's APIs).

## Flow

### Step 1 — Choose the event type

EPCIS 2.0 defines five event types. Pick the one that matches the
real-world observation:

| Event type            | When to use                                                                      | Required structural fields                                                   |
| --------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `ObjectEvent`         | Single-step observation on one or more EPCs (harvest, observation, retail sale). | `epcList` or `quantityList`, `action`.                                       |
| `AggregationEvent`    | Parent-child container relationship (case → pallet, lot → case).                 | `parentID`, `childEPCs` (and/or `childQuantityList`), `action`.              |
| `TransactionEvent`    | Linking EPCs to a business transaction (PO, invoice, despatch advice).           | `bizTransactionList`, `epcList` and/or `quantityList`, `action`.             |
| `TransformationEvent` | Inputs → outputs (kill step, cook step, repack).                                 | `inputEPCList`/`inputQuantityList` AND `outputEPCList`/`outputQuantityList`. |
| `AssociationEvent`    | Sensor / asset attachment to an EPC (cold-chain logger, RFID reader).            | `parentID`, `childEPCs`, `action`.                                           |

### Step 2 — Pick a `bizStep` (GS1 CBV 2.0)

The full list lives in `apps.epcis.vocabulary.BizStep`. The FSMA
204-relevant subset that materializes a CTE in the same transaction:

| `bizStep`                           | Materialized CTE type | FSMA 204 §                                                |
| ----------------------------------- | --------------------- | --------------------------------------------------------- |
| `harvesting`                        | `HARVEST`             | § 1.1330(a) — first-receiver of food on the FTL.          |
| `cooling`                           | `COOLING`             | § 1.1330(b) — first cooling step.                         |
| `packing`                           | `INITIAL_PACK`        | § 1.1330(c) — initial packing.                            |
| `accepting`                         | `LAND_RECEIVE`        | § 1.1335 — first land-based receive of a fishery product. |
| `receiving`                         | `RECEIVE`             | § 1.1345 — receiving CTE.                                 |
| `shipping`                          | `SHIP`                | § 1.1340 — shipping CTE.                                  |
| `TransformationEvent` (any bizStep) | `TRANSFORM`           | § 1.1350 — transformation CTE.                            |

Any other `bizStep` is accepted and persisted but does NOT produce a
CTE — useful for non-FSMA 204 categories (cosmetics, beauty, household).

Submit `bizStep` either as the bare CBV token (`shipping`) or the
canonical URI (`urn:epcglobal:cbv:bizstep:shipping`); the server
accepts both forms.

### Step 3 — Encode EPCs (GS1 Digital Link preferred)

Two EPC encodings are accepted on `epcList` / `outputEPCList` /
`inputEPCList`:

* **GS1 Digital Link** (preferred):
  `https://id.gs1.org/01/{gtin14}/10/{lot}` — AI(10) lot is extracted
  directly from the URI. Supports serialization via `/21/{serial}`.
* **EPCIS 1.x SGTIN URN**:
  `urn:epc:id:sgtin:{company}.{item}.{serial}` — GTIN-14 reconstructed
  via mod-10 check digit. The URN form does NOT carry AI(10), so the
  lot must come from the `ilmd` block.

For `bizLocation` and `readPoint`, accepted forms are
`https://id.gs1.org/414/{gln}` or `urn:epc:id:sgln:{...}`. The server
matches against `Location.gln.gln`.

### Step 4 — Compose the EPCIS document and POST

```http theme={null}
POST /epcis/api/2.0/capture
Authorization: Bearer <token>
Content-Type: application/json
```

```json theme={null}
{
  "@context": ["https://ref.gs1.org/standards/epcis/2.0.0/epcis-context.jsonld"],
  "type": "EPCISDocument",
  "schemaVersion": "2.0",
  "creationDate": "2026-05-18T14:32:00Z",
  "epcisBody": {
    "eventList": [
      {
        "type": "ObjectEvent",
        "eventTime": "2026-05-18T14:30:00Z",
        "eventTimeZoneOffset": "+00:00",
        "epcList": [
          "https://id.gs1.org/01/00614141123456/10/LOT-2026-042"
        ],
        "action": "OBSERVE",
        "bizStep": "shipping",
        "disposition": "in_transit",
        "readPoint": {
          "id": "https://id.gs1.org/414/0614141000005"
        },
        "bizLocation": {
          "id": "https://id.gs1.org/414/0614141000005"
        }
      }
    ]
  }
}
```

Wrap multi-event captures in the same `eventList` array — the server
runs them in a single DB transaction (atomic on partial failure).

### Step 5 — Read the response

On success (`201 Created`):

```json theme={null}
{
  "captureID": "8c30f2c4-d8e5-4e3a-9b15-6f24a1b8d5fa",
  "createdEvents": 1
}
```

On per-event validation failure (`400`): `detail` includes the
offending `eventID` (or the index in the array). On duplicate event
(`409`): the EPCIS event hash (§ 8.4) already exists — fetch the
existing one via `GET /epcis/api/2.0/events/{event_id}` rather than
retrying.

### Step 6 — Provide an `eventID` for idempotency

EPCIS 2.0 events optionally carry an `eventID` (a UUID URN). Submitting
the same `eventID` twice produces `409 Conflict` (the hash dedup
already enforces this on the wire, but an explicit `eventID` makes
retries on flaky networks safe):

```json theme={null}
{
  "eventID": "urn:uuid:8c30f2c4-d8e5-4e3a-9b15-6f24a1b8d5fa",
  "type": "ObjectEvent",
  "...": "..."
}
```

### Step 7 — Include `ilmd` for FSMA 204 KDEs

The `ilmd` (Instance/Lot Master Data) block carries the FSMA 204 Key
Data Elements. The server reads keys in either `cbvmda:` namespace or
bare form, case-insensitive. Canonical keys:

* `lot_number` — overrides any AI(10) embedded in the EPC.
* `harvest_start_date`, `harvest_end_date` — backfilled onto
  `ProductLot` if the lot is unlocked.
* `cooling_date` — written onto the `CriticalTrackingEvent` row.
* `expiration_date`, `best_before_date` — backfilled onto `ProductLot`.
* `vessel_name`, `vessel_identifier`, `species_description` — copied
  onto LAND\_RECEIVE CTEs.
* `growing_area` — informational; reserved for future structured use.

Unknown keys are silently dropped.

```json theme={null}
{
  "type": "ObjectEvent",
  "bizStep": "harvesting",
  "ilmd": {
    "cbvmda:lotNumber": "LOT-2026-042",
    "cbvmda:harvest_start_date": "2026-05-15",
    "cbvmda:growing_area": "Salinas Valley, CA"
  }
}
```

## Required inputs

* `type` — one of `ObjectEvent`, `AggregationEvent`, `TransactionEvent`,
  `TransformationEvent`, `AssociationEvent`.
* `eventTime` — RFC 3339 timestamp of the real-world observation.
* `eventTimeZoneOffset` — the offset of the recorder (e.g. `+00:00`).
* `action` — `ADD`, `OBSERVE`, or `DELETE` (NOT required on
  `TransformationEvent`).
* The structural field(s) for the event type per Step 1.

## Optional inputs

* `eventID` — strongly recommended for idempotency.
* `bizStep` — required if you want a CTE materialized (Step 2 table).
* `disposition` — see `apps.epcis.vocabulary.Disposition`. Notable
  values: `in_transit`, `in_progress`, `recalled`, `expired`,
  `damaged`, `destroyed`.
* `bizLocation` / `readPoint` — required to resolve a `Location`-keyed
  CTE.
* `bizTransactionList` — for `TransactionEvent`s.
* `sourceList` / `destinationList` — `owning_party`, `possessing_party`,
  `location` source/destination triples.
* `ilmd` — KDEs (Step 7).
* `sensorElementList` — for cold-chain / temperature data on
  `AssociationEvent`.

## Output

* `201 Created` with `{ "captureID": "<uuid>", "createdEvents": <n> }`.
* `400` — per-event validation failure (offending `eventID`/index in `detail`).
* `403` — caller is not `OWNER` / `MANAGER` of the target organization,
  or the org's billing tier doesn't include EPCIS capture.
* `409` — duplicate event hash (idempotent retry succeeded; no new row).
* `422` — malformed JSON-LD envelope.
* `429` — rate limited.

## Guidance for agents

* **Always send the `@context`.** Capture parses the body as JSON-LD;
  the EPCIS 2.0 context is what tells the server `cbvmda:` keys are
  KDEs rather than arbitrary metadata.
* **Prefer GS1 Digital Link EPCs.** The URN form drops AI(10), forcing
  the lot to come from `ilmd`. Digital Link keeps the lot inline and
  is the format the rest of Closient's surfaces (resolver, QR, hosted
  pages) speak natively.
* **One event per real-world action.** Don't batch unrelated harvest
  events into a single `eventList` — each event is hashed and dedup'd
  independently, but a transaction-wide validation failure rolls all
  of them back. Group only when they represent one transactional
  business operation (e.g., shipping 12 pallets in one despatch).
* **Read the `eventTypes` capability first.** A
  `GET /epcis/api/2.0/eventTypes` (no auth required) returns the set
  of event types supported. If the environment doesn't expose EPCIS
  at all, you'll get a 404 — fail fast rather than thrashing on
  capture.
* **Materializer is silent on non-FSMA bizSteps.** Capturing an event
  with `bizStep=retail_selling` succeeds but produces no CTE. That's
  expected — don't treat a missing CTE as a failure unless the bizStep
  is in the FSMA table.
* **Lock-aware ILMD backfill.** Once a `ProductLot` has been used in a
  FSMA-relevant event, its `LOCKED_FIELDS` (lot number, harvest dates,
  expiration) become immutable. Subsequent captures still produce
  CTEs, but the ILMD KDEs are dropped on the floor for the lot
  itself. The captured event preserves them.
* **Resolve trading partners and locations up front.** For
  `RECEIVE` / `SHIP` CTEs the server needs to match a
  `OWNING_PARTY` source/destination GLN against an existing
  `TradingPartner.gln.gln` and a `bizLocation` GLN against
  `Location.gln.gln`. If those rows don't exist, the EPCIS event
  still captures but the CTE materialization logs an INFO and skips.

## Known gaps (planned)

* **No streaming capture.** All events are flushed synchronously on the
  request. For bulk historical backfills (>1k events), use the
  management CSV importer at `POST /epcis/api/v1/cte-imports/csv`
  rather than this endpoint.
* **`AssociationEvent` sensor handling is opaque.** Closient persists
  the `sensorElementList` block verbatim but does not yet decompose
  it into a structured sensor-reading table; cold-chain alerts are
  filed but not actionable from query results.
* **No partial-success response.** If one event in a batched
  `eventList` fails validation, the whole transaction rolls back.
  Per-event success/failure responses would require splitting the
  capture into per-event sub-transactions; deferred until a customer
  actually hits the all-or-nothing pain.
* **No webhook on materialization.** Materialized CTEs are not
  pushed back out via webhook today. Compliance dashboards refresh on
  demand.

## Related skills

* `query-epcis-events` — read-side counterpart for chain-of-custody
  reconstruction.
* `check-fsma-204-readiness` — validate that captured events have the
  KDEs needed for FSMA 204 export.
* `manage-recall` — uses captured `disposition=recalled` events as
  ground truth for "what is currently under recall".
* `check-recalls` — consumer side; reads `disposition=recalled` to
  surface alerts.
