RouteHut Developers

External API and webhooks

Build integrations for quotes, bookings, shipment updates and delivery evidence. For the operational process behind these endpoints, see RouteHut workflows.

Base URL https://api.routehut.example.com/api/v1

The address above is an example, not a public sandbox. Access requires a RouteHut environment and scoped credentials.

Download OpenAPI schema
Version
v1
Protocol
REST + JSON
Auth
Scoped API keys

Getting started

Authentication

Tenant administrators issue scoped credentials from the RouteHut integrations workspace. Send the credential as a bearer token. Raw keys are shown once, so store them in your own secret manager.

Scopes are deliberate.Use `shipments:read`, `shipments:write`, `tracking:read`, or `pod:read` only where the integration needs them.
HTTP
Authorization: Bearer opk_your_credential

Write safety

Idempotency

Every state-changing write requires an `Idempotency-Key`. Retry the identical request with the same key after a timeout; RouteHut returns the original response and sets `Idempotent-Replayed: true`. Keys remain replayable for 48 hours.

Do not reuse keys for different requests.RouteHut returns `409 idempotency_key_reused` when a key is paired with a changed payload.
POST/external/quotes

Calculate a shipment quote

Price a shipment for an active customer account before creating it. Quotes are calculations, not bookings: RouteHut does not create a shipment or address-book record.

Scope quotes:readNon-persistent
Coordinates make geographic rules available.Include latitude and longitude inside either address when zone, territory, or remote-area pricing matters. Without them, only non-geographic rate rules can match.
cURL
curl https://api.routehut.example.com/api/v1/external/quotes \
  -X POST \
  -H "Authorization: Bearer opk_your_credential" \
  -H "Content-Type: application/json" \
  --data '{
    "quote": {
      "customer_account_code": "ACME-RETAIL",
      "collection_address": { "line1": "1 Dock Road", "city": "Cape Town", "country_code": "ZA", "coordinates": { "latitude": -33.9249, "longitude": 18.4241 } },
      "delivery_address": { "line1": "2 Loop Street", "city": "Johannesburg", "country_code": "ZA" },
      "parcels": [{ "weight_kg": "2.5", "length_cm": "20", "width_cm": "15", "height_cm": "10" }]
    }
  }'
POST/external/shipments

Create a shipment

Create a draft shipment using the customer’s stable `customer_account_code`. Collection and delivery addresses become address-book records for that customer. Internal RouteHut IDs are never accepted.

Scope shipments:writeRequired header Idempotency-Key
cURL
curl https://api.routehut.example.com/api/v1/external/shipments \
  -X POST \
  -H "Authorization: Bearer opk_your_credential" \
  -H "Idempotency-Key: order-100045" \
  -H "Content-Type: application/json" \
  --data '{
    "shipment": {
      "customer_account_code": "ACME-RETAIL",
      "shipment_number": "ACME-100045",
      "collection_address": { "line1": "1 Dock Road", "city": "Cape Town", "country_code": "ZA" },
      "delivery_address": { "line1": "2 Loop Street", "city": "Johannesburg", "country_code": "ZA" },
      "parcels": [{ "barcode": "ACME-100045-1", "weight_kg": "2.5" }]
    }
  }'
GET/external/shipments

Synchronize shipments

Use the keyset-paginated feed for an initial synchronization, then poll with `updated_since`. Follow `meta.next_cursor` until it is null. The feed is ordered by update time and shipment number.

Scope shipments:readMaximum page 100
cURL
curl "https://api.routehut.example.com/api/v1/external/shipments?limit=50&updated_since=2026-07-19T08:00:00Z" \
  -H "Authorization: Bearer opk_your_credential"
GET/external/shipments/{shipment_number}

Read one shipment

Retrieve the current operational shipment record when an integration already knows its shipment number. The response includes the customer account code, collection and delivery addresses, and parcels for that tenant only.

