Table of contents
API v1.7 · Public reference
Verified against route handlers · September 2026
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.
Authorization: Bearer $OTT_KEY| Scope | Grants |
|---|---|
| quote:read | Create and retrieve your own rate quotes (runs the live carrier rate-shop). |
| shipment:read | Retrieve shipment details by PRO. |
| shipment:write | Book an eligible saved quote and request pickup (live carrier booking). |
| tracking:read | Retrieve 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
- 01
Quote
Send the lane and freight details.
- 02
Book
Turn an eligible quote into a shipment.
- 03
Retrieve
Read the shipment using its PRO.
- 04
Track
Poll the live event timeline.
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
/quotesPrice a shipment and get transit days for every eligible carrier option.GET/quotes/{id}Fetch one saved quote by its API quote ID or visible carrier quote number.POST/quotes/{id}/bookCreate the BOL, assign a PRO, and request pickup in one idempotent call.GET/shipments/{pro}Read shipment details using its carrier PRO number.GET/shipments/{pro}/trackingRead the current status and carrier event timeline.GET/shipments/{pro}/documents/{type}Download your shipment's Bill of Lading (HTML), Proof of Delivery (PDF), or Invoice (PDF) by PRO.There is no quote-list endpoint in v1. To retrieve a quote later, retain the quoteId returned by POST /quotes.
/quotesCreate a rate quote
Price a shipment and get transit days for every eligible carrier option.
Required scope quote:read
| Field | Type | Description |
|---|---|---|
| originZip | string · required | Origin postal code (US, Canada, or Mexico). |
| destZip | string · required | Destination postal code (US, Canada, or Mexico). |
| originCountry / destCountry | US · CA · MX | Optional; each defaults to US. |
| weightLbs | number · 1–30,000 | Total weight for the flat single-commodity path. Legacy: the flat body can't carry dimensions — prefer commodities[]. |
| pieces | integer · 1–1,000 | Handling-unit or pallet count; defaults to 1. |
| freightClass | string | NMFC freight class used for rating; defaults to 100. |
| nmfc | string · max 30 | Optional single-commodity item number. Prints on the BOL; does not affect price. |
| commodities[] | array · max 30 · recommended | Per-line items (see below) — the canonical, recommended request shape. When present, becomes the rating source of truth. |
| commodities[].dimsIn | object · REQUIRED | Per-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. |
| ratingMode | STANDARD · VOLUME | Optional; 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). |
| linearFeet | number · 8–28 | Required when ratingMode is VOLUME: the trailer floor space the freight occupies, in linear feet. Ignored for STANDARD. |
| accessorials[] | string[] · max 20 | Optional OTT service codes or documented friendly labels (see Accessorial codes). Unknown, blank, or malformed values return 422. |
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"]
}'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"
}
]
}'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 }
}
]
}'{
"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.
/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.
curl https://ontimetrucking.com/api/v1/quotes/qt_3Fa9c2 \
-H "Authorization: Bearer $OTT_KEY"{
"id": "qt_3Fa9c2",
"originZip": "11735",
"destZip": "10001",
"weightLbs": 500,
"pro": null,
"createdAt": "2026-07-21T18:42:00Z"
}/quotes/{id}/bookBook a quote
Create the BOL, assign a PRO, and request pickup in one idempotent call.
Required scope shipment:write
| Field | Type | Description |
|---|---|---|
| pickupDate | YYYY-MM-DD · required | Cannot be in the past. |
| readyTime | HH:MM · required | Earliest pickup time in 24-hour format. |
| closeTime | HH:MM · required | Dock close; must be later than readyTime. |
| shipFrom / shipTo | object · optional | Address override; saved quote/profile values are reused when omitted. |
| requester | object · optional | Pickup requester company, contact, email, and phone. |
| customerReference | string · max 50 | Your PO or pickup reference; sent to the carrier and returned on shipment reads. |
| poNumber | string · max 30 | Purchase-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. |
| pickupReference | string · max 30 | Your 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. |
| notes | string · max 500 | Special handling or pickup instructions printed on the OTT BOL. |
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"
}'{
"quoteId": "qt_3Fa9c2",
"status": "BOOKED",
"pro": "773117166",
"bolNumber": "BOL-260723-3FA9C2",
"pickupConfirmation": "WBU68949302",
"carrierName": "TForce",
"customerReference": "PO-88231"
}/shipments/{pro}Retrieve a shipment
Read shipment details using its carrier PRO number.
Required scope shipment:read
curl https://ontimetrucking.com/api/v1/shipments/773117166 \
-H "Authorization: Bearer $OTT_KEY"{
"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.
/shipments/{pro}/trackingTrack a shipment
Read the current status and carrier event timeline.
Required scope tracking:read
curl https://ontimetrucking.com/api/v1/shipments/773117166/tracking \
-H "Authorization: Bearer $OTT_KEY"{
"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.
/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
curl https://ontimetrucking.com/api/v1/shipments/773117166/documents/bol \\
-H "Authorization: Bearer ***"curl https://ontimetrucking.com/api/v1/shipments/773117166/documents/bol?format=pdf \\
-H "Authorization: Bearer ***" -o bol.pdfcurl https://ontimetrucking.com/api/v1/shipments/773117166/documents/pod \\
-H "Authorization: Bearer ***" -o pod.pdfcurl https://ontimetrucking.com/api/v1/shipments/773117166/documents/invoice \\
-H "Authorization: Bearer ***" -o invoice.pdfbol 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.
| Carrier | Quote | Book | Track | BOL | POD | Invoice |
|---|---|---|---|---|---|---|
| TForce | Yes | Auto or manual | Yes | OTT | Yes | OTT |
| Saia | Yes | Auto or manual | Yes | OTT | 501 — contact OTT | OTT |
| Old Dominion | Yes | Auto or manual | Yes | OTT | 501 — contact OTT | OTT |
| XPO Logistics | Yes | Auto or manual | Yes | OTT | 501 — contact OTT | OTT |
| A. Duie Pyle | Yes | Auto or manual | Yes | OTT | Yes | OTT |
| ArcBest | Yes | Auto or manual | Yes | OTT | 501 — contact OTT | OTT |
| Dot-Line | Yes | Auto or manual | Yes (short PROs, e.g. 86820) | OTT | 501 — contact OTT | OTT |
| Oak Harbor | Yes | Auto or manual | Yes | OTT | 501 — contact OTT | OTT |
| Ward | Yes | Always manual | Yes (10-digit) | OTT | 501 — contact OTT | OTT |
| OTT fleet (NY lanes) | Yes | Always manual | OTT dispatch status | OTT | Not applicable | OTT |
| WorldTrak (forwarding) | Yes | Always manual | 501 — no live feed | OTT | 501 — contact OTT | OTT |
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.
| Code | Service | Applies to |
|---|---|---|
| INPU | Inside pickup | Pickup |
| LIFO | Liftgate pickup | Pickup |
| LAPU | Limited-access pickup | Pickup |
| TRPU | Tradeshow pickup | Pickup |
| RESP | Residential pickup | Pickup |
| IBFD | In-bond freight pickup | Pickup |
| INDE | Inside delivery | Delivery |
| LIFD | Liftgate delivery | Delivery |
| LADL | Limited-access delivery | Delivery |
| NTFN | Call before delivery | Delivery |
| TRDS | Tradeshow delivery | Delivery |
| RESD | Residential delivery | Delivery |
| GWHD | Grocery warehouse delivery | Delivery |
| HAZD | Hazardous materials | Shipment |
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.
{
"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"
}| HTTP | Code | Meaning |
|---|---|---|
| 401 | missing_key · invalid_key · revoked_key · expired_key | Authentication failed. |
| 403 | insufficient_scope · no_tenant_scope · client_disabled · ip_not_allowed · signup_required | The key cannot perform this operation, the client is not active, or the visitor sign-in assertion is missing. |
| 404 | not_found | Not found, not yours (no existence leak), or — for a document — genuinely not published yet. |
| 409 | conflict | The 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. |
| 422 | validation_failed | Bad 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. |
| 422 | unprocessable | Valid 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. |
| 429 | rate_limited | Slow down; wait for the Retry-After interval before retrying. |
| 501 | not_implemented · document_not_supported | The 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. |
| 502 | upstream_error | A carrier answered with something we could not use. Retry; if it persists, contact us with the requestId. |
| 503 | upstream_unavailable | A 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.
| Policy | Applies to | Limit |
|---|---|---|
| api_default | Most calls — quote reads, booking, shipment reads, tracking | 60 / minute |
| api_rate | Quote creation (POST /quotes) — the live carrier rate-shop | 20 / 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.
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 plainwarnings[]line telling you the PRO will appear onGET /quotes/{id}.statusis documented asBOOKED | BOOKING_REQUESTED, andsandboxis 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_supportednaming 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 /quotesanswers422 unprocessablewith acarriers[]array (carrier + short reason: no_service, rejected, needs_assistance, timeout, unavailable, unknown) when every carrier declined, and503+Retry-Afteronly 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_implementedwith the dispatch number instead of a 404 from another carrier's system. Tracking responses now always includecarrier. - 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.
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.
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, orheightis refused with422 validation_failed. Send each commodity withdimsIn: { length, width, height }in inches. - Canonical shape — use the
commodities[]array (each line withdimsIn,weightLbs,pieces,freightClass). The legacy flatweightLbs/freightClassbody 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.
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), not96". Loads up to the ceiling are rated online with per-carrier extreme-length handling; only a side over 336" is refused with422. - Carrier selection with
carrierOptionId—POST /quotesreturns acarrierOptionIdon every option; pass it unchanged toPOST /quotes/{id}/bookto book that exact priced option. A stale or unknown id returns409. - 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.)
Invoice document retrieval on GET /shipments/{pro}/documents/{type}. Backward-compatible — bol and pod are unchanged.
- Invoice PDF — request
type=invoiceto download your OnTime Trucking invoice asapplication/pdfonce the shipment is booked (before booking it returns404). 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 existingshipment:read. - Bill of Lading as PDF — add
?format=pdftotype=bolto receive the BOL asapplication/pdf. Backward-compatible: omit it andbolstill returns HTML as before.
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"withlinearFeet(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
422pointing to OTT dispatch (not a retryable error). - Safer input compatibility — per-commodity
dimsanddimensionsare accepted as aliases fordimsIn; common accessorial labels are normalized to OTT codes; malformed or conflicting values return structured field errors.
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) onPOST /quotes; each line rates with its own freight class and the top-levelweightLbs/pieces/freightClassbecome optional aggregates. When present,commodities[]is the rating source of truth. - Per-commodity NMFC — each line accepts an
nmfcitem number (also available as a single-commoditynmfc). Prints on the BOL; does not affect price. commodities[].dimsInvalidation — dimensions live only inside a per-linedimsInobject ({ 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 with422naming the offending field — before any carrier call.- Booking
notesandcustomerReferenceonPOST /quotes/{id}/book—customerReference(your PO, echoed on shipment reads) andnotes(special-handling text printed on the OTT BOL).
- Initial public v1:
POST /quotes,GET /quotes/{id},POST /quotes/{id}/book,GET /shipments/{pro}, andGET /shipments/{pro}/tracking. - Bearer API-key auth with account-inherited scopes, RFC 7807 errors with a stable
code, per-client rate limits, andX-Request-Idon 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.
