OnTime Trucking · Developer Platform

Build freight workflows without leaving your system.

Quote, book, retrieve, and track OTT shipments through a scoped REST API with predictable JSON, Bearer-token auth, and traceable RFC 7807 errors. Read it here, grab the machine-readable spec, and integrate in minutes.

View as Markdown ↗
Protocol
REST · JSON · HTTPS
Authentication
Bearer API key

Start here

Overview

The OTT API follows the shipment lifecycle: create a quote, book an eligible option, then retrieve and track the resulting shipment. Every response is tenant-scoped, so your key can only read records associated with your account.

Base URL
https://ontimetrucking.com/api/v1
Machine-readable contract
/api/openapi.json

Step 1

Authenticate every request

Send your API key as a Bearer token over HTTPS. Mint one in the portal under API Access. Live 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.

HTTP header
Authorization: Bearer $OTT_KEY
ScopeGrants
quote:readCreate and retrieve your own rate quotes (runs the live carrier rate-shop).
shipment:readRetrieve shipment details by PRO.
shipment:writeBook an eligible saved quote and request pickup (live carrier booking).
tracking:readRetrieve live tracking status and events by PRO.

A key inherits your account's scopes — you don't choose them at mint time. Scope changes (granting or removing) apply to your existing keys instantly, on the very next call, and a key can never exceed what OTT has granted your account. Calling an endpoint you aren't granted returns 403 insufficient_scope and names the missing scope.

Step 2

Follow the integration flow

  1. 01

    Quote

    Send the lane and freight details.

  2. 02

    Book

    Turn an eligible quote into a shipment.

  3. 03

    Retrieve

    Read the shipment using its PRO.

  4. 04

    Track

    Poll the live event timeline.

Your first quote
curl -X POST https://ontimetrucking.com/api/v1/quotes \
  -H "Authorization: Bearer $OTT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "originZip": "11735",
    "destZip": "10001",
    "weightLbs": 500,
    "pieces": 1,
    "freightClass": "100",
    "accessorials": ["LIFD", "RESD"]
  }'

Reference

Endpoint directory

There is no quote-list endpoint in v1. To retrieve a quote later, retain the quoteId returned by POST /quotes.

POST/quotes

Create a rate quote

Price a shipment and get transit days for every eligible carrier option.

Required scope quote:read

FieldTypeDescription
originZipstring · requiredOrigin postal code (US, Canada, or Mexico).
destZipstring · requiredDestination postal code (US, Canada, or Mexico).
originCountry / destCountryUS · CA · MXOptional; each defaults to US.
weightLbsnumber · 1–30,000Total weight for the flat single-commodity path. Legacy: the flat body can't carry dimensions — prefer commodities[].
piecesinteger · 1–1,000Handling-unit or pallet count; defaults to 1.
freightClassstringNMFC freight class used for rating; defaults to 100.
nmfcstring · max 30Optional single-commodity item number. Prints on the BOL; does not affect price.
commodities[]array · max 30 · recommendedPer-line items (see below) — the canonical, recommended request shape. When present, becomes the rating source of truth.
commodities[].dimsInobject · REQUIREDPer-line dimensions in inches: { length, width, height }. Now required for accurate rating (carriers re-rate on measured size). Compatibility aliases dims and dimensions are accepted. All three values are required together; any side over 336" (28 ft) is refused (422). A request missing dimensions is refused with 422.
ratingModeSTANDARD · VOLUMEOptional; defaults to STANDARD (class-based LTL). Use VOLUME to force spot rating for a large load — priced off linear feet, not freight class. You don't have to set it: OTT auto-runs a spot quote per carrier when a load trips that carrier's cubic-capacity rule (TForce Item 575: 750+ cu ft and under 6 lb/cu ft).
linearFeetnumber · 8–28Required when ratingMode is VOLUME: the trailer floor space the freight occupies, in linear feet. Ignored for STANDARD.
accessorials[]string[] · max 20Optional OTT service codes or documented friendly labels (see Accessorial codes). Unknown, blank, or malformed values return 422.
Request · flat single commodity
curl -X POST https://ontimetrucking.com/api/v1/quotes \
  -H "Authorization: Bearer $OTT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "originZip": "11735",
    "destZip": "10001",
    "weightLbs": 500,
    "pieces": 1,
    "freightClass": "100",
    "accessorials": ["LIFD", "RESD"]
  }'
