# OnTime Trucking API

Source: https://ontimetrucking.com/technology/api
Version: 1.5.0

Programmatic access to OnTime Trucking freight data: rate quotes, shipment details, live tracking, and documents. Authenticate with an API key as a Bearer token. All errors use RFC 7807 problem+json with a stable `code` and an echoed `requestId`.

## Servers

- `https://ontimetrucking.com/api/v1` — Production
- `https://ott-portal-dev.up.railway.app/api/v1` — Dev / sandbox (use ott_test_… keys)

## Authentication

API key minted in the portal (ott_live_… / ott_test_…), sent as `Authorization: Bearer <key>`.

## Endpoints

### POST /quotes

**Create a rate quote**

Scope: `quote:read`
Rate limit: api_rate (20 / 10 min / client)

Price a shipment and get transit days. Returns {quoteId, options[{carrierOptionId,carrierName,totalUSD,transitDays}], expiresAt}. Pass the chosen carrierOptionId unchanged to POST /quotes/{id}/book so the selected standard or guaranteed tier is persisted atomically. Each total is your final all-in price — nothing to add on top. Pass `guaranteed: true` to also receive `guaranteedOptions[]` (same shape, cheapest-first) for carriers offering date-certain delivery; omitted when none qualify or the shipment is residential. `expiresAt` is an informational UTC (ISO-8601, trailing Z) timestamp indicating how long the quoted rates are good — it is advisory for display, but booking IS gated: an unbooked quote older than the validity window (default 3 days) is refused at POST /quotes/{id}/book with 409 conflict — request a fresh quote to book at current pricing. The saved quote itself stays retrievable by id via GET /quotes/{id}.

Request body:
- `originZip` (string) **(required)** — 5-digit US ZIP (or CA/MX postal).
- `destZip` (string) **(required)** — 5-digit US ZIP (or CA/MX postal).
- `originCountry` (string) — one of: US, CA, MX
- `destCountry` (string) — one of: US, CA, MX
- `weightLbs` (number) **(required)** — Total shipment weight.
- `pieces` (integer) — Handling-unit / pallet count.
- `freightClass` (string) — NMFC freight class, e.g. "100". Drives the rate.
- `nmfc` (string) — Optional NMFC item number for the shipment (single-commodity path). Prints on the BOL; does NOT affect the rate (freightClass does). For mixed-class loads use commodities[].nmfc instead.
- `ratingMode` (string) — Rating mode. STANDARD = class/density LTL (default). VOLUME = spot/volume rating, priced off trailer floor space (linearFeet) via the carriers' volume endpoints. VOLUME requires linearFeet. — one of: STANDARD, VOLUME
- `linearFeet` (integer) — Trailer floor space in linear feet. REQUIRED when ratingMode=VOLUME (bounded 8–28). Ignored for STANDARD.
- `guaranteed` (boolean) — Request guaranteed (date-certain) delivery options in addition to standard. When true, the response includes guaranteedOptions[] (cheapest-first) for carriers that offer it. Not available on residential shipments (RESD/RESP).
- `commodities` (array<object>) — Optional per-commodity line items for mixed-class / multi-pallet shipments. When present, this is the source of truth for rating and each line is rated with its own class; the top-level weightLbs/pieces/freightClass become aggregates and may be omitted. When absent, the flat weightLbs/pieces/freightClass fields are used (single commodity). Max 30 lines; total weight <= 30000 lbs.
- `accessorials` (array<object>) — Optional neutral accessorial codes. Pickup: INPU, LIFO, LAPU, TRPU, RESP, IBFD. Delivery: INDE, LIFD, LADL, NTFN, TRDS, RESD, GWHD. Hazmat: HAZD. Friendly labels are normalized; exact aliases are listed in the item schema, and directionless labels default to delivery. Runtime matching is case-insensitive and normalizes separators (including spaces, hyphens, and underscores), so equivalent case/separator variants are accepted. Unknown values return 422. Fees are folded into each option's totalUSD (never itemized separately).

Responses:
- `201` — Quote created
- `401` — Missing/invalid/revoked/expired key
- `403` — Insufficient scope or no tenant scope
- `422` — Validation failed (bad PRO, bad input)
- `429` — Rate limit exceeded; see Retry-After header
- `503` — Upstream carrier temporarily unavailable; see Retry-After

---

### GET /quotes/{id}

**Get a quote by id**

Scope: `quote:read`

Fetch a saved quote by its API id (the quoteId from POST /quotes). For convenience the customer-facing carrier "Quote #" (tforceQuoteNumber) is also accepted; the API id is authoritative and matched first.

Parameters:
- `id` (path, string) **(required)**

Responses:
- `200` — Quote
- `401` — Missing/invalid/revoked/expired key
- `403` — Insufficient scope or no tenant scope
- `404` — Not found / cross-tenant (no existence leak)

---

### POST /quotes/{id}/book

**Book a quote (assign PRO + schedule pickup)**

Scope: `shipment:write`
Rate limit: api_default (60 / min / client)

Book a saved quote in ONE call: creates the BOL, assigns a PRO, and schedules the pickup. Requires the shipment:write scope. IDEMPOTENT — a retry or a call on an already-booked quote returns the existing booking (200) instead of double-booking. Provide the pickup date + window; ship-from/ship-to are reused from the saved quote unless overridden in the body (a complete address + 10-digit phone is required on both ends). On some shipments the carrier PRO is assigned shortly after booking rather than instantly — `pro` is null until then and appears on GET /quotes/{id} and GET /shipments once assigned; the booking and pickup are confirmed either way. `id` accepts the API quote id (authoritative) or the carrier Quote #.

