Imported from orbit-logistics/orbit-skills (
plugins/orbit-document-templates/skills/orbit-document-templates/SKILL.md). Install upstream withnpx skills add orbit-logistics/orbit-skills --skill orbit-document-templates. Copyright stays with the author.
Orbit Document Templates
Generate logistics paperwork — delivery notes, loading lists, labels, CMR consignment notes, invoices — as PDFs, straight from live Orbit data, through the public REST API.
A document template is a reusable recipe: HTML with placeholders. Rendering it against real
Tour/Shipment/Order/Carrierdata produces a document — a stored, versioned PDF that you can attach to those entities. The template is the recipe; the document is the meal.
This skill covers the complete Orbit-side workflow: designing a template, rendering it, downloading the result, attaching it to entities, and versioning it. It does not run a customer's automation platform for them — for triggering generation automatically it gives you the wiring pattern (an Orbit webhook into an automation tool) and stops there.
Step 0 — always establish the environment first
Every call needs the right base URL and the API key that belongs to that environment. A staging key against production (or the reverse) fails, and worse, a production run creates real customer paperwork. Before any API call, confirm with the user which environment they mean and never assume.
| Environment | Base URL |
|---|---|
| Production | https://api.orbit.do/v5 |
| Staging | https://api.staging.orbit.do/v5 |
- Authentication is the header
X-API-KEYwith the key's literal value (it is not aBearertoken). Keys are created and managed in Orbit MissionControl under Settings. - Treat the key as a secret: read it from an environment variable, never hard-code it into a file
you save, never print it back to the user, and never commit it. The bundled script reads it from
ORBIT_API_KEY. - A key is scoped to one tenant and one environment. If a user hands you a key, ask which environment it is for rather than guessing from the value.
Set up your session once:
export ORBIT_API_KEY="<the customer's key>"
export ORBIT_BASE_URL="https://api.staging.orbit.do/v5" # or the production URL
The mental model
Three objects, kept distinct — confusing them is the most common mistake:
- DocumentTemplate — the recipe. A
name, a set of data objects it may read (which entity types it pulls from), a localised file-name template, and localised HTML content written in Liquid. There is no "type" field on a template — "delivery note" versus "invoice" is expressed through the template'snameand, at render time, a free-textdocumentTypeon the link. Do not look for atypeproperty; it does not exist. - Document — the output. A stored PDF with a
name, a storagekey, afileType, afileSize, and aversionsarray. It back-links to the template it came from. - DocumentLink — the attachment. What ties a document to a business entity (a specific tour, shipment, order, …), in a slot, with per-role visibility rules. One document can be linked to several entities.
A subtle but important distinction: a template's data objects (what it reads) are limited to
order, shipment, tour, carrier, carrier-user, carrier-team. A link's target entity
(what the output is attached to) can be a much wider set — tour, shipment, order, carrier,
vehicle, shipper, load, and more.
The end-to-end workflow
- Design the template (Liquid HTML). Do this in MissionControl's template editor when a human
is involved — it has a live preview — or create it through the API. For the Liquid variables,
filters, barcodes, and localisation rules, read
references/liquid.md. - Render it for a specific entity.
POST {base}/documenttemplates/{templateId}/renderwith adataObjectIdsmap (one id per data object the template declares) and alanguage. This stores a PDF and returns its metadata — includingdocument.key. - Download the PDF. The render response does not contain a URL or the bytes. Take
document.keyand callPOST {base}/storage/downloadto get a presigned URL valid for 15 minutes. - Attach it (optional). Pass
documentLinksin the render call to link the PDF to a tour / shipment / order in one shot, with visibility per role. - Regenerate as a new version (optional). Pass
upsert: truewith exactly one link to append a new version to the existing document instead of piling up duplicates.
The full request/response shapes, every field, pagination, and the retrieval endpoints are in
references/api.md — read it before constructing any payload so you use the exact field names.
A minimal render + download, using the bundled helper (it reads ORBIT_API_KEY and ORBIT_BASE_URL
from the environment and prints the download URL):
scripts/render_and_download.sh \
--template <templateId> \
--language de \
--data '{"tour":"<tourId>","order":"<orderId>"}'
Or by hand, the two calls:
# 1) render → capture document.key
curl -sS -X POST "$ORBIT_BASE_URL/documenttemplates/<templateId>/render" \
-H "X-API-KEY: $ORBIT_API_KEY" -H "Content-Type: application/json" \
-d '{"dataObjectIds":{"tour":"<tourId>"},"language":"de"}'
# 2) download → presigned URL (pass document.key exactly as returned)
curl -sS -X POST "$ORBIT_BASE_URL/storage/download" \
-H "X-API-KEY: $ORBIT_API_KEY" -H "Content-Type: application/json" \
-d '{"key":"<document.key from step 1>"}'
Know the logistics documents
Customers do not ask for "a template" — they ask for a Lieferschein, a Rollkarte, a CMR, a label.
Knowing which Orbit data each of those needs, and the conventions that trip people up, is most of
the value here. references/use-cases.md is a catalogue of the common logistics documents, the data
objects each one reads, and the gotchas (a label needs barcode filters; a CMR spans consignor,
carrier and consignee; a loading list iterates a tour's stops and loads). Read it whenever the user
names a real-world document rather than an abstract template.
Triggering generation automatically (the wiring pattern)
Orbit does not generate persisted documents on its own — there is no built-in "on tour dispatched, make the delivery note" switch. Persisted generation happens when something calls the render endpoint. In practice that "something" is the customer's own automation tool.
The recommended pattern is a webhook into an automation platform (n8n is the tool Orbit customers most commonly use, but any tool with a webhook trigger and an HTTP request step works):
- In MissionControl → Settings → Webhooks, subscribe to the lifecycle event that should trigger the document (for example, a shipment or tour reaching a given status).
- Point the webhook at the automation tool's inbound webhook URL.
- In the automation tool, an HTTP request step calls
POST {base}/documenttemplates/{templateId}/renderwith the ids from the webhook payload, and (usually)documentLinksso the PDF is attached back to the entity.
For the exact events, payloads, the one document-related webhook you can subscribe to
(document-link-action-created), and least-privilege key guidance, read references/automation.md.
Keep the how-to-run-n8n details to a pointer — this skill's job is the Orbit side.
Gotchas — don't assume
Each of these has bitten a real integration. Internalise them before you build.
- Don't assume the render response gives you the file — actually it gives you metadata plus a
storage
key. You must make a second/storage/downloadcall to get a (15-minute) URL. There is no GET-a-file endpoint. - Don't assume you strip or prefix the key — actually pass
document.keyexactly as returned. The download endpoint adds the internal prefix itself and rejects a key that already carries it. - Don't assume a template has a "type" — actually the kind of document lives in its
nameand the link's free-textdocumentType. Filter and label by those, not by a non-existent enum. - Don't assume you can supply any data object — actually you must supply an id for every data
object the template declares, and only from
order/shipment/tour/carrier/carrier-user/carrier-team. A missing or extra id is a 400. - Don't assume a template can be deleted via the API — actually there is no delete endpoint for templates. List, get, create, and update (via the actions endpoint) only. Deletion is a MissionControl action.
- Don't assume create is a plain POST — actually create and update both go through
POST .../{templateId}/actions, create requires the literal path segmentnew, and the body wraps its fields in apayloadobject ({"type":"create","payload":{…}}). A flat body is a 400. - Don't assume an over-long barcode throws — actually it renders a red "ERROR" placeholder in the PDF instead. Watch the caps: Code 128 ~2,000 chars, QR ~4,000, Data Matrix ~2,000.
- Don't assume you can reference any entity in the HTML — actually the API validates the Liquid at
save time and 400s if it doesn't parse or references an undeclared data object. The message is
precise (
[de] content: Variable "carrier" is not available.or a syntax error with line/col) — treat it as a fix-it loop: declare every data object your content reads. Seereferences/liquid.md. - Don't assume
font-family: Arialsilently degrades — actually web-safe names render as metric-compatible equivalents (Arial/Helvetica → Liberation Sans, Times New Roman → Liberation Serif, Courier New → Liberation Mono), and symbols like ▲ ▼ → ❄ render via automatic fallback. A template with NOfont-familyat all renders in a serif face — always set one onbody. Full font list and custom-font guidance:references/liquid.md. - Don't assume corporate fonts are unavailable — actually
@font-facewith a publicly reachable URL works and the font is embedded in the PDF. Keep a provided family in the stack as fallback. - Don't assume rendering fires a webhook — actually only creating a DocumentLink does
(
document-link-action-created). A render with nodocumentLinksproduces no webhook, so an automation that waits for one will hang. - Don't assume every timestamp is a date — actually Orbit stores times as epoch seconds, money as
eurocent, dimensions in cm, weight in kg, countries as ISO 3166-1 alpha-3. Format them in the
template with the provided filters; see
references/liquid.md.
Reference files
Read the one you need; each is self-contained.
references/api.md— every document-template, document, storage, and webhook endpoint on the API: methods, paths, exact request/response fields, pagination, the render→download flow, and auth. Read before building any payload.references/liquid.md— authoring templates: the Liquid variables exposed per data object, the date/time and barcode filters, the load deep-link filter, fonts and typography (available families, symbol fallback,@font-facefor corporate fonts), localisation and the language fallback, content validation, and how to discover an entity's available fields.references/use-cases.md— the logistics document catalogue: delivery note (Lieferschein), loading list (Rollkarte), shipment/parcel label, CMR consignment note, invoice, transport order, proof of delivery — with the data objects and gotchas of each.references/automation.md— the webhook-into-automation-tool trigger pattern, the subscribable document webhook, payload shape, and least-privilege key guidance. Pointer-level on the automation tool itself.