Scope shipments:readTenant-scoped
POST/external/shipments/{shipment_number}/cancel

Cancel before operational handoff

Draft and untouched booked shipments can be cancelled through the external API. RouteHut voids pending charges, returns prepaid funds through auditable wallet credits, and records one cancellation event. Invoiced, assigned, routed, manifested, or held shipments require an operational exception workflow.

Scope shipments:writeRequired header Idempotency-Key
GET/external/tracking/shipments/{shipment_number}

Read customer-safe tracking

Retrieve the current status and tracking timeline without exposing the public tracking token. This is the integration-friendly counterpart to the customer tracking page.

Scope tracking:read
GET/external/shipments/{shipment_number}/proof-of-delivery

Proof of delivery

Read POD metadata, then request a short-lived attachment URL from the matching endpoint when evidence is attached. URL issuance is audit recorded.

Scope pod:readDownload lifetime 5 minutes
GET/external/shipments/{shipment_number}/proof-of-delivery/download

Real-time operations

Webhooks

Use webhooks when a system needs to react as work changes, rather than repeatedly asking RouteHut for the latest shipment state. A warehouse can prepare goods when a shipment is booked, an ecommerce portal can update its order timeline when it is collected or delivered, and a customer platform can fetch POD when delivery completes.

  1. Configure an endpoint.A RouteHut owner or administrator currently creates the subscription in the staff workspace and selects the events it needs.
  2. Verify it safely.Use Send test to queue a signed webhook.test.v1 payload and inspect its delivery history.
  3. Handle the event.Return a fast 2xx, record the event ID, and send heavier work to the receiving system’s own queue.
Delivery is at least once.Consumers must deduplicate using event_id. A network timeout can mean the receiver processed an event before RouteHut received the response.
Signing HMAC SHA-256Header X-Opero-SignatureHeader X-Opero-Timestamp
Compatibility headers remain stable.The existing X-Opero-* webhook header names are retained during the RouteHut brand migration so current integrations continue to verify deliveries.

RouteHut retries non-2xx responses and network failures up to five attempts with backoff. A receiver can return a standard Retry-After response to defer the next delivery for up to one hour. If an endpoint remains unavailable, the delivery is marked exhausted. Administrators can correct the endpoint or rotate its secret, then replay the preserved event from delivery history. Pausing a subscription stops queued work while it is under review.

Subscriptions receive a partner-safe delivery envelope without internal identifiers. External credentials can discover event names, list their tenant's subscriptions, and create a subscription; the signing secret is returned only when it is created and must be stored securely.

GET/external/webhooks/events

List supported webhook events

Discover the versioned event names available to RouteHut subscriptions. This endpoint does not create, update, or reveal subscriptions, signing secrets, or delivery payloads.

Scope webhooks:readCache no-store
GET/external/webhooks/subscriptions

List subscription configuration with opaque identifiers and an opaque keyset cursor. Signing secrets and delivery attempts are never included.

GET/external/webhooks/subscriptions/{external_id}

Retrieve one tenant-scoped subscription by its opaque identifier. Signing secrets are never returned by this read endpoint.

PATCH/external/webhooks/subscriptions/{external_id}

Pause, resume, or adjust subscription configuration with webhooks:write and an Idempotency-Key. Signing secrets remain unchanged and are never returned.

POST/external/webhooks/subscriptions/{external_id}/test-delivery

Queue a signed webhook.test.v1 delivery with webhooks:write and an Idempotency-Key. It is available only while the subscription is active.

POST/external/webhooks/subscriptions/{external_id}/rotate-secret

Rotate a signing secret with webhooks:write and an Idempotency-Key. Store the returned replacement immediately; it is not available from future read requests.

GET/external/webhooks/subscriptions/{external_id}/deliveries

Inspect delivery status through an opaque-cursor history feed with webhooks:read. It excludes target URLs, request headers, request bodies, and internal identifiers.

