Developers · API Reference

The OnTime Trucking API

Quote and track your freight programmatically. REST + JSON, Bearer-token auth, scoped keys, RFC 7807 errors. Read it here, grab the machine-readable spec, and integrate in minutes.

Base URL
https://ontimetrucking.com/api/v1

Step 1

Authenticate

Every request carries an API key as a Bearer token over HTTPS. Mint one in the portal under API Access. Keys are prefixed ott_live_, shown once at creation (copy it immediately), and you can keep 2 active keys at a time so you can rotate with zero downtime.

Authorization: Bearer ott_live_xxxxxxxx

Step 2

Scopes & access

A key can only do what OnTime Trucking has granted your account — its scopes. Each endpoint below lists the scope it requires; calling one you aren't granted returns 403 insufficient_scope (and names the missing scope).

scopegrants
quote:readCreate and read your own rate quotes.
rate:readPrice a shipment for live transit days (live carrier call).
shipment:readLook up shipment details by PRO.
shipment:writeBook a saved quote — assign a PRO and schedule the pickup (live carrier booking).
tracking:readRead live tracking status and events by PRO.

How keys and scopes fit together

  • Keys inherit your account's scopes. You don't pick scopes when you mint a key — it automatically carries whatever your account is granted.
  • Scope changes apply to your existing keys instantly. If OTT grants a new scope (say tracking:read), your current key can use it on the very next call — no need to mint a new one. Removing a scope takes effect just as fast.
  • A key can never exceed its grant. There's no way to widen a key beyond what OTT has granted your account.

Reference

Endpoints

MethodPathScopeDescription
POST/quotesquote:readCreate a rate quote — returns customer totals + transit days per carrier option.
GET/quotes/{id}quote:readFetch one of your saved quotes by id.
POST/quotes/{id}/bookshipment:writeBook a saved quote — assigns a PRO and schedules the pickup in one call. Idempotent.
GET/shipments/{pro}shipment:readShipment details by 9-digit PRO.
GET/shipments/{pro}/trackingtracking:readLive tracking timeline by PRO (TForce in V1).

Which id goes in the path?

  • {id} (quotes) — the “Quote #” shown on your quote page (also the quoteId returned by POST /quotes).
  • {pro} (shipments) — the 9-digit PRO assigned when a shipment is booked.

Full request/response schemas live in the OpenAPI spec — point your codegen, Postman, or AI agent at it for the canonical types.

Reference

Accessorial codes

Pass accessorial services on POST /quotes as an accessorials array of these codes — for example "accessorials": ["LIFD", "RESD"]. Send the code, not a label like “liftgate”.

codeserviceapplies to
LIFOLiftgate PickupPickup
INPUInside PickupPickup
LAPULimited Access PickupPickup
TRPUTradeshow PickupPickup
RESPResidential PickupPickup
IBFDIn-Bond Freight PickupPickup
LIFDLiftgate DeliveryDelivery
INDEInside DeliveryDelivery
LADLLimited Access DeliveryDelivery
NTFNCall Before DeliveryDelivery
TRDSTradeshow DeliveryDelivery
RESDResidential DeliveryDelivery
GWHDGrocery Warehouse DeliveryDelivery
HAZDHazmatHazmat

Good to know

  • Codes are case-insensitivelifd and LIFD both work.
  • An unknown code returns 422 validation_failed listing the valid codes — it is never silently ignored.
  • Accessorial charges are folded into each option's totalUSD; they are not itemized as separate line items.

Step 3

Make your first call

Replace $OTT_KEY with your key. Prefer a guided walkthrough? The interactive quickstart in your portal takes you from key → first verified call with a live test button.

export OTT_KEY=ott_live_xxxxxxxx

# Fetch one of YOUR quotes by its id
# (the "Quote #" on the quote page, or the quoteId from POST /quotes)
curl https://ontimetrucking.com/api/v1/quotes/759124905 \
  -H "Authorization: Bearer $OTT_KEY"

# Live tracking by PRO
curl https://ontimetrucking.com/api/v1/shipments/123456789/tracking \
  -H "Authorization: Bearer $OTT_KEY"

# Create a rate quote (add accessorials by code — see the Accessorial codes table)
curl -X POST https://ontimetrucking.com/api/v1/quotes \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{"originZip":"11735","destZip":"10001","weightLbs":500,"accessorials":["LIFD","RESD"]}'

# Create a rate quote with per-commodity line items (mixed classes + NMFC per line).
# When you send commodities[], each line is rated with its own class; top-level
# weightLbs/pieces/freightClass become optional aggregates.
curl -X POST https://ontimetrucking.com/api/v1/quotes \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{"originZip":"11735","destZip":"10001","commodities":[
        {"freightClass":"70","weightLbs":800,"pieces":2,"nmfc":"156600"},
        {"freightClass":"125","weightLbs":300,"pieces":1,"nmfc":"049880"}
      ]}'