Request · per-commodity line items with dimensions
curl -X POST https://ontimetrucking.com/api/v1/quotes \
  -H "Authorization: Bearer $OTT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "originZip": "11735",
    "destZip": "10001",
    "commodities": [
      {
        "freightClass": "70",
        "weightLbs": 800,
        "pieces": 2,
        "nmfc": "156600",
        "dimsIn": { "length": 48, "width": 40, "height": 48 }
      },
      {
        "freightClass": "125",
        "weightLbs": 300,
        "pieces": 1,
        "nmfc": "049880"
      }
    ]
  }'
Request · volume / spot (large low-density load)
curl -X POST https://ontimetrucking.com/api/v1/quotes \\
  -H "Authorization: Bearer ***" \\
  -H "Content-Type: application/json" \\
  -d '{
    "originZip": "91764",
    "destZip": "75069",
    "ratingMode": "VOLUME",
    "linearFeet": 14,
    "commodities": [
      {
        "freightClass": "100",
        "weightLbs": 6200,
        "pieces": 6,
        "dimsIn": { "length": 48, "width": 40, "height": 60 }
      }
    ]
  }'
Example 201 response
{
  "quoteId": "qt_3Fa9c2",
  "options": [
    { "carrierOptionId": "v1.9VLCDygJZ8Z9WxAdpD4hGfaS3pHG9HOeODuBejZI9FQ", "carrierName": "TForce Freight", "totalUSD": 312.40, "transitDays": 2 },
    { "carrierOptionId": "v1.EYiAHNfjMugZqAbVW_tVF0SAjGPiyVmt5FXvyrk6psM", "carrierName": "Saia LTL Freight", "totalUSD": 318.75, "transitDays": 2 }
  ],
  "expiresAt": "2026-07-24T20:14:00Z"
}

Each totalUSD is your final all-in price — nothing to add on top. expiresAt tells you how long OTT stands behind the displayed rate. The saved quote stays retrievable past it, but booking an expired quote is refused with 409 conflict (quotes are valid for 3 days by default) — request a fresh quote to book at current pricing.

GET/quotes/{id}

Retrieve a quote

Fetch one saved quote by its API quote ID or visible carrier quote number.

Required scope quote:read

Use the API quoteId returned by quote creation. The visible carrier “Quote #” is also accepted for convenience; the API id is matched first.

Request
curl https://ontimetrucking.com/api/v1/quotes/qt_3Fa9c2 \
  -H "Authorization: Bearer $OTT_KEY"
Example 200 response
{
  "id": "qt_3Fa9c2",
  "originZip": "11735",
  "destZip": "10001",
  "weightLbs": 500,
  "pro": null,
  "createdAt": "2026-07-21T18:42:00Z"
}
POST/quotes/{id}/book

Book a quote

Create the BOL, assign a PRO, and request pickup in one idempotent call.

Required scope shipment:write

FieldTypeDescription
pickupDateYYYY-MM-DD · requiredCannot be in the past.
readyTimeHH:MM · requiredEarliest pickup time in 24-hour format.
closeTimeHH:MM · requiredDock close; must be later than readyTime.
shipFrom / shipToobject · optionalAddress override; saved quote/profile values are reused when omitted.
requesterobject · optionalPickup requester company, contact, email, and phone.
customerReferencestring · max 50Your PO or pickup reference; sent to the carrier and returned on shipment reads.
poNumberstring · max 30Purchase-order number. Prints on the OTT BOL and rides to the carrier — TForce as a structured PO reference; Saia, XPO, and Old Dominion in carrier/driver instructions.
pickupReferencestring · max 30Your pickup/warehouse reference. Prints on the OTT BOL and rides to the carrier — TForce in the native pickup Reference Number field; Saia, XPO, and Old Dominion in carrier/driver instructions.
notesstring · max 500Special handling or pickup instructions printed on the OTT BOL.
Request
curl -X POST https://ontimetrucking.com/api/v1/quotes/qt_3Fa9c2/book \
  -H "Authorization: Bearer $OTT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pickupDate": "2026-07-23",
    "readyTime": "09:00",
    "closeTime": "17:00",
    "customerReference": "PO-88231",
    "poNumber": "PO-88231",
    "pickupReference": "WH-4471",
    "notes": "Call dock 30 minutes before arrival"
  }'
