Base URL
https://vibe.holiostage.com/api/tms
Every path is under /v1/organizations/{organizationId}/tms. The organization must be the one your token is for.
Holio TMS API
One API in front of every carrier your organization has an agreement with. Look products up in the catalogue, price a shipment, book it with a label, order the pickup and follow it to the door. Web shops get checkout rates and order shipping on the same engine.
https://vibe.holiostage.com/api/tms
Every path is under /v1/organizations/{organizationId}/tms. The organization must be the one your token is for.
A Holio access token as Authorization: Bearer …, for one organization. Systems use a service account with the client-credentials grant.
Bookings, pickups and order shipments take an Idempotency-Key. Send the same key again after a timeout and you get the first answer, never a second shipment.
Every refusal is application/problem+json with a stable code such as tms_booking_unknown.
Bring, PostNord, DHL Freight, DB Schenker, DSV, Budbee, Porterbuddy and more, each through its own integration. The live list and what each supports are in the catalogue and the capability matrix.
Loading the OpenAPI document…
Five calls from nothing to a shipment with a label. Set two variables first:
export TMS=https://vibe.holiostage.com/api/tms/v1/organizations/$ORG/tms
export AUTH="Authorization: Bearer $TOKEN"
curl -s -H "$AUTH" "$TMS/catalogue/carriers"
curl -s -H "$AUTH" "$TMS/catalogue/products?carrierId=bring"
A product lists its rules (maximum weight and measurements, whether a pickup point is required) and its additional services.
curl -s -H "$AUTH" -H 'Content-Type: application/json' "$TMS/internal-quotes" -d @quote.json
The quote uses the price lists of your own carrier agreement. The agreementId is one of your organization's carrier agreements.
curl -s -H "$AUTH" -H 'Content-Type: application/json' "$TMS/internal-shipments/checks" -d @booking.json
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
-H "Idempotency-Key: order-1001-1" "$TMS/internal-shipments" -d @booking.json
The check runs the product's rules without booking. The booking answers the shipment with its id and tracking numbers.
curl -s -H "$AUTH" "$TMS/internal-shipments/$SHIPMENT/label"
The answer is a link valid for ten minutes. Fetch the file from it right away; don't store the link.
curl -s -H "$AUTH" "$TMS/internal-shipments/$SHIPMENT/tracking"
Every source's events, oldest first: the booking, the carrier's API and webhooks, reports and invoices.
Each call is in the reference with its full request and answer, and can be run in the console.
The TMS API takes a Holio access token: Authorization: Bearer <token>. The token says which organization it is for and which permissions it carries. The organization in the path must be that organization; an organization sent any other way is never trusted.
Use a confidential client with a service account and the OAuth 2.0 client-credentials grant, asking for the scope organization:<organization id>:
curl -s https://vibe-auth.holiostage.com/realms/holio/protocol/openid-connect/token \
-d grant_type=client_credentials \
-d client_id=$CLIENT_ID -d client_secret=$CLIENT_SECRET \
-d scope=organization:$ORG
Holio registers the client and the permissions it may use. Ask your Holio contact for one; tell us which organization and which of the calls in this reference you need.
An application acting for a person signs them in with Holio (OpenID Connect, authorization code with PKCE) and asks for the same organization: scope. The person's roles in the organization decide what the token may do.
| Permission | Lets the caller |
|---|---|
tms.shipment.book | book shipments and pickups, check bookings, read the catalogue |
tms.shipment.read | read shipments, labels, documents, tracking and pickups |
tms.shipment.manage | void shipments and cancel pickups |
tms.pricing.read | quote on own agreements (with tms.pricing.cost it sees the carrier's side) |
tms.checkout.quote | ask checkout rates and pickup points |
tms.orders.ship | ship and return web shop orders |
hol_…) are not accepted by the TMS API yet. Until they are, use a token as above.
Tokens live for minutes. Ask for a new one when the old one expires rather than storing it.
A booking, a pickup, an order shipment and a return each take a header Idempotency-Key, matching [A-Za-z0-9._:-]{1,100}. Choose it from your own data, such as your order number and a counter, so a retry after a crash sends the same key.
tms_idempotency_key_reused.tms_upstream_rejected); the same key may book again once you have fixed the request.booking_unknown (409 tms_booking_unknown). It is never sent again by itself: read the shipment list before booking with a new key.Reads, checks and quotes take no key: they change nothing and are always safe to repeat.
Every refusal is a problem document (application/problem+json, RFC 9457). Act on status and code; the English detail is for logs, not for your users.
{
"type": "about:blank",
"title": "Conflict",
"status": 409,
"detail": "A booking with this Idempotency-Key got no clear answer",
"code": "tms_booking_unknown"
}
Codes are a contract: new ones are added, none is ever renamed.
| Status | Code | What to do |
|---|---|---|
| 400 | tms_product_unknown | The product isn't in the carrier's catalogue; read the catalogue again |
| 400 | tms_pickup_point_unknown | The carrier has no such pickup point; let the receiver choose again |
| 400 | tms_label_format_unsupported | The carrier doesn't give labels in that format |
| 400 | tms_pickup_unsupported | The carrier takes no pickup orders through the API |
| 401 | — | No token, or it expired: get a new one |
| 403 | — | The token lacks the permission, or is for another organization |
| 404 | tms_label_unavailable | The carrier has no label for the shipment yet |
| 409 | tms_booking_unknown | No clear answer from the carrier; read before booking again |
| 409 | tms_idempotency_key_reused | The key was used with another body |
| 409 | tms_pickup_exists | The carrier already comes to that address that day |
| 409 | tms_product_not_offered | The product isn't offered for booking |
| 422 | tms_upstream_rejected | The carrier refused; nothing was booked. Its error codes are in the answer |
| 422 | tms_product_rules_failed | The shipment breaks the product's rules; findings lists them |
| 503 | tms_outcome_unknown | A change got no answer and may have been applied; read, then retry with the same key |
A web shop asks POST /checkout/rates with the cart and the delivery address, and shows what comes back. The delivery methods, their prices and rules are set up by the merchant in Holio, not in the request.
code (such as bring-3584, or with a pickup point appended). Keep it on the order: shipping the order later names the method by it.priceMinor is in the checkout's currency, VAT included. A request in another currency gets no rates; nothing is converted.skipped with a reason, for the merchant's own test page. Don't show them to the customer.selector, a rate stands for the method and the customer picks the point with GET /checkout/pickup-points?code=.A carrier that can't be asked in time (four seconds by default) gets the method's fallback price instead of no rate.
Once a customer has paid, ship the order with POST /order-shipments. Weights, customs and the method come from the order lines and the merchant's settings in Holio, not from free text a browser typed.
Idempotency-Key made from your order, for example myshop:1001:1.GET /order-shipments?externalOrderId=, and print again with reprint.POST /order-returns.Shopify shops use Holio's own Shopify app, which makes these calls. Build directly on them for other platforms.
Run calls against Vibe from your browser. The token stays in this tab's memory: it is never stored, and it is sent only to this site's own /api/tms/, which forwards it to the Holio gateway.
Checked from your browser now. Any answer from the TMS service, including 401 for a call without a token, means the path from the internet to the service is up.