# Book that quote — assigns a PRO and schedules the pickup in one call.
# ship-from/ship-to are reused from the saved quote unless you override them here.
# notes prints on the OTT BOL; customerReference (your PO / pickup ref) rides to
# the carrier so it lands on carrier paperwork + tracking.
curl -X POST https://ontimetrucking.com/api/v1/quotes/qt_3Fa9c2/book \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{"pickupDate":"2026-07-15","readyTime":"09:00","closeTime":"17:00","customerReference":"PO-88231","notes":"Call dock 30 min before arrival"}'

You only ever see your own data — a quote or shipment that isn't yours returns 404 (never a hint that it exists).

X-Request-Id — keep it for support

Every response (success or error) returns an X-Request-Id header — a unique id for that one call, mirrored in our logs. On errors it's also in the body as requestId. Quote it to support and we find your exact request instantly.

Sample — POST /quotes (201)

{
  "quoteId": "qt_3Fa9c2",
  "options": [
    { "carrierName": "TForce Freight", "totalUSD": 312.40, "transitDays": 2 },
    { "carrierName": "Ward", "totalUSD": 318.75, "transitDays": 2 }
  ],
  "expiresAt": "2026-06-29T20:14:00Z"
}

Sample — POST /quotes/{id}/book (201)

{
  "quoteId": "qt_3Fa9c2",
  "status": "BOOKED",
  "pro": "773117166",
  "bolNumber": "BOL-260715-3FA9C2",
  "pickupConfirmation": "WBU68949302",
  "carrierName": "TForce",
  "customerReference": "PO-88231"
}

Booking is idempotent — safe to retry

A repeat call on a quote that's already booked returns the SAME pro and pickupConfirmation with a 200 (not a second shipment). One booking per quote — a retry after a network blip never double-books. Provide pickupDate, readyTime, and closeTime (HH:MM, 24h); ship-from and ship-to are reused from the saved quote unless you pass shipFrom/shipTo overrides. On some shipments the carrier PRO is assigned shortly after booking rather than instantly — pro is null until then; the booking and pickup are confirmed either way.

Sample — GET /shipments/{pro} (200)

{
  "pro": "123456789",
  "status": "IN_TRANSIT",
  "originZip": "11735",
  "destZip": "10001",
  "weightLbs": 500,
  "freightClass": "100",
  "dims": { "lengthIn": 48, "widthIn": 40, "heightIn": 48 },
  "pieces": 2,
  "nmfc": "156600",
  "customerReference": "PO-88231",
  "accessorials": ["LIFD"],
  "commodities": []
}

Sample — GET /shipments/{pro}/tracking (200)

{
  "pro": "123456789",
  "status": { "code": "IN_TRANSIT", "description": "In transit" },
  "pickup": { "date": "2026-06-25T14:02:00Z" },
  "delivery": null,
  "events": [
    {
      "date": "2026-06-26T09:14:00Z",
      "description": "Departed service center",
      "serviceCenter": "Edison, NJ"
    },
    {
      "date": "2026-06-25T14:02:00Z",
      "description": "Picked up",
      "serviceCenter": "Farmingdale, NY"
    }
  ]
}

Every response is clean and ready to use — one all-in price, transit time, live status, and your shipment documents. No rate tables to decode, no surcharges to add up.

Reference

Errors (RFC 7807)

New to RFC 7807?

It's the web standard for machine-readable errors — a small JSON object with type, title, status, and detail, served as application/problem+json. We add a stable code so you switch on one short string instead of parsing a sentence.

Switch on code, not the human title. Every error echoes the requestId too.

{
  "type": "https://ontimetrucking.com/technology/api#errors",
  "title": "Resource not found",
  "status": 404,
  "detail": "Shipment not found.",
  "code": "not_found",
  "requestId": "0be4c35a-0f05-4905-97da-d3367748eaa4"
}
codemeaning
missing_key / invalid_key / revoked_key / expired_key401 — authentication failed
insufficient_scope / no_tenant_scope403 — key lacks the required scope
not_found404 — not found, or not yours (no existence leak)
conflict409 — already being booked / booked (idempotent replay returns the existing booking)
validation_failed422 — bad input (e.g. PRO not 9 digits, or a lane OTT dispatches itself)
rate_limited429 — slow down; see Retry-After header
not_implemented501 — endpoint/feature not enabled
upstream_unavailable / upstream_error503 — carrier temporarily unavailable; see Retry-After

Reference

Rate limits

Per-client caps, shared across our servers. On exceed you get 429 with a Retry-After header (seconds) — wait, then retry.

policyendpointslimit
api_defaultreads (quotes/{id}, shipments, tracking)60 / min
api_ratePOST /quotes + POST /quotes/{id}/book (live carrier calls)20 / 10 min
api_docsdocuments30 / 5 min

Ready to build?

Grab a key in the portal, then point your tooling at the spec. Questions? Email sales@ontimetrucking.com or call dispatch at (800) 248-4630 — include your requestId so we can trace the call.