Example 201 response
{
  "quoteId": "qt_3Fa9c2",
  "status": "BOOKED",
  "pro": "773117166",
  "bolNumber": "BOL-260723-3FA9C2",
  "pickupConfirmation": "WBU68949302",
  "carrierName": "TForce",
  "customerReference": "PO-88231"
}
GET/shipments/{pro}

Retrieve a shipment

Read shipment details using its carrier PRO number.

Required scope shipment:read

Request
curl https://ontimetrucking.com/api/v1/shipments/773117166 \
  -H "Authorization: Bearer $OTT_KEY"
Example 200 response
{
  "pro": "773117166",
  "status": "IN_TRANSIT",
  "originZip": "11735",
  "destZip": "10001",
  "weightLbs": 500,
  "freightClass": "100",
  "dims": { "lengthIn": 48, "widthIn": 40, "heightIn": 48 },
  "pieces": 1,
  "nmfc": "156600",
  "customerReference": "PO-88231",
  "accessorials": ["LIFD"],
  "commodities": []
}

Note the response returns dimensions as dims.lengthIn (and null when none were captured) — the read shape, not the dimsIn.length request shape.

GET/shipments/{pro}/tracking

Track a shipment

Read the current status and carrier event timeline.

Required scope tracking:read

Request
curl https://ontimetrucking.com/api/v1/shipments/773117166/tracking \
  -H "Authorization: Bearer $OTT_KEY"
Example 200 response
{
  "pro": "773117166",
  "status": { "code": "IN_TRANSIT", "description": "In transit" },
  "pickup": { "date": "2026-07-23T14:02:00Z" },
  "delivery": null,
  "events": [
    {
      "date": "2026-07-24T09:14:00Z",
      "description": "Departed service center",
      "serviceCenter": "Edison, NJ"
    },
    {
      "date": "2026-07-23T14:02:00Z",
      "description": "Picked up",
      "serviceCenter": "Farmingdale, NY"
    }
  ]
}

The carrier is resolved from the booking, never from the request, and the response's carrier field names which carrier answered. Nine carriers are tracked live (TForce, Saia, Old Dominion, XPO, A. Duie Pyle, ArcBest, Dot-Line, Oak Harbor, Ward); OTT self-dispatch shipments return an honest OTT dispatch status rather than a fabricated carrier timeline, and a freight-forwarding shipment returns 501 not_implemented because there is no live feed to read. A carrier brownout returns 503 with a Retry-After header.

GET/shipments/{pro}/documents/{type}

Retrieve a shipment document

Download your shipment's Bill of Lading (HTML), Proof of Delivery (PDF), or Invoice (PDF) by PRO.

Required scope shipment:read

Request — Bill of Lading (HTML)
curl https://ontimetrucking.com/api/v1/shipments/773117166/documents/bol \\
  -H "Authorization: Bearer ***"
Request — Bill of Lading (PDF)
curl https://ontimetrucking.com/api/v1/shipments/773117166/documents/bol?format=pdf \\
  -H "Authorization: Bearer ***" -o bol.pdf
Request — Proof of Delivery (PDF)
curl https://ontimetrucking.com/api/v1/shipments/773117166/documents/pod \\
  -H "Authorization: Bearer ***" -o pod.pdf
Request — Invoice (PDF)
curl https://ontimetrucking.com/api/v1/shipments/773117166/documents/invoice \\
  -H "Authorization: Bearer ***" -o invoice.pdf

