{
  "info": {
    "name": "Origonex Public API (v1)",
    "description": "Generated from GET /v1/openapi.json. Set the collection variables `baseUrl` (e.g. https://api.origonex.com) and `apiKey` (an orx_… key from Settings → API keys).",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.origonex.com"
    },
    {
      "key": "apiKey",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "Tenant",
      "item": [
        {
          "name": "GET /tenant — Read the caller’s own tenant",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/tenant",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tenant"
              ],
              "variable": [],
              "query": []
            },
            "description": "Returns the tenant the authenticated session is scoped to.\n\nScope: master.read"
          }
        },
        {
          "name": "PATCH /tenant — Update the caller’s own tenant",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/tenant",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tenant"
              ],
              "variable": [],
              "query": []
            },
            "description": "Mutates the public mutable subset of Tenant (displayName / primaryCountry). Plan changes go through sales/admin, never this endpoint.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Party",
      "item": [
        {
          "name": "GET /parties — List parties",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/parties",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "parties"
              ],
              "variable": [],
              "query": [
                {
                  "key": "kind",
                  "value": "",
                  "disabled": true,
                  "description": "Party (legal entity) role in the supply chain"
                },
                {
                  "key": "country",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "kycStatus",
                  "value": "",
                  "disabled": true,
                  "description": "KYC / KYB status: per-tenant onboarding flow"
                },
                {
                  "key": "sanctionsMatch",
                  "value": "",
                  "disabled": true,
                  "description": "Sanctions screening outcome, produced by the screening package (OFAC / EU / UK / UN / etc.). `unscreenable` is not a severity band: it means no comparison was possible, because the matcher scores Latin transliterations and the party's name is in a script it cannot read. Treat it as non-clear and requiring human review, never as an outcome."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only records changed at or after this instant (read from a 5-minute overlap before it; dedupe by id). Combine with `includeDeleted=true` to learn of deletions."
                },
                {
                  "key": "includeDeleted",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Scope: master.read"
          }
        },
        {
          "name": "POST /parties — Create a party",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/parties",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "parties"
              ],
              "variable": [],
              "query": []
            },
            "description": "Creates a legal entity in the supply chain. kycStatus starts not_started and is decided by a person in the company (its Supervisor, with a second factor) after reviewing the documents; a sanctions screen never approves or rejects it. sanctionsMatch starts clear and comes from the sanctions screening. Neither is written by this endpoint.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"supplier\",\n  \"name\": \"\",\n  \"tradingName\": \"\",\n  \"country\": \"\",\n  \"legalRegistrationNumber\": \"\",\n  \"eori\": \"\",\n  \"contactEmail\": \"\",\n  \"contactPhone\": \"\",\n  \"metadata\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /parties/{id} — Read a party by id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/parties/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "parties",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: master.read"
          }
        },
        {
          "name": "PATCH /parties/{id} — Update a party",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/parties/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "parties",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "DELETE /parties/{id} — Soft-delete a party",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/parties/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "parties",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Deactivates the party (active=false). Hard deletion never happens: auditor scope retains the row for the regulatory retention window.\n\nScope: master.write"
          }
        }
      ]
    },
    {
      "name": "Plot",
      "item": [
        {
          "name": "GET /plots — List plots",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/plots",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "plots"
              ],
              "variable": [],
              "query": [
                {
                  "key": "ownerPartyId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "country",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only records changed at or after this instant (read from a 5-minute overlap before it; dedupe by id). Combine with `includeDeleted=true` to learn of deletions."
                },
                {
                  "key": "includeDeleted",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Scope: master.read"
          }
        },
        {
          "name": "POST /plots — Create a plot",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/plots",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "plots"
              ],
              "variable": [],
              "query": []
            },
            "description": "Creates a new plot of land, geolocated by a `polygon` or by a single `point` with `declaredAreaHectares` (EUDR Art. 9(1)(d): a point up to 4 ha; Art. 2(28): any size for a cattle establishment, i.e. `commodities: [\"cattle\"]`). areaHectares (measured for a polygon, declared for a point) and centroid are server-computed; the response carries them. The plot owner (`ownerPartyId`) must be a producing party (kind supplier, sub_supplier, processor, smallholder). Until 2026-12-23 a non-producing owner (certifier, transporter, buyer) is still accepted, and that response carries `Deprecation`, `Sunset` and `Link` headers; from 2026-12-23T00:00:00.000Z it is refused with 422 `plot-owner-not-producer`. Changelog: https://origonex.com/docs/changelog#2026-09-24-plot-owner-must-be-a-producer.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"\",\n  \"ownerPartyId\": \"\",\n  \"externalRef\": \"\",\n  \"country\": \"\",\n  \"subdivision\": \"\",\n  \"registrySource\": \"car_sicar\",\n  \"registryRef\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /plots/{id} — Read a plot by id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/plots/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "plots",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: master.read"
          }
        },
        {
          "name": "PATCH /plots/{id} — Update a plot",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/plots/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "plots",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Submitting a new polygon re-versions the geometry (the change is recorded in the audit ledger; area + centroid are recomputed); on a point plot it converts the plot to a polygon. `point` / `declaredAreaHectares` restate a point plot under the same rule as creation; a polygon plot is never turned into a point (422). The plot owner (`ownerPartyId`) must be a producing party (kind supplier, sub_supplier, processor, smallholder). Until 2026-12-23 a non-producing owner (certifier, transporter, buyer) is still accepted, and that response carries `Deprecation`, `Sunset` and `Link` headers; from 2026-12-23T00:00:00.000Z it is refused with 422 `plot-owner-not-producer`. Changelog: https://origonex.com/docs/changelog#2026-09-24-plot-owner-must-be-a-producer. A patch that does not change `ownerPartyId` (absent, or the owner the plot already has) is not judged by this rule.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "DELETE /plots/{id} — Soft-delete a plot",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/plots/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "plots",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: master.write"
          }
        }
      ]
    },
    {
      "name": "Tokens",
      "item": [
        {
          "name": "GET /tokens/{publicToken}/proof — Resolve an anchored Token to its on-chain proof reference",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/tokens/:publicToken/proof",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tokens",
                ":publicToken",
                "proof"
              ],
              "variable": [
                {
                  "key": "publicToken",
                  "value": "",
                  "description": "256-bit CSPRNG capability token (base64url, ~43 chars)"
                }
              ],
              "query": []
            },
            "description": "Returns the chain, contract address, token id / Merkle root, transaction hash, block number, and confirmation timestamp for a given Token public token. Public, unauthenticated: the proof is the trust artifact. The path segment is the high-entropy `publicToken` (256-bit, base64url), NOT the internal cuid id: the id is enumerable, the publicToken is not.\n\nScope: public",
            "auth": {
              "type": "noauth"
            }
          }
        }
      ]
    },
    {
      "name": "Label",
      "item": [
        {
          "name": "GET /batches/{batchId}/label — Resolve the mark a batch should carry",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/batches/:batchId/label",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "batches",
                ":batchId",
                "label"
              ],
              "variable": [
                {
                  "key": "batchId",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "mode",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "gtin",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "serial",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Returns the payload URL a printed or encoded mark for this batch must carry, plus the facts printed beside it. `payload` is null when the batch has never been sealed: there is nothing truthful to encode yet, which is a state rather than an error. Request `mode=gs1_digital_link` with a `gtin` to get a GS1 Digital Link that retail POS scanners resolve; without a GTIN the response falls back to the verifier URL rather than emitting an unresolvable Digital Link.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /batches/{batchId}/label.svg — Render the batch label as vector artwork",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/batches/:batchId/label.svg",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "batches",
                ":batchId",
                "label.svg"
              ],
              "variable": [
                {
                  "key": "batchId",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "mode",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "gtin",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "serial",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "preset",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "ecc",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "showPayloadUrl",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "The whole label (mark and text) as a self-contained SVG in millimetre units, for pre-press artwork or a raster-only driver that scales it to the print head. Resolution-independent: nothing is resampled from a fixed bitmap.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /batches/{batchId}/label.zpl — Render the batch label as ZPL II",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/batches/:batchId/label.zpl",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "batches",
                ":batchId",
                "label.zpl"
              ],
              "variable": [
                {
                  "key": "batchId",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "mode",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "gtin",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "serial",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "preset",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "ecc",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "dpi",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "copies",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "ZPL II for a Zebra-compatible printer at the packing line. The symbol is sent as text for the printer firmware to draw (`^BQ`), so it lands exactly on the dot grid instead of being resampled, and `^PQ` repeats the label in the printer rather than in the caller.\n\nScope: custody.read"
          }
        }
      ]
    },
    {
      "name": "Custody",
      "item": [
        {
          "name": "GET /custody/status — Whether chain-of-custody is enabled for the calling tenant",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "status"
              ],
              "variable": [],
              "query": []
            },
            "description": "Answers 200 when chain-of-custody resources are available to the caller, and 404 when they are not: the same response an unknown path gets.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/templates — The chain each commodity typically runs through",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/templates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "templates"
              ],
              "variable": [],
              "query": []
            },
            "description": "The stage template of each of the seven EUDR commodities: its stages in chain order (with the units, documents and kinds of site each typically has, and the tolerance on a receipt), the transformations between stages with their expected yield and losses, and the link where the chain is most exposed.\n\nReference data, the same for every workspace, versioned: a record stores the `version` it was checked against. A template guides: a yield outside its range is recorded and flagged, never refused.\n\nThe yield ranges, receipt tolerances (`shrink`) and grading thresholds are REFERENCE DEFAULTS, not regulatory tolerances. Each carries a `source`: the published source of its reference values, or `origonex_default` where Origonex chose the value.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/templates/{commodity} — The chain one commodity typically runs through",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/templates/:commodity",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "templates",
                ":commodity"
              ],
              "variable": [
                {
                  "key": "commodity",
                  "value": "",
                  "description": ""
                }
              ],
              "query": []
            },
            "description": "The stage template of one commodity. Templates exist for wood, cattle, coffee, cocoa, palm_oil, soy, rubber; any other commodity answers 404.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/tags/resolve — Resolve a scanned tag to the lot it identifies",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/tags/resolve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "tags",
                "resolve"
              ],
              "variable": [],
              "query": []
            },
            "description": "Takes what a scanner read (a QR or DataMatrix payload, an NFC chip value or the URI it carries, an RFID EPC) and answers which lot it identifies. A POST, not a GET, because the value is often a capability-bearing URL that must not land in access logs.\n\nAccepted, first match wins: (1) a tag issued in this workspace with the same `kind` and exact `value`; (2) the `tagValue` of one of this workspace’s live vehicles, exactly, answered as `vehicle` (with its id, plate and kind), not a lot; (3) our public proof link `https://verify.origonex.com/t/{workspace}/{token}` or `https://verify.origonex.com/{token}`, resolved to the lot the proof covers; (4) our GS1 Digital Link `https://verify.origonex.com/dl/01/{gtin}/10/{lot}[/21/{serial}]?ox={token}`, resolved on `ox` only; a Digital Link without `ox`, or with an empty or malformed one, is not a mark we issued; (5) a product unit’s page `https://verify.origonex.com/u/{key}` (or under `/t/{workspace}/`), or its Digital Link carrying the unit’s key as `ox` with the unit’s own GTIN and serial (AI 21), resolved to the unit’s lot. Links on any other host, including other GS1 resolvers, answer `unknown`.\n\n`status` is `resolved` (with the lot), `revoked` (the mark was withdrawn; `revokedAt` says when), `unbound` (a tag of this workspace not on any lot; link it with `/custody/tags/{id}/bind`), `vehicle` (a vehicle’s tag) or `unknown`. A tag or proof belonging to another workspace answers `unknown`, identically to a value nobody issued.\n\nScope: custody.read",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"qr\",\n  \"value\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/tags/{id}/bind — Put a tag on a lot",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/tags/:id/bind",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "tags",
                ":id",
                "bind"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": []
            },
            "description": "Links a tag of this workspace to a lot, recorded in the audit trail as `tag.bound`. Idempotent on the same lot. A tag already on another lot is refused with 409 unless `move` is true. A revoked tag is refused with 409: a withdrawn mark does not come back into use by being re-linked.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"batchId\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/tags/link — Link what was scanned to a lot",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/tags/link",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "tags",
                "link"
              ],
              "variable": [],
              "query": []
            },
            "description": "Links a scanned `(kind, value)` to a lot in one step: the tag is issued on the lot (audited as `tag.created`) when this workspace does not hold it yet, and bound under the rules of `/custody/tags/{id}/bind` (audited as `tag.bound`) when it does. Safe to retry: a repeat on the same lot changes nothing.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"qr\",\n  \"value\": \"\",\n  \"batchId\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/events/{id}/correction — Correct a recorded event",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/events/:id/correction",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "events",
                ":id",
                "correction"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": []
            },
            "description": "Recorded events are never edited or deleted. A correction is a NEW event that supersedes the one in the path; both stay in the history, labelled, and the public page and the phones show the correction only. Recorded in the audit trail as `event.corrected`.\n\nWhat the body leaves out is kept from the event being corrected. `payload`, when sent, is the whole new payload for the event type, not a patch. The type, the subject, the location and the attachments are always kept: an event of the wrong type, or recorded against the wrong subject, is retracted and recorded again. `reason` is required.\n\nCorrecting or retracting an `audit` or `inspection` event also needs the audit & verification capability, a `mass_balance_credit` or `mass_balance_claim` event compliance-output approval, and a `geometry_repaired` event master-data write: the same capability that records it (403 otherwise).\n\nAn event is superseded at most once; correct the latest version.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/events/{id}/retraction — Retract a recorded event",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/events/:id/retraction",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "events",
                ":id",
                "retraction"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": []
            },
            "description": "Withdraws a recorded event: it leaves the public page and the phones, and the history keeps the retraction, with who, when and why, in its place. Recorded in the audit trail as `event.retracted`. A retraction cannot itself be corrected or retracted.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/events — The event history of a subject",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/events",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "events"
              ],
              "variable": [],
              "query": [
                {
                  "key": "subjectType",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "subjectId",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                },
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                }
              ]
            },
            "description": "Every recorded version of the events on one subject, oldest first: `kind` is `record`, `correction` (of `supersedesEventId`) or `retraction`; `correctedById` is set on a version a later correction replaced. A retracted event is not listed; its retraction is, at the same time. Keyset-paged: pass `nextCursor` back as `cursor`.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/events — Record an event, signed on a device or not",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/events",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "events"
              ],
              "variable": [],
              "query": []
            },
            "description": "Records a custody event with the checks every capture gets: the subject is a live record of this workspace, the time is believable, the place is on the globe, every attachment is a live document here, the payload fits the type, and the caller may record the type. Recorded in the audit trail as `event.created`, with the fingerprint of the event’s content.\n\n`deviceSignature` is the phone’s signature over the event and the bytes of its files (see the schema for the exact bytes signed). The server recomputes what was signed from the event as it records it, checks it against the caller’s registered key, and records the verdict with the event (`deviceSignature` in the answer). A verdict other than `verified` FLAGS the event: it is recorded, never refused.\n\n`id` is the client’s. The same id for the same event answers the event it recorded (`replayed: true`, 200); for a different event, 409 `EVENT_ID_TAKEN`.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"\",\n  \"eventType\": \"harvest\",\n  \"subjectType\": \"plot\",\n  \"subjectId\": \"\",\n  \"occurredAt\": \"2026-01-01T00:00:00.000Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/events/{id} — Read one recorded version of an event",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/events/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "events",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": []
            },
            "description": "One version as the history lists it: `kind` is `record`, `correction` or `retraction`; `correctedById` names the version that replaced it, if one did.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/sites — List sites",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/sites",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "sites"
              ],
              "variable": [],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                },
                {
                  "key": "includeDeleted",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "kind",
                  "value": "",
                  "disabled": true,
                  "description": "What kind of place the site is"
                }
              ]
            },
            "description": "Sites in (updatedAt, id) order. Pass the previous page's `nextCursor` as `cursor` to continue. For incremental sync pass `updatedSince` (and `includeDeleted=true` to learn of deletions): the server re-reads from 5 minutes before it, because writes commit out of `updatedAt` order, so the same site can come back in consecutive syncs; dedupe by `id`. A page walk alone is not a complete sync; finish it with an `updatedSince` read. A cursor that was not produced by this API is refused with 400.\n\nScope: master.read"
          }
        },
        {
          "name": "POST /custody/sites — Register a site",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/sites",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "sites"
              ],
              "variable": [],
              "query": []
            },
            "description": "Registers a place where custody happens. `ownerPartyId` must be one of your live parties and `responsibleUserId` an active member of your workspace; anything else is refused with 422.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"yard\",\n  \"name\": \"\",\n  \"country\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/sites/{id} — Read a site",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/sites/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "sites",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: master.read"
          }
        },
        {
          "name": "PATCH /custody/sites/{id} — Update a site",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/sites/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "sites",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Absent fields are kept; `null` clears an optional field.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "DELETE /custody/sites/{id} — Delete a site",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/sites/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "sites",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Soft delete: the site disappears from lists and can no longer be named by new records, and stays readable in incremental sync (`includeDeleted=true`) with `deletedAt` set.\n\nScope: master.write"
          }
        },
        {
          "name": "GET /custody/lots — List lots",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/lots",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "lots"
              ],
              "variable": [],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id."
                },
                {
                  "key": "commodity",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "includeDeleted",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "The workspace’s lots in `(updatedAt, id)` order: the feed an ERP syncs from. `includeDeleted=true` with `updatedSince` also returns archived lots (with `deletedAt`), so an incremental reader learns of them.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/lots — Form a lot",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/lots",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "lots"
              ],
              "variable": [],
              "query": []
            },
            "description": "Forms a lot of one commodity from its producer, with its origin plots and lineage, with the same checks as the app (every reference resolves in this workspace, mass balance against parents, the lot code unique, the unit fits the commodity and, with `stage`, the stage). Recorded in the audit trail as `batch.created`.\n\nIdempotent by the body’s `id` (a lot id the client chose, 16 to 64 url-safe characters): the same lot again answers the lot already made; the same id with a different lot is 409. The `Idempotency-Key` header works too.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"commodity\": \"\",\n  \"weightKg\": 1,\n  \"unitType\": \"live_weight_kg\",\n  \"producedAt\": \"2026-01-01T00:00:00.000Z\",\n  \"producedByParty\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/lots/{id} — Read a lot",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/lots/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "lots",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "GET /custody/certificates — List certificates",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/certificates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "certificates"
              ],
              "variable": [],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id."
                },
                {
                  "key": "certifier",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "subjectType",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "subjectId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "includeDeleted",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "The workspace’s certificates (held by a party, a plot, a site or a lot) in `(updatedAt, id)` order. `updatedAt` moves with a registry check too.\n\nScope: master.read"
          }
        },
        {
          "name": "POST /custody/certificates — Record a certificate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/certificates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "certificates"
              ],
              "variable": [],
              "query": []
            },
            "description": "Records a certificate the holder presented: the scheme, its number, what it covers and its validity (`validUntil` on or after `validFrom`). Recorded in the audit trail as `certificate.imported`. Check it against the certifier’s registry with `POST /custody/certificates/{id}/registry-check`.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"certifier\": \"fsc\",\n  \"certificateNumber\": \"\",\n  \"subjectType\": \"plot\",\n  \"subjectId\": \"\",\n  \"validFrom\": \"\",\n  \"validUntil\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/certificates/{id} — Read a certificate",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/certificates/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "certificates",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: master.read"
          }
        },
        {
          "name": "GET /custody/claims — List claim declarations",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/claims",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "claims"
              ],
              "variable": [],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id. A declaration is never edited: this keeps the ones recorded since."
                },
                {
                  "key": "batchId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ]
            },
            "description": "Every claim declaration of the workspace (what a supplier’s invoice stated about a lot) in `(createdAt, id)` order. A declaration is never edited: a correction is a new one naming the one it corrects in `supersedesId`. Declare one with `POST /custody/batches/{id}/claims`; a lot’s current claims are at `GET /custody/batches/{id}/claims`.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/claims/{id} — Read a claim declaration",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/claims/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "claims",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "GET /custody/vehicles — List vehicles",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/vehicles",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "vehicles"
              ],
              "variable": [],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                },
                {
                  "key": "includeDeleted",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "kind",
                  "value": "",
                  "disabled": true,
                  "description": "What the vehicle is: truck, trailer, boat, rail wagon, other"
                }
              ]
            },
            "description": "Vehicles in (updatedAt, id) order. Pass the previous page's `nextCursor` as `cursor` to continue. For incremental sync pass `updatedSince` (and `includeDeleted=true` to learn of deletions): the server re-reads from 5 minutes before it, so the same vehicle can come back in consecutive syncs; dedupe by `id`. A cursor that was not produced by this API is refused with 400.\n\nScope: master.read"
          }
        },
        {
          "name": "POST /custody/vehicles — Register a vehicle",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/vehicles",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "vehicles"
              ],
              "variable": [],
              "query": []
            },
            "description": "Registers a truck, trailer, boat or rail wagon. The plate is stored normalised (upper case, without spaces, hyphens or dots) and is unique among the live vehicles of the workspace. `capacity` goes with `capacityUnit`; for every mass unit the number is kilograms. `tagValue` is what a QR, barcode or chip on the vehicle carries: `POST /v1/custody/tags/resolve` answers `vehicle` for it.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"plate\": \"\",\n  \"kind\": \"truck\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/vehicles/{id} — Read a vehicle",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/vehicles/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "vehicles",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: master.read"
          }
        },
        {
          "name": "PATCH /custody/vehicles/{id} — Update a vehicle",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/vehicles/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "vehicles",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Absent fields are kept; `null` clears an optional field.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "DELETE /custody/vehicles/{id} — Delete a vehicle",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/vehicles/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "vehicles",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Soft delete: the vehicle leaves lists, its plate and tag value become free, and it stays readable in incremental sync (`includeDeleted=true`) with `deletedAt` set.\n\nScope: master.write"
          }
        },
        {
          "name": "GET /custody/drivers — List drivers",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/drivers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "drivers"
              ],
              "variable": [],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                },
                {
                  "key": "includeDeleted",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Drivers in (updatedAt, id) order, each with the identity the personal-data vault holds now. Same paging and incremental-sync contract as vehicles and sites: pass the previous page's `nextCursor` as `cursor`; `updatedSince` re-reads from 5 minutes before it, dedupe by `id`. A driver whose identity was corrected or erased comes back with a new `updatedAt`.\n\nScope: master.read"
          }
        },
        {
          "name": "POST /custody/drivers — Register a driver",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/drivers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "drivers"
              ],
              "variable": [],
              "query": []
            },
            "description": "Registers a driver. `identity` (name, document, licence) is sealed in the workspace's erasable personal-data vault; the driver record and the audit trail keep only `personalDataRef`. The workspace is the controller of this personal data.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"identity\": {\n    \"fullName\": \"\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/drivers/{id} — Read a driver",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/drivers/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "drivers",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: master.read"
          }
        },
        {
          "name": "PATCH /custody/drivers/{id} — Change the carrier of a driver",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/drivers/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "drivers",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Changes `carrierPartyId` (`null` = the workspace’s own staff). The identity is corrected with `PUT /custody/drivers/{id}/identity`.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "DELETE /custody/drivers/{id} — Delete a driver",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/drivers/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "drivers",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Soft delete: the driver leaves lists and stays readable in incremental sync with `deletedAt` set. The identity stays in the vault until the workspace erases it or the retention purge removes the record.\n\nScope: master.write"
          }
        },
        {
          "name": "PUT /custody/drivers/{id}/identity — Correct a driver's identity",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/drivers/:id/identity",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "drivers",
                ":id",
                "identity"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Replaces the whole identity held for the driver: a rectification of the vault entry: the reference stays the same, so every record that names the driver shows the corrected identity. Requires `master_data:approve`. Refused with 409 once the person was erased (`PERSONAL_DATA_ERASED`) or while a legal hold is open (`LEGAL_HOLD_ACTIVE`).\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fullName\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/transformations — List transformations",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transformations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transformations"
              ],
              "variable": [],
              "query": [
                {
                  "key": "batchId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "reference",
                  "value": "",
                  "disabled": true,
                  "description": "CUS-607: only the step with exactly this reference"
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                }
              ]
            },
            "description": "Transformations in (recordedAt, id) order. Pass the previous page's `nextCursor` as `cursor` to continue; `batchId` keeps only the steps a lot went into or came out of. A cursor that was not produced by this API is refused with 400.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/transformations — Record a transformation",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transformations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transformations"
              ],
              "variable": [],
              "query": []
            },
            "description": "Records N input lots, each with the quantity it consumes, turning into M new output lots plus losses by kind, at a site: a split, a merge, a process, a pack or a slaughter. Each output becomes a lot. Consuming more of a lot than it still holds (summed over every transformation) is refused with 409, and so is a `reference` (the company’s own number for the step, S-0388) another step already carries; outputs above the inputs, or a malformed record, with 422. A yield outside the commodity template’s range is recorded and listed in `alerts`, not refused. Send an `id` to make a retry safe: the same id with the same inputs returns the stored record with 200.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"process\",\n  \"occurredAt\": \"2026-01-01T00:00:00.000Z\",\n  \"inputs\": [\n    {\n      \"batchId\": \"\",\n      \"quantity\": 1\n    }\n  ],\n  \"outputs\": [\n    {\n      \"quantity\": 1,\n      \"unitType\": \"live_weight_kg\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/transformations/{id} — Read a transformation",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transformations/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transformations",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "GET /custody/transports — List transports",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transports",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transports"
              ],
              "variable": [],
              "query": [
                {
                  "key": "status",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "originSiteId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "destinationSiteId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "batchId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "reference",
                  "value": "",
                  "disabled": true,
                  "description": "CUS-607: only the trip with exactly this reference"
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Transports in (updatedAt, id) order. Pass the previous page's `nextCursor` as `cursor` to continue. `status=loaded,in_transit&destinationSiteId=…` is the trips on their way to a site; `batchId` keeps the transports that carry a lot; `updatedSince` reads what changed. A cursor that was not produced by this API is refused with 400.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/transports — Create a transport",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transports",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transports"
              ],
              "variable": [],
              "query": []
            },
            "description": "Creates one leg: carrier, vehicle, driver, origin and destination (each a site or a party), planned times, an optional route. With `loads` the lots go on board in the same call (status `loaded`), and with `actualDepartureAt` the truck leaves too (`in_transit`): the phone’s \"load and leave\", offline. A `reference` (the company’s own number for the trip, T-2091) another trip already carries is refused with 409. Loading more of a lot than it still holds at the origin (not already loaded, consumed or transformed) is refused with 409. The loaded total is compared with the weighing (net = gross − tare, or a measured volume): outside the tolerance it is recorded and listed in `alerts`, not refused. Send an `id` to make a retry safe: the same id for the same trip returns the stored transport with 200.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": {},\n  \"destination\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/transports/{id} — Read a transport",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transports/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transports",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "PATCH /custody/transports/{id} — Edit a planned transport",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transports/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transports",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Changes the header (carrier, vehicle, driver, places, plan, route, shipment, note, reference) while the transport is planned. From `loaded` on the header is fixed: 409.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/transports/{id}/load — Load a planned transport",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transports/:id/load",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transports",
                ":id",
                "load"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Puts the lots on board, each with its quantity, and records the weighing. Status `planned` → `loaded`; the header is fixed from here on.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"loads\": [\n    {\n      \"batchId\": \"\",\n      \"quantity\": 1\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/transports/{id}/depart — The truck leaves",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transports/:id/depart",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transports",
                ":id",
                "depart"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Status `loaded` → `in_transit`, with the actual departure time.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"actualDepartureAt\": \"2026-01-01T00:00:00.000Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/transports/{id}/deliver — The truck arrives",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transports/:id/deliver",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transports",
                ":id",
                "deliver"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Status `in_transit` → `delivered`, with the actual arrival time. What was received is the receipt.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"actualArrivalAt\": \"2026-01-01T00:00:00.000Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/transports/{id}/cancel — Cancel a transport",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/transports/:id/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "transports",
                ":id",
                "cancel"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Any status before `delivered` → `cancelled`, with a reason. The lots it carried count as available again.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/receipts — List receipts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/receipts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "receipts"
              ],
              "variable": [],
              "query": [
                {
                  "key": "siteId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "transportId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "batchId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "outcome",
                  "value": "",
                  "disabled": true,
                  "description": "accepted: everything that arrived; partial: part of it (say how much per lot); rejected: none of it."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                }
              ]
            },
            "description": "Receipts in (createdAt, id) order. Pass the previous page's `nextCursor` as `cursor` to continue. `siteId` is what a site received; `batchId` the receipts of a lot. A cursor that was not produced by this API is refused with 400.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/receipts — Receive a transport",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/receipts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "receipts"
              ],
              "variable": [],
              "query": []
            },
            "description": "Receives a transport at its destination site: when, the weighing there, the ticket, the photos (documents already filed against the transport), and for each lot on board what arrived and what was accepted: `accepted`, `partial` (say how much per lot) or `rejected`, with a reason for the last two. The transport becomes `delivered`. What was accepted is placed at the site, and custody passes to the site and the party that owns it; what was refused goes back to the origin; the difference between sent and received is the transit loss. A divergence beyond the directional band of the lot’s stage is recorded, listed in `alerts` and raised in the compliance queue, never refused. Send an `id` to make a retry safe.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"transportId\": \"\",\n  \"outcome\": \"accepted\",\n  \"lines\": [\n    {\n      \"batchId\": \"\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/receipts/{id} — Read a receipt",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/receipts/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "receipts",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "GET /custody/batches/{id}/verification — Check a lot's chain of custody",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/verification",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "verification"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Recomputes the content fingerprint of every record upstream of the lot (the events recorded on each lot, the transformations that made them and their lines, the transports that carried a lot with their load lines, events and receipts, and the documents) from the records as they are now, and compares each with the fingerprint stored with it, with the one the audit trail recorded when it was written, and with the lot’s last chain seal. `ok` is true when nothing differs and the whole chain was read; otherwise `divergences` names each record that differs (`recordType`, `id`) and where (`row`, `ledger`, `chain`, `seal`). A record the last seal covered that was deleted through the application, or that the chain no longer reaches because a link was removed through it, is listed in `departures` when the audit trail records that act after the seal: it does not count as a difference, and sealing again covers the chain as it is. A record that left without such an entry is a difference. Records written before fingerprints existed are counted in `unhashed` and never as checked. The check reads at most 5 000 lots and 64 links upstream; beyond, `truncated` is true and `ok` false. The check shows the records match the audit trail; it does not by itself prove when they were written.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/batches/{id}/seal — Seal a lot's chain of custody",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/seal",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "seal"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Checks the chain as `GET …/verification` does and, only when nothing differs, records a seal over the fingerprints of every record in it. A chain the live seal already covers answers `unchanged` and records nothing; a chain that grew, or that records left without being altered (`departures`), answers `sealed` with a new seal that supersedes the last. A chain where a record differs is refused with 409 and the check that says which; so is a chain larger than one check reads.\n\nScope: custody.write"
          }
        },
        {
          "name": "GET /custody/timestamps — The days of the audit trail timestamped by an independent authority",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/timestamps",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "timestamps"
              ],
              "variable": [],
              "query": [
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id. A day is closed once and never changes."
                }
              ]
            },
            "description": "Newest first. Each day covers the audit-trail entries written since the previous day, in order, and carries their fingerprint. Once a day, the fingerprint of every workspace’s day is sent to an independent RFC 3161 time-stamping authority; `GET /custody/timestamps/{day}` returns what is needed to check a day without Origonex.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/timestamps/{day} — Download one day of the audit trail with its independent timestamp",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/timestamps/:day",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "timestamps",
                ":day"
              ],
              "variable": [
                {
                  "key": "day",
                  "value": "",
                  "description": "ISO 8601 calendar date"
                }
              ],
              "query": []
            },
            "description": "Every audit-trail entry of the day with the content its fingerprint is computed from, the fingerprint of the day, the path from it to the fingerprint the authority stamped, and the authority’s time-stamp token with its certificates. Check it anywhere with `node scripts/verify-timestamp.mts --day <day> --export <file>`: it recomputes every fingerprint from the entries and verifies the token with OpenSSL. The entries are read as they are now, so an entry changed after the day was stamped makes the check fail. `timestamp` is null while the authority has not answered yet. Above 20 000 entries in a day, entries carry their fingerprint only.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/ledger-events/{ledgerEventId}/inclusion-proof — The proof that one audit-trail entry is in an independently timestamped day",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/ledger-events/:ledgerEventId/inclusion-proof",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "ledger-events",
                ":ledgerEventId",
                "inclusion-proof"
              ],
              "variable": [
                {
                  "key": "ledgerEventId",
                  "value": "",
                  "description": ""
                }
              ],
              "query": []
            },
            "description": "CUS-301. The entry with the content its fingerprint is computed from, the path from its fingerprint to the fingerprint of its day (hashes only; the other entries are not disclosed), the path on to the fingerprint every workspace’s day shares, and the authority’s time-stamp token over it. Check it anywhere with `node scripts/verify-timestamp.mts --record <file>`. `status` is `open` (no `proof`) while the entry’s day is not closed, and `pending` (a proof whose `timestamp` is null) while the authority has not answered.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/inclusion-proofs — Where each audit-trail entry of one record stands on the way to a timestamped day",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/inclusion-proofs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "inclusion-proofs"
              ],
              "variable": [],
              "query": [
                {
                  "key": "subjectType",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "subjectId",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "CUS-301. The entries recorded against one subject (a lot, a transport, a receipt…), oldest first, up to 200, each with the day that covers it and whether that day is timestamped yet. The proof of each is `GET /custody/ledger-events/{ledgerEventId}/inclusion-proof`.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/tag-rolls — The rolls of loose labels, newest first, with what is left on each",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/tag-rolls",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "tag-rolls"
              ],
              "variable": [],
              "query": [
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id. A roll is never edited: `updatedSince` keeps the rolls issued since then; what is left on a roll is read off its labels each time."
                }
              ]
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "POST /custody/tag-rolls — Issue a roll of loose labels",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/tag-rolls",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "tag-rolls"
              ],
              "variable": [],
              "query": []
            },
            "description": "Creates `count` QR labels (1 to 500) that belong to no lot yet, for a field buyer to link on the spot by scanning (`POST /custody/tags/link`). Each label carries an internal code (`OXL-…`), not a URL. Idempotent on `requestId`: a retry with the same key and count answers the same roll; the same key with another count is a 409.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"count\": 1,\n  \"requestId\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/tag-rolls/{id} — One roll and each of its labels: free, used (with its lot) or withdrawn",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/tag-rolls/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "tag-rolls",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": []
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "GET /custody/supply-links — The supply links this workspace holds as producer",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/supply-links",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "supply-links"
              ],
              "variable": [],
              "query": [
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id."
                }
              ]
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "POST /custody/supply-links — Offer an importer a supply link, consenting at once to its scope",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/supply-links",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "supply-links"
              ],
              "variable": [],
              "query": []
            },
            "description": "The importer is named by its workspace handle. The link shares only the lots listed, only the fields listed that each lot’s visibility policy also opens to importers (omitted: the conservative default), and the due-diligence statements only with `shareDds`. One open link per importer.\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"importerSlug\": \"\",\n  \"scope\": {\n    \"batchIds\": []\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/supply-links/{id}/grant — Consent to a request, or replace what a granted link shares",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/supply-links/:id/grant",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "supply-links",
                ":id",
                "grant"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "The scope REPLACES the previous one: a lot left out stops being visible to the importer on its next read.\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"scope\": {\n    \"batchIds\": []\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/supply-links/{id}/end — Decline a request or revoke a consent",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/supply-links/:id/end",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "supply-links",
                ":id",
                "end"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Terminal: the importer reads nothing through the link from this moment on, and a new consent is a new link. Accepted with the chain-of-custody programme off.\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/supply-link-requests — Ask a producer for a supply link (as the importer)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/supply-link-requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "supply-link-requests"
              ],
              "variable": [],
              "query": []
            },
            "description": "Shares nothing until the producer grants it.\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"producerSlug\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/importer/links/{id}/accept — Accept a producer’s offered supply link (as the importer)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/importer/links/:id/accept",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "importer",
                "links",
                ":id",
                "accept"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "An offer shares nothing and counts nowhere until the importer accepts it; from then on the link shares the offered scope.\n\nScope: sharing.manage"
          }
        },
        {
          "name": "POST /custody/importer/links/{id}/decline — Decline a producer’s offered supply link (as the importer)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/importer/links/:id/decline",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "importer",
                "links",
                ":id",
                "decline"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Ends the offer: it shares nothing and leaves the importer’s list.\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/importer/links — The supply links naming this workspace as importer",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/importer/links",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "importer",
                "links"
              ],
              "variable": [],
              "query": [
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id."
                }
              ]
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "GET /custody/importer/lots — The lots producers shared with this workspace, as an importer may see them",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/importer/lots",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "importer",
                "lots"
              ],
              "variable": [],
              "query": [
                {
                  "key": "linkId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "take",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "skip",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Resolves the granted links first: none, and the list is empty. Each lot is projected for the `importer` audience under its visibility policy narrowed to the fields the link names; a withheld key is absent, never blank.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/contaminations — List contamination flags",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/contaminations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "contaminations"
              ],
              "variable": [],
              "query": [
                {
                  "key": "status",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                }
              ]
            },
            "description": "Flags newest first, each with how many subjects it marked by type. `status=open` keeps the ones still in force. Pass the previous page's `nextCursor` as `cursor` to continue; a cursor this API did not produce is refused with 400.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/contaminations — Flag a plot or a certificate as contaminated",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/contaminations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "contaminations"
              ],
              "variable": [],
              "query": []
            },
            "description": "Records that a plot or a certificate was found bad, and marks as affected everything that came from it: the lots harvested on the plot (or certified), every lot made from them through transformations or lineage links, the transformations, the shipments carrying them, and every DDS on any of those. The walk is bounded (`traceDepthLimit` steps); `truncated` says it reached a bound. Flagging the same source for the same reason while its flag is open does not flag it twice: it traces again, marks only what is new, and answers 200 with `replayed: true`.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"source\": {\n    \"kind\": \"plot\",\n    \"id\": \"\"\n  },\n  \"reason\": \"deforestation\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/contaminations/{id} — Read a contamination flag and what it marked",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/contaminations/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "contaminations",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "The flag with its marks, nearest the source first (the first 500; `affectedTruncated` says there are more).\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/contaminations/{id}/retrace — Trace an open flag again",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/contaminations/:id/retrace",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "contaminations",
                ":id",
                "retrace"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Walks again from the flagged source and marks what came from it since (a lot made later, a DDS issued since). Nothing new: nothing is written. A lifted flag answers 409.\n\nScope: custody.write"
          }
        },
        {
          "name": "POST /custody/contaminations/{id}/lift — Lift a contamination flag",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/contaminations/:id/lift",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "contaminations",
                ":id",
                "lift"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Closes the flag with the reason (the source was cleared). The marks stay as history; they stop showing as affected. A flag is lifted once: a source found bad again is flagged again.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/contaminations/{id}/mitigations — List the mitigations recorded on a flag",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/contaminations/:id/mitigations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "contaminations",
                ":id",
                "mitigations"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id. A note is never edited: this keeps the notes recorded since."
                }
              ]
            },
            "description": "Every note recorded against the flag, oldest first.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/contaminations/{id}/mitigations — Record a mitigation so a DDS can be issued over an open flag",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/contaminations/:id/mitigations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "contaminations",
                ":id",
                "mitigations"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "A DDS whose subject (lot, shipment or plot) is reached by an open contamination flag is refused with `DDS_CONTAMINATION_OPEN` (EUDR Art. 10 and Art. 11). This records what was done about the flag for that subject (at least 20 characters) and from then on the flag no longer blocks that subject’s statement. The flag stays open and the banner stays. One note per flag and subject; the audit trail records its fingerprint, never its words. A flag that does not reach the subject answers 422 `MITIGATION_NOT_REACHING`.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"subject\": {\n    \"type\": \"batch\",\n    \"id\": \"\"\n  },\n  \"note\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/batches/{id}/passages — The passages a fixed gate reader saw of this lot",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/passages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "passages"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": [
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id. A passage is never edited: this keeps the passages recorded since."
                }
              ]
            },
            "description": "CUS-308. Newest first: when and at which site’s gate the lot went through, which way, with which vehicle, as the gate reader behind the site’s hardware bridge saw it and the tag registry identified it.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/vehicles/{id}/passages — The passages a fixed gate reader saw of this vehicle",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/vehicles/:id/passages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "vehicles",
                ":id",
                "passages"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": [
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id. A passage is never edited: this keeps the passages recorded since."
                }
              ]
            },
            "description": "CUS-308. Newest first: when and at which site’s gate the vehicle went through, which way, and the lots identified with it.\n\nScope: master.read"
          }
        },
        {
          "name": "POST /custody/bridge/events — Push signed events from a hardware bridge",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/bridge/events",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "bridge",
                "events"
              ],
              "variable": [],
              "query": []
            },
            "description": "Up to 100 signed events of this workspace’s bridges, each answered on its own, in the order sent. A bridge is registered at a site from the web (settings → hardware), which shows its HMAC key once.\n\nAn event is the envelope `{bridgeId, deviceClass, sourceId, occurredAt, payload, signature}`; `signature` is the HMAC-SHA256, base64, of the canonical JSON of the other five fields with keys sorted at every level. Only `weighing_scale` is taken today.\n\nA `gate_reader` event (CUS-308) is ONE passage at the gate of the bridge’s site: `payload.reads` holds everything the fixed QR/RFID reader saw, each resolved in this workspace’s tag registry (a lot’s tag, a unit’s mark, a vehicle’s QR). It is answered with `passageId` (the passage recorded on the lots and the vehicle, the same one on a replay) or `passageId: null` with `passageOutcome` `no_match` (nothing of this workspace was identified; the reading is kept) or `vehicles_ambiguous` (two vehicles in one passage). Wire format: `spec/06_architecture/09_hardware-bridge.md` §\"Gate reader (CUS-308)\".\n\n`accepted: true` gives the reading’s `readingId` (what a receipt names in `weighing.readingId` or a line’s `readingId` (`POST /v1/custody/receipts`)) with `duplicate: true` when the same `(bridgeId, sourceId, occurredAt)` was accepted before (the first id comes back, nothing is written) and `usable: false` when the scale’s reading is not a weight (unstable, overload, underload, invalid): it is kept, and a receipt cannot take it.\n\n`accepted: false` gives the reason: `stale` (occurredAt more than 5 minutes from the server’s clock), `bad_signature`, `wrong_key`, `bridge_unknown` (including another workspace’s bridge), `bridge_revoked`, `plan_not_entitled` (the plan does not include the hardware bridge), `unsupported_device_class` (also a class the bridge was not registered for: Settings → Hardware), `malformed_envelope`, `malformed_payload`.\n\nScope: bridge.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"bridgeId\": \"\",\n      \"deviceClass\": \"\",\n      \"sourceId\": \"\",\n      \"occurredAt\": \"\",\n      \"payload\": {},\n      \"signature\": \"\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/device-keys — List phone signing keys",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/device-keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "device-keys"
              ],
              "variable": [],
              "query": [
                {
                  "key": "scope",
                  "value": "",
                  "disabled": true,
                  "description": "`mine` (default): the caller’s keys. `workspace`: every key; needs workspace settings"
                },
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id. A key changes once, when it is revoked."
                }
              ]
            },
            "description": "The phones’ signing keys, newest first, revoked ones included. `scope=mine` (default) lists the caller’s; `scope=workspace` lists every key and needs workspace settings. Only the public half of a key is ever here; the private half never leaves the phone’s keystore.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/device-keys — Register a phone signing key",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/device-keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "device-keys"
              ],
              "variable": [],
              "query": []
            },
            "description": "Registers the public half of an ECDSA P-256 key the phone generated in its keystore, bound to the CALLER and this workspace. `id` is a UUID the phone chose when it generated the key, so events it signed offline already name it. Recorded in the audit trail as `device_key.registered` (the id and the fingerprint).\n\nSafe to retry: the same id with the same key answers the key registered before (`replayed: true`, 200). Refused (422): a key that is not P-256 (`DEVICE_KEY_INVALID`), an id that names another key (`DEVICE_KEY_ID_TAKEN`), a key already registered under another id (`DEVICE_KEY_ALREADY_REGISTERED`), or a user holding 10 active keys (`DEVICE_KEY_LIMIT`).\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"\",\n  \"publicKeySpki\": \"\",\n  \"label\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/device-keys/{id}/revocation — Revoke a phone signing key",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/device-keys/:id/revocation",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "device-keys",
                ":id",
                "revocation"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": []
            },
            "description": "Revokes a key, finally: from then on an event it signed is recorded with the verdict `revoked_key`, whatever time the phone claims it signed at; the events it signed before keep theirs. The caller may revoke a key of their own; any key needs workspace settings (a lost phone). Another member’s key answers 404 to anyone else. Recorded in the audit trail as `device_key.revoked`. Revoking a revoked key answers it as it is.\n\nScope: sharing.manage"
          }
        },
        {
          "name": "GET /custody/batches/{id}/critical-link — Check the critical link of a lot's commodity",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/critical-link",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "critical-link"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Walks the lot’s upstream chain (both the transformations that made each lot and the recorded lot links; at most 5 000 lots and 64 links) and judges the one link where its commodity typically takes in material of unknown origin: the sawmill yield for wood, the indirect supplier (rearing and calf farms) for cattle, who each bunch load came from at the mill for palm oil, the village buyer for cocoa, the collector and the reseller for rubber, the point where coffee stops being one producer’s, and the silo (with mass balance) for soy. `status` is `pass`, `warn` (it fails or cannot be proven; `findings` say where, naming the lot) or `not_applicable` (the lot has not reached the link, or its commodity has no stage template). A warning does not refuse a statement; it is shown before one is issued. A chain that records no stage, a walk cut short, or a parent lot this workspace cannot see are warnings, never a pass.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/batches/{id}/certificates — The certificates a lot's chain of custody met",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/certificates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "certificates"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Walks the lot’s upstream chain and lists every certificate it met (the lot’s own, the producer’s, an origin plot’s, the stage (transformation) that made a lot, the site it happened at or the site that received the lot, and the parties that own or run them), each judged at the moment the material passed there: `in_force`, `expired`, `not_yet_valid` or `suspended`. `inherited` says whether the lot carries that certification through a segregated chain. A certificate not in force is what the `chain.certificates_valid` check reports before a statement is issued.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/batches/{id}/claims — A lot’s certification claims",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/claims",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "claims"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "The lot’s current claim under each scheme (`declared` (what the supplier’s invoice states, for a lot bought in) or `computed` (from the inputs of the step that made it, under the control system the step was run under)) and every declaration of the lot, oldest first, corrections included. `declarable` is false for a lot a step made: its claim is computed, never declared.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/batches/{id}/claims — Record what a supplier’s invoice states about a lot",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/claims",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "claims"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Declares the lot’s claim under one scheme (FSC 100%, FSC Mix 70%, RSPO Segregated…) as its supplier’s invoice states it, with the supplier’s certificate code. To correct it, send `supersedesId` naming the lot’s current declaration for the scheme; a claim a step has already used cannot be corrected. A lot a step made cannot be declared (409). Send an `id` to make a retry safe: the same id with the same claim, certificate code, certificate and note returns the stored declaration with 200; the same id with any of them changed is a 409 (`CLAIM_ID_TAKEN`); send a correction under a new id.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"scheme\": \"fsc\",\n  \"category\": \"fsc_100\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/credit-accounts — The credit accounts and their balances",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/credit-accounts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "credit-accounts"
              ],
              "variable": [],
              "query": [
                {
                  "key": "siteId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "scheme",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                }
              ]
            },
            "description": "One account per scheme × product class × site, kept by the credit systems (FSC/PEFC credit, mass balance): what came in, what went out, and the balance, which never goes negative. `siteId` and `scheme` narrow the list.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/credit-accounts/{id}/entries — An account’s movements, newest first",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/credit-accounts/:id/entries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "credit-accounts",
                ":id",
                "entries"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                }
              ]
            },
            "description": "Each credit a step booked and each debit an output drew, with the step and the lot. Pass the previous page's `nextCursor` as `cursor` to continue; a cursor that was not produced by this API is refused with 400.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/trademark-licences — The company's trademark licence codes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/trademark-licences",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "trademark-licences"
              ],
              "variable": [],
              "query": []
            },
            "description": "Every licence code the company registered, one live per certification scheme, revoked ones kept with the date. A certification claim is shown on the public page only under a scheme with a live licence, with its code.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/trademark-licences — Register a scheme's trademark licence code",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/trademark-licences",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "trademark-licences"
              ],
              "variable": [],
              "query": []
            },
            "description": "Registers the code for the scheme, replacing (revoking) the live one. FSC codes are `FSC-C` and six digits, PEFC `PEFC/nn-nn-n…`, RSPO like `9-2497-18-100-00`; the other schemes take the code as printed. A malformed code is 422 `LICENCE_CODE_INVALID`.\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"scheme\": \"fsc\",\n  \"code\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/trademark-licences/{id}/revoke — Revoke a trademark licence code",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/trademark-licences/:id/revoke",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "trademark-licences",
                ":id",
                "revoke"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Ends the licence: a public claim under its scheme stops being shown on the next read. A revoked licence is 409.\n\nScope: sharing.manage"
          }
        },
        {
          "name": "GET /custody/conflicts/{id} — Read an offline conflict between devices",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/conflicts/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "conflicts",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "The item a conflict raised on a lot (drawn on beyond what it holds by records two devices made with no signal) or on a truck (received twice): every record it names (kept, flagged or refused) with who recorded it, how much, when on the ground and when it reached the server, and how the item was closed, if it was.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/conflicts/{id}/resolve — Close an offline conflict, explained",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/conflicts/:id/resolve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "conflicts",
                ":id",
                "resolve"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Closes an open item with the approver's note: the surplus is real, or which side is wrong and how it is being put right. The note stays on the item; the audit trail records its fingerprint. While a lot's item is open, a DDS whose chain holds the lot is refused. An item already closed answers 404.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"note\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/visibility — Read the company’s visibility policy",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/visibility",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "visibility"
              ],
              "variable": [],
              "query": []
            },
            "description": "The policy in force for the company’s lots, field by field, with the catalogue default it starts from and where each answer comes from (`legacy` is one of the public verifier’s three switches, until the company saves a policy). A field names who may see it among `public` (anyone scanning a printed code), `buyer` (a customer holding a share link) and `importer` (a connected importer); `auditor` and `owner` always see everything that exists. Operator, plate, driver and exact coordinates are never public by default.\n\nScope: custody.read"
          }
        },
        {
          "name": "PATCH /custody/visibility — Change the company’s visibility policy",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/visibility",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "visibility"
              ],
              "variable": [],
              "query": []
            },
            "description": "Sets the audiences of each field named; `null` gives a field back to the catalogue default. Fields not named keep what they had: a company that never saved a policy starts from what its verifier switches already publish, so a first change never opens anything else. The verifier switches follow the policy. Every change is recorded in the audit trail with each field before and after. A field names who may see it among `public` (anyone scanning a printed code), `buyer` (a customer holding a share link) and `importer` (a connected importer); `auditor` and `owner` always see everything that exists. Operator, plate, driver and exact coordinates are never public by default.\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"rules\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/batches/{id}/visibility — Read a lot’s visibility policy",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/visibility",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "visibility"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "The policy in force for one lot: its exception, where it has one, over the company’s policy. A field names who may see it among `public` (anyone scanning a printed code), `buyer` (a customer holding a share link) and `importer` (a connected importer); `auditor` and `owner` always see everything that exists. Operator, plate, driver and exact coordinates are never public by default.\n\nScope: custody.read"
          }
        },
        {
          "name": "PATCH /custody/batches/{id}/visibility — Change a lot’s visibility exception",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/visibility",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "visibility"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Sets, for this lot only, the audiences of each field named; `null` gives a field back to the company’s policy. A change that leaves nothing different from the company’s policy removes the exception. A field names who may see it among `public` (anyone scanning a printed code), `buyer` (a customer holding a share link) and `importer` (a connected importer); `auditor` and `owner` always see everything that exists. Operator, plate, driver and exact coordinates are never public by default.\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"rules\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "DELETE /custody/batches/{id}/visibility — Remove a lot’s visibility exception",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/visibility",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "visibility"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "The lot follows the company’s policy again. Accepted on every plan and with the chain-of-custody programme off: it is how a company takes a lot back to its default after a downgrade.\n\nScope: sharing.manage"
          }
        },
        {
          "name": "GET /custody/batches/{id}/visibility/preview — Preview a lot as each audience receives it",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/visibility/preview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "visibility",
                "preview"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "audience",
                  "value": "",
                  "disabled": true,
                  "description": "`public`: anyone scanning a printed code. `buyer`: a customer holding a share link. `importer`: a connected importer acting with consent. `auditor`: an engagement holder. `owner`: the company itself. `auditor` and `owner` always see everything that exists."
                }
              ]
            },
            "description": "The lot’s record (origin, stages, events, transports, receipts, transformations, documents, certificates, seal) through the same projection every read that leaves the company goes through, once per audience (or for `audience` only). A key the audience may not see is absent, not blanked; a section closed to it is absent entirely.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/certificates/{id}/registry-check — Check a certificate in its certifier's registry",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/certificates/:id/registry-check",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "certificates",
                ":id",
                "registry-check"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Asks the certification scheme's public registry (FSC, RSPO) about this certificate now, stores the answer on the certificate (status, when, and where it was read) and records it in the audit trail. A registry answering anything but `valid` becomes a warning in the certificate checks. When the registry cannot be read nothing is written: 502, or 429 when the registry asks to slow down. `detail` carries the machine code: CERTIFICATE_NOT_FOUND, SCHEME_NOT_SUPPORTED, REGISTRY_OFF, REGISTRY_NOT_CONFIGURED, REGISTRY_UNAVAILABLE, CHECKED_RECENTLY (one check per certificate per minute), REGISTRY_BUSY (the registry's own call quota, FSC 5 a minute, shared with the nightly re-checks, has no call left; nothing was asked), FSC_LICENCE_CODE_REQUIRED (an FSC certificate recorded by a code other than its licence code FSC-C######; nothing was asked). A `holderName` in the body is compared and shown, and recorded as typed: it never marks the certificate verified; only the certificate's own subject party can.\n\nScope: master.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/cooperatives — List the workspace's cooperatives",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/cooperatives",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "cooperatives"
              ],
              "variable": [],
              "query": []
            },
            "description": "The workspace itself when it is a cooperative (the Cooperative Plan, one of its own sites is a cooperative, or it has members; id `workspace`), then the parties that own a cooperative site or have members, each with its live sites and the members whose membership is open now.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/cooperatives/candidates — Parties that could become a cooperative",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/cooperatives/candidates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "cooperatives",
                "candidates"
              ],
              "variable": [],
              "query": [
                {
                  "key": "q",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Live parties of a kind that can have members (not a carrier, a certifier or a family farmer) that are not a cooperative yet (no cooperative site, no members) in name order, a page at a time; `workspace` says whether this workspace itself is not listed as one yet. A party becomes a cooperative when it gets its first member.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/cooperatives/{id}/totals — A cooperative's consolidated totals over a period",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/cooperatives/:id/totals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "cooperatives",
                ":id",
                "totals"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "from",
                  "value": "",
                  "disabled": true,
                  "description": "Start of the period, inclusive. Omitted: since the first record."
                },
                {
                  "key": "to",
                  "value": "",
                  "disabled": true,
                  "description": "End of the period, exclusive. Omitted: now."
                },
                {
                  "key": "timeZone",
                  "value": "",
                  "disabled": true,
                  "description": "IANA time zone the months of `received.byMonth` are cut in. Omitted: the workspace’s time zone, or UTC."
                }
              ]
            },
            "description": "`{id}` is a party, or `workspace` for this workspace itself. Members (at the end of the period, joined and left in it), their plots, what the cooperative received in `[from, to)` (its own transfers between its sites are not received again): per unit, per member and per month, in the lot’s stored unit (kg for a mass), and, now, the lots holding a balance at its sites and the open receipt-divergence and conflict items on its trips and lots. Every number is one aggregate over the records themselves.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/cooperatives/{id}/members — A cooperative's members",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/cooperatives/:id/members",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "cooperatives",
                ":id",
                "members"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "scope",
                  "value": "",
                  "disabled": true,
                  "description": "`active` (default): open memberships. `all`: former members too."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                },
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                }
              ]
            },
            "description": "A page of members in name order, each with its membership period and live plots. `scope=all` adds former members.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/cooperatives/{id}/members — Add a member to a cooperative",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/cooperatives/:id/members",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "cooperatives",
                ":id",
                "members"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Opens a membership from `joinedAt` (default now; never in the future). The member must be a producer of the workspace (supplier, sub-supplier, processor, smallholder) that has not withdrawn its consent to be traced; a pair has at most one open membership, and a rejoin starts on or after the last leave.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"memberPartyId\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/cooperatives/{id}/members/{membershipId}/end — End a member's membership",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/cooperatives/:id/members/:membershipId/end",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "cooperatives",
                ":id",
                "members",
                ":membershipId",
                "end"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "membershipId",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Closes an open membership at `leftAt` (default now; never in the future, never before joining). Deliveries received while it was open stay the member’s.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/batches/{id}/lacey-summary — Generate a lot's Lacey Act declaration summary for the customs broker",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/lacey-summary",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "lacey-summary"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Builds the PPQ 505 lines the chain of custody answers (genus and species from the lots, country of harvest from the production plots those lots came from, the quantity when the chain can state it, the origin lots) and returns them with the file (`pdf` or `csv`, base64). Species and country are paired by origin lot, never crossed. Fields that belong to the importer of record (entry number, port, EIN, entered value, percent recycled, the due-care statement) are left blank for the broker, who files the declaration. `gaps` names what the chain could not answer. Species and country are never paired where the chain did not pair them: an origin lot with several species and several countries lists its countries as `unpairedCountries` with the gap `species_country_pairing_ambiguous`. Each generation is recorded in the audit trail with the SHA-256 of the file. PPQ 505 is required for wood only: any other commodity is refused with 422 and the reason in `detail` (`LACEY_NOT_A_PLANT_PRODUCT` cattle, `LACEY_OUT_OF_SCOPE_FOOD_CROP` soy, `LACEY_EXEMPT_COMMON_CULTIVAR` palm oil, `LACEY_NO_CURRENT_OBLIGATION` cocoa, coffee and rubber).\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/batches/{id}/buyer-pack — Generate a lot's buyer audit pack",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/buyer-pack",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "buyer-pack"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "locale",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Builds the lot’s chain of custody (the lot and every lot upstream of it, with their stages, trips, receipts and the transformations that made them), its documents and its certificates, as the `buyer` audience may see them under the company’s visibility policy: a field or section the policy closes to buyers is absent, and a natural person’s name never appears. Returns a PDF, printed in `locale` (the seven launch languages; English when omitted), and a JSON manifest listing the SHA-256 of the PDF and of each shared document, both base64, with `sections` telling the company what the pack carries. Each generation is recorded in the audit trail with both hashes.\n\nScope: custody.write"
          }
        },
        {
          "name": "GET /custody/batches/{id}/units — List a lot's units",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/units",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "units"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Units per page, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                },
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                }
              ]
            },
            "description": "The lot's units in the order they were made (then by serial), a page at a time, with the total. Each carries its public page (`pageUrl`) and, with a GTIN, its GS1 Digital Link; both open the unit's page, so treat them like the printed mark.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/batches/{id}/units — Generate serialised units under a lot",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/units",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "units"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Makes `count` units (1 to 10 000) under the lot (a door, a sack, a box, a bale each) with a serial that is the GS1 AI (21) value printed on the piece: `random` (12 characters of Crockford base32 from a cryptographic source, after an optional prefix of up to 8 characters) or `sequential` (the prefix and a zero-padded number, going on from the last number used under that prefix). With a `gtin`, each unit also gets a GS1 Digital Link, and a serial is unique across every lot of that GTIN; without, unique within the lot.\n\nA lot counted in pieces holds no more units than it has pieces. Each unit has its own public page, opened by a key that is never the serial (listed by `GET …/units`). Once a lot has units, its quantity, unit and commodity are fixed and it cannot be deleted.\n\nIdempotent by `requestId`: the same request again answers `replayed` with what the first made; the same id with a different request is refused with 409.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"count\": 1,\n  \"requestId\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/units — List every unit of the workspace",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/units",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "units"
              ],
              "variable": [],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id."
                }
              ]
            },
            "description": "Every unit of the workspace's live lots, in `(updatedAt, id)` order (a unit's `updatedAt` moves when its status does) with its lot. The feed an ERP syncs from with `updatedSince`; a lot's own units, in the order they were made, are at `GET /custody/batches/{id}/units`.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/units/{id} — Read one unit",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/units/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "units",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": []
            },
            "description": "Scope: custody.read"
          }
        },
        {
          "name": "GET /custody/batches/{id}/auditor-opinions — The auditors’ opinions on a lot, every version",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/auditor-opinions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "auditor-opinions"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "shape",
                  "value": "",
                  "disabled": true,
                  "description": "`v2`: answer `{ data, pageInfo }` only, the shape of every newer list. Omitted: the shape this list answered before (kept for its clients), plus `pageInfo`."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "The `pageInfo.nextCursor` of the previous page."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size, 1 to 200 (default 50)."
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "Incremental sync: only what was written or changed at or after this instant, read from a 5-minute overlap before it because writes commit out of timestamp order; dedupe by id. An opinion is never edited: a replacement is a new version, and it is listed."
                }
              ]
            },
            "description": "Every opinion an auditor signed on the lot under an engagement, newest first, including the ones replaced, each marked `replaced` with the opinion that replaced it; a replacement carries the reason. An opinion is never edited or deleted. Each one carries its fingerprint (sha256 over its fields), the platform’s detached signature over that fingerprint, and `integrity`: whether the fingerprint recomputed from the opinion as stored now still matches, whether the audit trail attests the same fingerprint, and whether the signature verifies. At most 200 versions; `truncated` says when there are more.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/gov-documents/support — Whether a government document can be fetched by its number here",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/gov-documents/support",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "gov-documents",
                "support"
              ],
              "variable": [],
              "query": []
            },
            "description": "The kinds (`dof`, `gf`, `gta`) this deployment can fetch from the registry by number. Off by default: the registries need credentials the workspace holds, and without them a document is imported from its export.\n\nScope: custody.read"
          }
        },
        {
          "name": "GET /custody/gov-documents — List imported government documents",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/gov-documents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "gov-documents"
              ],
              "variable": [],
              "query": [
                {
                  "key": "status",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "order",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "transportId",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                }
              ]
            },
            "description": "Imported documents in (updatedAt, id) order, without their check (read one with GET …/{id}). `order=asc` (the default, a sync's order: oldest change first) or `order=desc` (newest first, what a person scrolling wants); a cursor continues only the order that produced it. `status=pending` is what still waits for a decision; `transportId` keeps the documents linked to a transport. Pass the previous page's `nextCursor` as `cursor` to continue; a cursor this API did not produce is refused with 400.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/gov-documents — Import a government transport document",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/gov-documents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "gov-documents"
              ],
              "variable": [],
              "query": []
            },
            "description": "A DOF or GF (Sinaflor / SISFLORA) or a GTA (GTA-e), sent as the exported file’s bytes (`contentBase64`: decoded by the XML prolog’s declared encoding, UTF-8 or ISO-8859-1, and hashed as exported), as text already decoded (`content`: pasted XML, or JSON in the normalised shape; text carrying U+FFFD is refused as mis-decoded) or, where the lookup is on, fetched by `lookup` (kind, number, issuing state). The answer carries what was read, what the workspace’s records propose for it (the vehicle by plate, the places, a lot per species) and, when it matches a transport already recorded, the fields that differ. Idempotent by the document’s identity (kind, state, series, number): a second import answers 200 with `replayed: true` and the first row untouched; `contentChanged` says the bytes differ. When the registry cannot be read nothing is written: 502.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/gov-documents/{id} — Read a government document and its check",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/gov-documents/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "gov-documents",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "The document as read, the proposal (vehicle, places, a lot per line) and, against the transport it is checked with, every difference: plate, origin, destination, species, quantity, unit, validity. While pending and checked against no transport, `candidates` lists the transports it could describe.\n\nScope: custody.read"
          }
        },
        {
          "name": "POST /custody/gov-documents/{id}/match — Check a pending document against another transport",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/gov-documents/:id/match",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "gov-documents",
                ":id",
                "match"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Points the pending document at one of the workspace’s transports (`transportId`) (the differences are computed against it) or at none (`null`), to create a transport from it instead.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"transportId\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/gov-documents/{id}/confirm — Create the transport from the document, or decide every difference",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/gov-documents/:id/confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "gov-documents",
                ":id",
                "confirm"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Checked against no transport: creates one through the transport engine: the document’s plate (or `vehicleId`; `registerVehicle: true` registers the plate, which needs the vehicle register’s write), its origin and destination, a load per line on the lot proposed or named in `items`, the route between the places. Checked against a transport: every difference needs a decision in `resolutions`: `accept` records it, `fix` brings a planned transport’s header to the document’s value; a load is never fixed. Idempotent: applied again answers `replayed: true` with nothing written.\n\nScope: custody.write",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custody/imports — Import records in bulk (CSV or JSON)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/imports",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "imports"
              ],
              "variable": [],
              "query": []
            },
            "description": "Sites, vehicles, drivers, transports, receipts, transformations and units, and the origin base: parties (a CNPJ checked by its digits), plots (a polygon checked by PostGIS, or a point with its declared area) and lots naming them by `#key`, from CSV or JSON files. Answers 202 with the job; poll `GET /v1/custody/imports/{id}`. All or nothing: a job that ends `invalid` wrote NOTHING, and its `errors` list every problem by file, line and column in the asked language. The same upload sent again after it committed answers with that job (`replayed: true`) and writes nothing.\n\nScope: bulk.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"files\": [\n    {\n      \"entity\": \"parties\",\n      \"format\": \"csv\",\n      \"content\": \"\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custody/imports/{id} — Read an import job",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/imports/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "imports",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": [
                {
                  "key": "locale",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Scope: bulk.write"
          }
        },
        {
          "name": "POST /custody/imports/{id}/commit — Commit a previewed import",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/imports/:id/commit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "imports",
                ":id",
                "commit"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": [
                {
                  "key": "locale",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Writes a `ready` preview, all or nothing. Answers 202; poll the job. A job that is not `ready` is refused with 409.\n\nScope: bulk.write"
          }
        },
        {
          "name": "GET /custody/imports/{id}/errors — Download an import’s error report (CSV)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/imports/:id/errors",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "imports",
                ":id",
                "errors"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": ""
                }
              ],
              "query": [
                {
                  "key": "locale",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Scope: bulk.write"
          }
        },
        {
          "name": "GET /custody/exports/{entity} — Export one kind of record (CSV or JSON)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/exports/:entity",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "exports",
                ":entity"
              ],
              "variable": [
                {
                  "key": "entity",
                  "value": "",
                  "description": ""
                }
              ],
              "query": [
                {
                  "key": "format",
                  "value": "",
                  "disabled": true,
                  "description": "`geojson` and `kml` are for `plots` only (400 otherwise)."
                },
                {
                  "key": "batchId",
                  "value": "",
                  "disabled": true,
                  "description": "Units only: the lot whose units to export (required for `units`)."
                }
              ]
            },
            "description": "Every live record of the kind, up to 10 000, as a file. Parties, plots, lots, sites, vehicles and drivers come in the import’s own shape (`key` is the id, and a reference between parties, plots and lots is `#<id>`), so an export imports back; plots also as GeoJSON or KML (`format=geojson|kml`). Drivers’ document numbers are whole from `master_data:approve` and masked below it. Units need `batchId`. Transports, receipts, transformations and sites carry their custom values for a caller holding `custom-fields.read` while custom fields are on: in CSV one `cf:<key>` column per field (a money field adds `cf:<key>:currency`; choices joined with `|`), in JSON a `customFields` object.\n\nScope: custody.read"
          }
        }
      ]
    },
    {
      "name": "Custom fields",
      "item": [
        {
          "name": "GET /custom-fields — List the workspace’s custom fields",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-fields",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-fields"
              ],
              "variable": [],
              "query": [
                {
                  "key": "recordKind",
                  "value": "",
                  "disabled": true,
                  "description": "The kind of record a field sits on: the chain-of-custody records `batch`, `event`, `transport`, `receipt` and `transformation`, and the master data `site`, `plot` and `party`. Values on `transport`, `receipt`, `transformation` and `site` also need the chain-of-custody programme."
                },
                {
                  "key": "status",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Every field of the workspace, or of one record kind, in its display order, with the current version flattened. Retired fields are included unless `status=active`. Read the whole catalogue: it is bounded by the plan.\n\nScope: custom-fields.read"
          }
        },
        {
          "name": "POST /custom-fields — Create a custom field",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-fields",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-fields"
              ],
              "variable": [],
              "query": []
            },
            "description": "A new field, version 1, visible to the company only; open it with `PUT /custom-fields/{id}/visibility`. `key`, `type` and `recordKind` never change. Recorded in the audit trail as `custom_field.created`.\n\nScope: custom-fields.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"recordKind\": \"batch\",\n  \"key\": \"\",\n  \"labels\": {},\n  \"type\": \"number\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custom-fields/{id} — Read a custom field",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-fields/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-fields",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "One field with every version, oldest first.\n\nScope: custom-fields.read"
          }
        },
        {
          "name": "PATCH /custom-fields/{id} — Revise a custom field",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-fields/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-fields",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "A new version with the labels, help, requiredness, config or scope given (`custom_field.revised`). Values already stored keep the version they were captured under. A body that changes nothing answers the field as it is.\n\nScope: custom-fields.manage",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custom-fields/{id}/retire — Retire a custom field",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-fields/:id/retire",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-fields",
                ":id",
                "retire"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "A new version, retired: the field leaves the forms; every value already stored stays and stays visible. Frees a plan slot. Retiring a retired field changes nothing.\n\nScope: custom-fields.manage",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /custom-fields/{id}/restore — Restore a retired custom field",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "A fresh key per request; re-send the SAME key to retry safely."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-fields/:id/restore",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-fields",
                ":id",
                "restore"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "A new version, active again. Counts against the plan’s active-field limit, as a create does.\n\nScope: custom-fields.manage",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "PUT /custom-fields/{id}/visibility — Set who sees a custom field",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-fields/:id/visibility",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-fields",
                ":id",
                "visibility"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Who sees the field besides the company (`auditor`, `importer`, `buyer`, `public`), and whether field roles fill it. Live: closing bites on the next read everywhere, including on values captured long ago. Adding an audience needs the Growth plan and custom fields on; removing one is allowed on every plan and with custom fields off. `public` is refused for money and text fields. Recorded as `custom_field.visibility_changed`.\n\nScope: sharing.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"audiences\": []\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "PUT /custom-fields/order — Order a record kind’s custom fields",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-fields/order",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-fields",
                "order"
              ],
              "variable": [],
              "query": []
            },
            "description": "The fields listed first, in the order given, then any field of the kind not listed, in its current order (`custom_field.reordered`).\n\nScope: custom-fields.manage",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"recordKind\": \"batch\",\n  \"fieldIds\": [\n    \"\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custom-field-values — List and filter custom values",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-field-values",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-field-values"
              ],
              "variable": [],
              "query": [
                {
                  "key": "recordKind",
                  "value": "",
                  "disabled": true,
                  "description": "The kind of record in a values path (every kind a field can sit on)."
                },
                {
                  "key": "field",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "eq",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "in",
                  "value": "",
                  "disabled": true,
                  "description": "Comma-separated values."
                },
                {
                  "key": "gte",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "lte",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "isSet",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "currency",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                }
              ]
            },
            "description": "The records of one kind with their current custom values, in `(updatedAt, recordId)` order: the feed an external system syncs from.\n\nName a `field` to keep only the records holding it, and filter on it with one of `eq`, `in` (comma-separated) or `isSet`, or a `gte` / `lte` range: numbers and money amounts as decimal strings, dates as `YYYY-MM-DD`, choices by key, yes/no as `true` / `false`; `currency` narrows a money field. A filter a type cannot take is 422 `custom_field_invalid`.\n\nScope: custom-fields.read"
          }
        },
        {
          "name": "GET /custom-field-values/export — Export one record kind’s custom values (CSV or JSON)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-field-values/export",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-field-values",
                "export"
              ],
              "variable": [],
              "query": [
                {
                  "key": "recordKind",
                  "value": "",
                  "disabled": true,
                  "description": "The kind of record in a values path (every kind a field can sit on)."
                },
                {
                  "key": "format",
                  "value": "",
                  "disabled": true,
                  "description": "Default `csv`."
                }
              ]
            },
            "description": "One row per current value (`recordKind, recordId, key, type, value, unit, currency, fieldVersion, updatedAt`), retired fields included, cleared values left out. A money value is its amount, its currency in `currency`; choices are joined with `|` in CSV. Up to 10 000 rows; beyond, page them with `GET /custom-field-values`. Recorded in the audit trail as `custody_export.generated`.\n\nScope: custom-fields.read"
          }
        },
        {
          "name": "GET /custom-field-values/{recordKind}/{recordId} — Read a record’s custom values",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-field-values/:recordKind/:recordId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-field-values",
                ":recordKind",
                ":recordId"
              ],
              "variable": [
                {
                  "key": "recordKind",
                  "value": "",
                  "description": "The kind of record in a values path (every kind a field can sit on)."
                },
                {
                  "key": "recordId",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "",
                  "disabled": true,
                  "description": "The language of the labels; else the first `Accept-Language` tag this API speaks, else the workspace’s default."
                }
              ]
            },
            "description": "The record’s current values, every field (retired ones included), labelled in `lang`, else the first `Accept-Language` tag this API speaks, else the workspace’s language. A cleared value is absent.\n\nScope: custom-fields.read"
          }
        },
        {
          "name": "PATCH /custom-field-values/{recordKind}/{recordId} — Set or clear a record’s custom values",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-field-values/:recordKind/:recordId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-field-values",
                ":recordKind",
                ":recordId"
              ],
              "variable": [
                {
                  "key": "recordKind",
                  "value": "",
                  "description": "The kind of record in a values path (every kind a field can sit on)."
                },
                {
                  "key": "recordId",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": []
            },
            "description": "Writes the values given (`null` clears), each judged against its field’s definition as of `customFieldsAsOf` and against the record as stored. Every write is kept: a change is a new value, and the history lists both. Recorded in the audit trail as `custom_field.values_set` (fingerprints only, never the value) and announced as the `custom_field.values_changed` webhook.\n\n`Idempotency-Key` is the request id: the same key again answers what the first write stored (`replayed: true`) and writes nothing. `basedOn` names the value you last saw; a mismatch is not refused: the write is kept and the field is flagged as changed on two devices. A required field cannot be cleared.\n\nScope: custom-fields.write",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"values\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /custom-field-values/{recordKind}/{recordId}/history — The history of a record’s custom values",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custom-field-values/:recordKind/:recordId/history",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custom-field-values",
                ":recordKind",
                ":recordId",
                "history"
              ],
              "variable": [
                {
                  "key": "recordKind",
                  "value": "",
                  "description": "The kind of record in a values path (every kind a field can sit on)."
                },
                {
                  "key": "recordId",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "field",
                  "value": "",
                  "disabled": true,
                  "description": "Only this field’s writes."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "updatedSince",
                  "value": "",
                  "disabled": true,
                  "description": "ISO 8601 timestamp with timezone offset"
                }
              ]
            },
            "description": "Every write of the record’s values, newest first: who, when (the device’s time and the server’s), under which version of the field, and whether it was cleared, written over another device’s value, or erased at the data subject’s request. `updatedSince` keeps the writes recorded since (5-minute overlap).\n\nScope: custom-fields.read"
          }
        },
        {
          "name": "GET /custody/batches/{id}/custom-fields/upstream — Everything upstream of a lot, with its custom values",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/custody/batches/:id/custom-fields/upstream",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "custody",
                "batches",
                ":id",
                "custom-fields",
                "upstream"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Opaque entity id (cuid per SA-001; UUID accepted)"
                }
              ],
              "query": [
                {
                  "key": "maxDepth",
                  "value": "",
                  "disabled": true,
                  "description": "How many steps upstream to walk, 1 to 20 (default 12)."
                },
                {
                  "key": "lang",
                  "value": "",
                  "disabled": true,
                  "description": "The language of the labels; else the first `Accept-Language` tag this API speaks, else the workspace’s default."
                }
              ]
            },
            "description": "The cost-per-door helper: the lot and every lot upstream of it (by parent links and transformations, the walk the chain verification makes), and the transports, receipts and transformations that touched them, each with the company’s custom values, quantities and units. Lots deepest first, then trips, receipts and transformations in the order they were recorded. At most 2 000 nodes; `truncated` says when the walk was cut.\n\nOrigonex computes nothing from it: allocate the costs by your own rule and write the result back with `PATCH /custom-field-values/batch/{id}`. Needs `custody.read` besides `custom-fields.read`.\n\nScope: custom-fields.read"
          }
        }
      ]
    }
  ]
}