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…), plusaccount(profile and settings),now, andlocale. - Formatting filters:
money(localized minor units:{{ purchase.amount | money }}),weightanddimensions(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'.ssccformats 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 with422 template_data_missingwhen 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 withtriggers[] renders automatically when a matching event fires:
- Trigger rendering happens before
shipment.purchasedis delivered where possible, so the webhook payload already carries the document. Otherwisedocument.generatedfollows it. - A trigger failure never fails the purchase. It emits
document.generation_failedwith the template errors. - Typical triggers: packing slip on
shipment.purchased, commercial invoice onshipment.purchasedwhencustoms_declaration.paperless_tradeis false, gaylord tag onconsolidation.closed, delivery receipt ontracker.delivered, manifest summary onmanifest.submitted.
5. Batch generation and merged output
objectstakesids(up to 5000), orquery(a search query from chapter 10 §3, such as every purchased shipment in close groupdock-atoday).outputismerged(one file, insortorder, which suits a print run) orindividual(one document per object, attached ifattachis true).- A
document_jobhasstatus(processing,completed,completed_with_errors),counts,result_file(the merged file) anderrors[]. It emitsdocument_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.templateis a normaldocument_template(type: "label") owned by the carrier. Its services reference it in the catalog:service.label.template(chapter 14 §1).tracking_numbersdeclares how shipper-side generation gets tracking numbers.rangemeans ranges allocated throughPOST /v1/label_specs/{id}/tracking_rangeswith{ count }.apimeans 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}/certifywith{ "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 withcertification.required: truerejects 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}/certifyandPOST /v1/label_specs/{id}/tracking_ranges.
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
documentattached 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[]asdocument_mismatch, for a person to resolve.
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.