bol returns your shipment's OnTime Trucking Bill of Lading as a printable text/html document by default; add ?format=pdf to get it as application/pdf.pod returns the carrier Proof of Delivery once the shipment has been delivered, as application/pdf or, when that is how the carrier scans it, image/tiff, image/png or image/jpeg. POD is fetched from the carrier, so it is available for TForce and A. Duie Pyle shipments; any other carrier returns 501 document_not_supported naming the carrier — call OTT and we will send it, because the carrier does have one. A 404 means it is not available yet, and the detail says whether that is because the shipment has not been delivered or because the carrier has not published it. invoice returns your OnTime Trucking invoice as application/pdf once the shipment is booked. All are for shipments on your own account, looked up by their carrier PRO, and contain customer-facing information only — never carrier cost or margin.

Reference

What works per carrier

Quoting covers every carrier. A booking always succeeds: if a carrier cannot be booked through the API it is queued for OTT dispatch — the response says so with status: "BOOKING_REQUESTED", pro: null and a warnings[] line, and the PRO appears on GET /quotes/{id}/ once the carrier assigns it. BOL and invoice are OTT-generated and identical for every carrier. POD is the one document the API fetches from the carrier.

CarrierQuoteBookTrackBOLPODInvoice
TForceYesAuto or manualYesOTTYesOTT
SaiaYesAuto or manualYesOTT501 — contact OTTOTT
Old DominionYesAuto or manualYesOTT501 — contact OTTOTT
XPO LogisticsYesAuto or manualYesOTT501 — contact OTTOTT
A. Duie PyleYesAuto or manualYesOTTYesOTT
ArcBestYesAuto or manualYesOTT501 — contact OTTOTT
Dot-LineYesAuto or manualYes (short PROs, e.g. 86820)OTT501 — contact OTTOTT
Oak HarborYesAuto or manualYesOTT501 — contact OTTOTT
WardYesAlways manualYes (10-digit)OTT501 — contact OTTOTT
OTT fleet (NY lanes)YesAlways manualOTT dispatch statusOTTNot applicableOTT
WorldTrak (forwarding)YesAlways manual501 — no live feedOTT501 — contact OTTOTT

Why some PODs say 501. The carrier has the POD; our API just does not fetch that carrier's copy yet, so it answers 501 document_not_supported with the carrier's name and the number to call instead of pretending the document does not exist. TForce and A. Duie Pyle are served today; the rest are a one-line change per carrier once each adapter is verified against a customer PRO.

Reference

Accessorial codes

Pass accessorial services on POST /quotes as an accessorials array. Canonical codes are recommended — for example ["LIFD", "RESD"] — and are case-insensitive. Friendly accessorial labels such as liftgate, residential, appointment, limitedAccess, inside, tradeShow, and hazmat are normalized to the matching code. Add pickup or delivery to choose the side; a directionless label defaults to delivery.

CodeServiceApplies to
INPUInside pickupPickup
LIFOLiftgate pickupPickup
LAPULimited-access pickupPickup
TRPUTradeshow pickupPickup
RESPResidential pickupPickup
IBFDIn-bond freight pickupPickup
INDEInside deliveryDelivery
LIFDLiftgate deliveryDelivery
LADLLimited-access deliveryDelivery
NTFNCall before deliveryDelivery
TRDSTradeshow deliveryDelivery
RESDResidential deliveryDelivery
GWHDGrocery warehouse deliveryDelivery
HAZDHazardous materialsShipment

Reference

Errors use RFC 7807

Switch on the stable code, not the human-readable title or detail. Every error echoes the requestId too.

Validation responses may also include an errors[] array. Each item has a machine-readable path, code, message, and optional expected value so an integration can correct the exact field.

Example problem response
{
  "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"
}
HTTPCodeMeaning
401missing_key · invalid_key · revoked_key · expired_keyAuthentication failed.
403insufficient_scope · no_tenant_scope · client_disabled · ip_not_allowed · signup_requiredThe key cannot perform this operation, the client is not active, or the visitor sign-in assertion is missing.
404not_foundNot found, not yours (no existence leak), or — for a document — genuinely not published yet.
409conflictThe quote moved: it is already being booked, it expired, its pricing changed, the carrierOptionId is stale, its dimensions are missing or refused, or the selected option is not bookable on this lane. The detail says which.
422validation_failedBad input — a field out of range, a side over 336", an unaccepted PRO shape, or an incomplete address at booking. errors[] carries the exact field.
422unprocessableValid request, but no carrier will price it (every carrier declined or needs a manual volume quote). carriers[] names each carrier with a short reason. Retrying cannot help.
429rate_limitedSlow down; wait for the Retry-After interval before retrying.
501not_implemented · document_not_supportedThe feature is not available for this shipment: live tracking for a forwarding shipment, or POD for a carrier whose copy we cannot fetch through the API. The detail names the carrier and the number to call.
502upstream_errorA carrier answered with something we could not use. Retry; if it persists, contact us with the requestId.
503upstream_unavailableA carrier service or our document renderer did not respond, or every carrier timed out. The detail says which part failed; honour Retry-After.