Parameters:
- `id` (path, string) **(required)**

Request body:
- `pickupDate` (string) **(required)** — YYYY-MM-DD, not in the past.
- `readyTime` (string) **(required)** — HH:MM 24h — earliest ready time.
- `closeTime` (string) **(required)** — HH:MM 24h — dock close; must be after readyTime.
- `shipFrom` (object) — Optional pickup-address override; reuses the quote/owner profile when omitted.
- `shipTo` (object) — Optional consignee override; reuses the quote's stored consignee when omitted.
- `requester` (object) — Optional pickup requester; defaults to the saved physical shipper/dock contact, with bill-to only as a last-resort fallback.
- `notes` (string) — Optional free-text booking notes (special handling / pickup instructions). Stored on the shipment and printed on the OTT BOL.
- `customerReference` (string) — Legacy generic reference retained for existing integrations. New integrations should send poNumber and pickupReference separately so each reaches the correct carrier field.
- `poNumber` (string) — Customer PO number. TForce receives a structured PO reference; Saia, XPO, and Old Dominion receive it in carrier/driver instructions.
- `pickupReference` (string) — Customer or dock pickup reference. Send this field whenever the shipper uses one. TForce receives it in the native Reference Number as type 'Pickup Reference number', on the BOL as a shipper reference, and in driver instructions. Saia, XPO, and Old Dominion receive it in carrier/driver instructions.
- `carrierOptionId` (string) — The exact carrierOptionId returned by POST /quotes. Selects and atomically persists that option before booking. Required when choosing a non-default or guaranteed option; stale/unknown ids return 409.

Responses:
- `200` — Already booked — existing booking returned. Reference fields are the original persisted values, not replacement values from the replay body.
- `201` — Booked — new booking created. Reference fields are the values persisted and sent through booking.
- `401` — Missing/invalid/revoked/expired key
- `403` — Insufficient scope or no tenant scope
- `404` — Not found / cross-tenant (no existence leak)
- `409` — Conflict: the quote is already being booked, its pricing changed concurrently, or a stale or unknown carrier option id was supplied.
- `422` — Validation failed (bad PRO, bad input)
- `429` — Rate limit exceeded; see Retry-After header
- `503` — Upstream carrier temporarily unavailable; see Retry-After

---

### GET /shipments/{pro}

**Get shipment details by PRO**

Scope: `shipment:read`

Returns {pro,status,originZip,destZip,weightLbs,freightClass,dims,pieces,nmfc,accessorials} plus per-commodity rows for multi-commodity shipments. Scoped read; PRO must be a valid carrier tracking number (8-12 digits; ArcBest may add 1-2 letters).

Parameters:
- `pro` (path, string) **(required)**

Responses:
- `200` — Shipment details
- `401` — Missing/invalid/revoked/expired key
- `403` — Insufficient scope or no tenant scope
- `404` — Not found / cross-tenant (no existence leak)
- `422` — Validation failed (bad PRO, bad input)

---

### GET /shipments/{pro}/tracking

**Get live tracking for a shipment**

Scope: `tracking:read`

Returns the carrier tracking timeline for a shipment you own (carrier resolved from the booking). Returns 503 + Retry-After on a carrier brownout.

Parameters:
- `pro` (path, string) **(required)**

Responses:
- `200` — Tracking timeline
- `401` — Missing/invalid/revoked/expired key
- `403` — Insufficient scope or no tenant scope
- `404` — Not found / cross-tenant (no existence leak)
- `422` — Validation failed (bad PRO, bad input)
- `429` — Rate limit exceeded; see Retry-After header
- `503` — Upstream carrier temporarily unavailable; see Retry-After

---

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

**Get a shipment document (BOL, POD, or Invoice) for your own shipment**

Scope: `shipment:read`
Rate limit: api_docs (30 / 5 min / client)

Retrieve a document for a shipment you own. `bol` returns the OnTime Trucking Bill of Lading as a printable HTML document (text/html) by default, or as application/pdf with `?format=pdf`. `pod` returns the carrier Proof of Delivery — a signed delivery receipt — as application/pdf (streamed, no-store, size-capped). `invoice` returns the OnTime Trucking customer invoice as application/pdf once the shipment is booked (customer price only — never carrier cost or margin). All responses are no-store.

Parameters:
- `pro` (path, string) **(required)**
- `type` (path, string) **(required)** — bol = OnTime Bill of Lading (HTML, or PDF with ?format=pdf); pod = carrier Proof of Delivery (PDF); invoice = OnTime customer invoice (PDF, booked shipments only).
- `format` (query, string) — Only for type=bol: 'pdf' returns application/pdf; omit (default 'html') returns text/html. Ignored for pod/invoice (always PDF).

Responses:
- `200` — The document — HTML for bol, PDF for pod and invoice.
- `401` — Missing/invalid/revoked/expired key
- `403` — Insufficient scope or no tenant scope
- `404` — Not found / cross-tenant (no existence leak)
- `422` — Validation failed (bad PRO, bad input)
- `429` — Document service quota exceeded (Retry-After)
- `503` — Document service temporarily unavailable (Retry-After)

---

_Generated from the live OpenAPI 3.1 contract at https://ontimetrucking.com/api/openapi.json._