POST/external/webhooks/subscriptions/{external_id}/deliveries/{delivery_external_id}/replay

Replay an exhausted delivery with webhooks:write and an Idempotency-Key. The subscription must be active; the replay receives a new opaque delivery identifier.

POST/external/webhooks/subscriptions

Create a subscription with webhooks:write and an Idempotency-Key. The creation response returns its signing secret once; later reads never reveal it.

cURL
curl https://api.routehut.example.com/api/v1/external/webhooks/events \
  -H "Authorization: Bearer opk_your_credential"
GET/external/addresses

Synchronize the address book

Read customer-owned address-book entries by opaque address ID. Filter by customer_account_code when synchronizing one account, and follow meta.next_cursor for the next page.

Scope addresses:readMaximum page 100
GET/external/addresses/{external_id}

Retrieve one customer-owned address-book entry when a synchronized opaque address ID needs to be refreshed. Internal staff IDs and unowned operational addresses are never exposed.

POST/external/addresses

Create a customer-owned address-book entry with addresses:write and an Idempotency-Key. The customer account must be active in the credential tenant.

PATCH/external/addresses/{external_id}

Update an unused address-book entry with addresses:write and an Idempotency-Key. Addresses referenced by shipment history cannot be changed; create a replacement instead.

GET/external/customers

Synchronize customers and contacts

Use opaque customer and contact IDs for tenant-scoped synchronization. Customer reads and contact reads require customers:read; creates and operational updates require customers:write plus an Idempotency-Key.

GET/external/customers/{external_id}
POST/external/customers

Create a customer with its partner account code. Account codes and billing modes are not externally mutable.

PATCH/external/customers/{external_id}

Update the customer name or operational status without changing financial configuration.

GET/external/customers/{customer_external_id}/contacts

Follow meta.next_cursor through the bounded contact feed; the maximum page is 100.

POST/external/customers/{customer_external_id}/contacts
GET/external/customers/{customer_external_id}/contacts/{external_id}
PATCH/external/customers/{customer_external_id}/contacts/{external_id}

Tracking notifications require an email address. Contacts and consent state are never returned from the public tracking surface.

POST/external/shipment_imports

Submit bulk shipment imports

Queue a bounded JSON batch with shipments:write and an Idempotency-Key. Each partner row has a unique row_key; processing records independent row outcomes.

GET/external/shipment_imports/{external_id}

Poll import status and counters with shipments:read, then follow the row outcome feed with its opaque cursor. Submitted shipment data is never returned.

GET/external/shipment_imports/{external_id}/rows
POST/external/shipments/{shipment_number}/book

Book a draft shipment

Use shipments:write and an Idempotency-Key to confirm an existing draft. Booking applies the tenant's credit or prepaid funding rules and cannot be repeated outside idempotent replay.

GET/external/invoices

Synchronize invoices

Use the billing feed for issued, paid, and voided invoice updates. It uses the same opaque-cursor pattern as shipment synchronization; filter with updated_since and follow meta.next_cursor.

Scope billing:readMaximum page 100
cURL
curl "https://api.routehut.example.com/api/v1/external/invoices?limit=50&updated_since=2026-07-19T08:00:00Z" \
  -H "Authorization: Bearer opk_your_credential"

Retrieve /external/invoices/{invoice_number} when you need its line-level shipment references and charges. Internal IDs, contacts, addresses, and payment-provider data remain private.

Error model

Predictable failures

401Missing or invalid API credential.
403Credential does not carry the required scope.
404Resource does not exist in this credential’s tenant.
409Idempotency key conflict or in-progress retry.
429Credential rate limit exceeded. Wait for Retry-After, then retry.
422Validation or domain rule failed.

Rate limits are credential and tenant aware, with separate burst and sustained windows for reads and writes. A 429 rate_limit_exceeded response includes a body-level retry_after value and the matching Retry-After header. Do not retry before that delay.