Reference

Rate limits and retries

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

PolicyApplies toLimit
api_defaultMost calls — quote reads, booking, shipment reads, tracking60 / minute
api_rateQuote creation (POST /quotes) — the live carrier rate-shop20 / 10 minutes

Limits are per client and shared across OTT servers. On a 429 or a retryable 503, respect the Retry-After header before trying again.

Reference

Changelog

The OTT API is versioned in the OpenAPI spec (info.version). Changes are additive and backward-compatible within v1 — new optional fields and endpoints may appear; existing request and response shapes are not broken.

v1.7.0October 2026 · current

Carrier clarity. One theme: where a carrier or a document cannot do what you asked, the API now says which carrier, why, and what to do — instead of a generic code to loop on. Every change is additive; nothing you already send behaves differently.

  • PROs we issue are PROs we accept. The validator now takes every format OTT itself returns, including short Dot-Line PROs (e.g. 86820) and 10-digit Ward PROs, and it strips a leading carrier label (TForce:) or #. Previously our own booking could hand back a PRO our own tracking refused with 422. The 422 detail now lists the accepted shapes.
  • A manual booking says so. When OTT dispatch completes the booking by hand (Ward, OTT lane, forwarding, or a carrier whose auto-book is off), the response carries status: "BOOKING_REQUESTED", pro: null, bookingMode: "manual" and a plain warnings[] line telling you the PRO will appear on GET /quotes/{id}. status is documented as BOOKED | BOOKING_REQUESTED, and sandbox is always present.
  • POD says which carrier it cannot fetch. A POD request for a carrier we do not serve from the API now returns 501 document_not_supported naming the carrier, the supported carriers and the number to call. It used to return a 404 "No pod on file", which read as "the carrier has none". A 404 now means "not available yet" and says whether the shipment has been delivered. POD content types now cover TIFF/PNG/JPEG scans.
  • A lane nobody serves is not a retry loop. POST /quotes answers 422 unprocessable with a carriers[] array (carrier + short reason: no_service, rejected, needs_assistance, timeout, unavailable, unknown) when every carrier declined, and 503 + Retry-After only when a carrier could not be reached. Before, both were a 503 with no explanation.
  • Forwarding is labelled correctly. A freight-forwarding (WorldTrak) shipment is no longer reported as TForce: bookings, BOLs and tracking rows name WorldTrak, and its tracking answers 501 not_implemented with the dispatch number instead of a 404 from another carrier's system. Tracking responses now always include carrier.
  • Test keys are safer. Re-creating a test key can no longer re-enable a sandbox OTT disabled, and two accounts with the same company name can no longer share one sandbox. Behaviour for a healthy sandbox is unchanged.
v1.6.0October 2026

Test keys. Create an ott_test_ key yourself under API Access. It uses the same base URL and the same scopes as your live key. Quotes and rates are real; booking is simulated (no carrier is contacted), and tracking and BOL/POD return labelled sample data for the test PRO the booking returns. No request or response shapes changed for live keys; simulated bookings add "sandbox": true.

v1.5.0September 2026

Item dimensions are now required on POST /quotes. Carriers re-rate every shipment on measured size and weight and bill the difference (extreme-length, cube, reweigh). A class-only quote can't price those adjustments, so we now rate on real dimensions to keep your quotes accurate and avoid surprise re-rates.

  • Required now — a quote missing any line item's length, width, or height is refused with 422 validation_failed. Send each commodity with dimsIn: { length, width, height } in inches.
  • Canonical shape — use the commodities[] array (each line with dimsIn, weightLbs,pieces, freightClass). The legacy flat weightLbs/freightClass body can't carry dimensions and is no longer accepted for rating.
  • Commodity description field — each commodity line now accepts an optional plain-text description (e.g. "machine parts"). Prints on the BOL; does not affect the rate.
