API surface. Generation, ingestion, document reads and document jobs are REST. Template and label-spec authoring is GraphQL Interface. Shippers read label specs, certify and allocate tracking ranges over REST.
Some carrier labels come from carrier APIs. Everything else is generated by somebody, and often generated by one system and ingested by another: packing slips, commercial invoices, CN23s, pick lists, gaylord and ULD tags, CMR notes, air waybills, delivery receipts. Carrier, Network, 3PL and Fleet implementers also print their own shipping labels. This module standardizes how those documents are templated and produced, so a template written against the spec’s objects works on any implementer. It follows Karrio’s document templates (a template bound to a related object, rendered on demand) and extends them with versioning, typed contexts, barcode helpers, triggers and batch output.

1. Document template

Template language (normative for liquid)

  • Liquid syntax, with the context root named after related_object (shipment, manifest…), plus account (profile and settings), now, and locale.
  • Formatting filters: money (localized minor units: {{ purchase.amount | money }}), weight and dimensions (with an optional unit conversion: {{ hu.weight | weight: 'lb' }}), date (locale-aware), address (multi-line, using the country’s postal format).
  • Barcode filters, which output an inline SVG or image: barcode: 'code128' | 'gs1_128' | 'ean13' | 'itf14' | 'datamatrix' | 'gs1_datamatrix' | 'qr' | 'pdf417' | 'maxicode'. sscc formats a GS1-128 SSCC with the (00) application identifier.
  • Declaring expansions: {% expand shipment.handover.manifest, shipment.handling_units.items.product %} at the top of the template. Only declared expansions are fetched, so rendering stays bounded and fast.
  • Sandboxed. No network access, no file access beyond assets, and a time and size limit (5 seconds, 10 MB). Templates are data, so implementers MUST render them in isolation.
  • Missing fields render as empty, never as an error. {% required shipment.customs_declaration %} makes rendering fail with 422 template_data_missing when the shipment does not carry the data, which suits documents that must be complete, such as customs paperwork.

ZPL templates

engine: "zpl" takes ZPL II source with the same Liquid variables and filters. Barcode filters output native ZPL commands (^BC, ^BX, ^BQ). Output is ZPL text, or a PNG or PDF rendered from it, at the template’s DPI (203 or 300).

2. Endpoints for templates

validate checks the template’s field references against the JSON Schema of related_object (from openapi.yaml) and flags references to fields that do not exist. Templates written against a previous spec version are caught before they print blanks. The spec ships a set of reference templates (portable Liquid and ZPL) for packing slips, commercial invoices, CN23s, gaylord and ULD tags, and a 4x6 label, which implementers install as defaults with system_metadata.generated: true.

3. Generating documents

The response is a document:
template_version and sha256 make a printed document reproducible and verifiable. With data network replication, the document is also recorded as a signed record (chapter 23 §3). Other document endpoints: GET /v1/documents/{id} (re-signs url), GET /v1/documents?related.id=shp_…&type=…, DELETE /v1/documents/{id} (only standalone documents; attached documents follow their owner’s retention).

4. Triggers

A template with triggers[] renders automatically when a matching event fires:
  • Trigger rendering happens before shipment.purchased is delivered where possible, so the webhook payload already carries the document. Otherwise document.generated follows it.
  • A trigger failure never fails the purchase. It emits document.generation_failed with the template errors.
  • Typical triggers: packing slip on shipment.purchased, commercial invoice on shipment.purchased when customs_declaration.paperless_trade is false, gaylord tag on consolidation.closed, delivery receipt on tracker.delivered, manifest summary on manifest.submitted.

5. Batch generation and merged output

  • objects takes ids (up to 5000), or query (a search query from chapter 10 §3, such as every purchased shipment in close group dock-a today).
  • output is merged (one file, in sort order, which suits a print run) or individual (one document per object, attached if attach is true).
  • A document_job has status (processing, completed, completed_with_errors), counts, result_file (the merged file) and errors[]. It emits document_job.completed.
  • Labels can be merged the same way: { "type": "label", "output": "merged" } merges the carrier labels already on the shipments, without re-rendering them. This is the end-of-day “print all labels” operation.

6. Carrier label templates and certification

Many carriers (postal operators, regional parcel networks, and LTL carriers’ bills of lading) do not return labels from an API. They publish a label specification, and shippers must print compliant labels themselves. Others want every platform to render their label identically. In Carrier and Network modes, the carrier publishes both the template and the machine-checkable rules.
  • template is a normal document_template (type: "label") owned by the carrier. Its services reference it in the catalog: service.label.template (chapter 14 §1).
  • tracking_numbers declares how shipper-side generation gets tracking numbers. range means ranges allocated through POST /v1/label_specs/{id}/tracking_ranges with { count }. api means numbers are fetched per shipment.
  • Offline label generation. When a shipment’s service has a label spec, a universal implementer renders the label from the carrier’s template, using a number from the allocated range. No carrier call is made at purchase. Shipment data reaches the carrier later, through the manifest (chapter 05), which is how most postal and regional networks work.
  • Certification. POST /v1/label_specs/{id}/certify with { "samples": ["doc_…"] } (rendered labels, or photos of printed ones) checks required fields, barcode decodability and payload schema, zone placement and quiet zones. It returns { result: "pass" | "fail", findings: [...] }. A carrier with certification.required: true rejects live shipments from accounts that have not passed (422 carrier_error / label_not_certified).
  • Carriers author specs over GraphQL (createLabelSpec, updateLabelSpec, publishLabelSpec). Shippers use REST: GET /v1/label_specs, GET /v1/label_specs/{id}, POST /v1/label_specs/{id}/certify and POST /v1/label_specs/{id}/tracking_ranges.
The same pattern covers every regulated or carrier-defined document: a carrier’s bill of lading (VICS BOL for LTL), CMR, air waybill (IATA layout), CN22/CN23 (UPU layout), and commercial invoice. Each is a template plus, where a counterparty must accept it, a spec that can be certified.

7. Document ingestion

Systems also receive documents they did not create: a supplier’s commercial invoice PDF, a forwarder’s bill of lading, a carrier invoice, a scanned proof of delivery, a customs release notice, a certificate of origin. Ingestion turns them into structured data on the right objects.
  • Structured formats (EDI, UBL, XML, JSON) are parsed deterministically and never need review. Carrier invoices in those formats go straight to invoice import (chapter 28 §1).
  • The original file is always kept as a document attached to the target object, so the extraction can be audited against its source. With data network replication, it is also recorded as a signed record.
  • Never auto-apply to legal records. Fields of a customs declaration on a purchased shipment are never changed by ingestion. Mismatches are reported in messages[] as document_mismatch, for a person to resolve.
Endpoints: POST /v1/documents/ingest, GET /v1/document_ingestions, GET /v1/document_ingestions/{id}, POST /v1/document_ingestions/{id}/apply with { corrections?: { field: value } }, POST /v1/document_ingestions/{id}/reject. Batch ingestion runs as a job (chapter 27, type: "documents.ingest").

8. Events

document_template.created, document_template.published, document_template.retired, document.generated, document.generation_failed, document_job.completed, label_spec.published, label_spec.certification_passed, label_spec.certification_failed, document_ingestion.needs_review, document_ingestion.applied, document_ingestion.failed.