v1.4.0Effective August 2026

Documentation accuracy update — no request or response shapes changed. Brings the published reference in line with current live behavior.

  • Extended dimension ceiling — the per-side limit is now 336" (28 ft), not 96". Loads up to the ceiling are rated online with per-carrier extreme-length handling; only a side over 336" is refused with 422.
  • Carrier selection with carrierOptionId — POST /quotes returns a carrierOptionId on every option; pass it unchanged to POST /quotes/{id}/book to book that exact priced option. A stale or unknown id returns 409.
  • Quote-expiry booking gate clarified — booking an unbooked quote past its validity window (default 3 days) is refused with 409 conflict. (Previously documented as advisory-only or a 422; the live behavior is a 409.)
v1.3.0Effective August 2026

Invoice document retrieval on GET /shipments/{pro}/documents/{type}. Backward-compatible — bol and pod are unchanged.

  • Invoice PDF — request type=invoice to download your OnTime Trucking invoice as application/pdf once the shipment is booked (before booking it returns 404). It is the same invoice shown in the OTT portal, and contains customer-facing information only — never carrier cost or margin. No new scope: it uses the existing shipment:read.
  • Bill of Lading as PDF — add ?format=pdf to type=bol to receive the BOL as application/pdf. Backward-compatible: omit it and bol still returns HTML as before.
v1.2.0Effective August 2026

Volume / spot rating for large and multi-pallet loads on POST /quotes. Backward-compatible — standard LTL requests are unchanged.

  • Volume / spot quoting — send ratingMode: "VOLUME" with linearFeet (8–28) to rate a large load off linear feet instead of freight class. Omit both for standard class-based LTL.
  • Automatic cubic-capacity spot rating — OTT computes each load's cube and density and auto-runs a spot quote per carrier when it trips that carrier's cubic-capacity rule (TForce Item 575: 750+ cu ft and under 6 lb/cu ft), with no pallet-count threshold. Volume lanes that need a manual spot quote return a clear 422 pointing to OTT dispatch (not a retryable error).
  • Safer input compatibility — per-commodity dims and dimensions are accepted as aliases for dimsIn; common accessorial labels are normalized to OTT codes; malformed or conflicting values return structured field errors.
v1.1.0Effective August 2026

Backward-compatible additions and stricter request validation. The endpoint path is unchanged (/api/v1) — the path only changes for a breaking redesign.

  • Multi-commodity quoting — send commodities[] (max 30 lines) on POST /quotes; each line rates with its own freight class and the top-level weightLbs/pieces/freightClass become optional aggregates. When present, commodities[] is the rating source of truth.
  • Per-commodity NMFC — each line accepts an nmfc item number (also available as a single-commodity nmfc). Prints on the BOL; does not affect price.
  • commodities[].dimsIn validation — dimensions live only inside a per-line dimsIn object ({ length, width, height } in inches, all three required together). A side over 336" (28 ft), or dimensions sent at the wrong level, is now rejected with 422 naming the offending field — before any carrier call.
  • Booking notes and customerReference on POST /quotes/{id}/book — customerReference (your PO, echoed on shipment reads) and notes (special-handling text printed on the OTT BOL).
v1.0.0July 2026 · initial release
  • Initial public v1: POST /quotes, GET /quotes/{id}, POST /quotes/{id}/book, GET /shipments/{pro}, and GET /shipments/{pro}/tracking.
  • Bearer API-key auth with account-inherited scopes, RFC 7807 errors with a stable code, per-client rate limits, and X-Request-Id on every response.

Ready to integrate?

Start with a scoped key and one test quote.

Grab a key in the portal, then point your tooling at the spec. For support, include the endpoint, timestamp, and X-Request-Id — and never email your API key. Questions? sales@ontimetrucking.com or call dispatch at (800) 248-4630.