TruckTabs API · v1
A quote is priced by the same engine as trucktabs.com. It works out the states on the route, adds oversize/overweight permits where the load is over a state's legal limits, and returns every fee.
POSThttps://trucktabs.com/api/v1/quotes
Needs a write key. Every quote becomes a draft order on our side, which is what lets the carrier pay from resume_url, so send an Idempotency-Key if you might retry.
| Field | Type | Required | Notes |
|---|---|---|---|
| permit_types | array | Yes | Any of trip, fuel, osow. Oversize/overweight is added by us in each state where the load needs it, so you rarely ask for it. |
| route.origin | place | Yes* | Where the trip starts. See Places below. |
| route.destination | place | Yes* | Where the trip ends. |
| route.states | array | Yes* | Instead of origin and destination: the states crossed, in order, each { "state": "OR", "miles": 210 }. Use it when your system already has the miles. The miles are yours: we price what you send, in whole miles (at least 1 a state). |
| route.stops | array | No | Not supported yet. A request with stops is refused with UNSUPPORTED rather than priced wrong. |
| vehicle.vehicle_type | code | Yes | For example tractor_trailer. See vehicle types. |
| vehicle.trailer_type | code | Semis | For example step_deck. See trailer types. |
| vehicle.trailer_length | measure | Semis | The trailer length. At least 10 ft. |
| vehicle.power_unit_axles | integer | Semis | Axles on the tractor or truck. |
| vehicle.trailer_axles | integer | Semis | Axles on the trailer. |
| vehicle.total_axles | integer | Single units | Every vehicle type other than tractor_trailer and truck_trailer. |
| vehicle.combined_length | measure | Single units | The vehicle's overall length. At least 10 ft. |
| vehicle.deck_height | measure | No | Open decks: how high the load sits off the road. Only with load.cargo_height (refused with load.height); left out, we use a typical deck for the trailer type (60 in flatbed, 42 in step deck, 24 in lowboy, RGN and double drop). Where there is no typical deck, such as Power Only, it is listed in details.missing. |
| vehicle.empty_weight_lbs | integer | No | The truck and trailer, empty. Only with load.weight_lbs (refused with gross_weight_lbs); left out, we use a typical figure for the trailer type, or list it in details.missing where there is none (single vehicles, a pickup pulling a trailer). |
| load.gross_weight_lbs | integer | Yes* | Gross weight in pounds, with the truck and trailer. Or send load.weight_lbs instead. A gross well below what the trailer type weighs empty is refused as the load's weight alone. |
| load.weight_lbs | integer | Yes* | The load on its own. The gross is this plus vehicle.empty_weight_lbs. Send this or gross_weight_lbs, not both. |
| load.length | measure | Sometimes | Length, width and height are required when the load could be oversize (for example an open trailer or an osow request). A missing one is listed in details.missing. |
| load.width | measure | Sometimes | Decides whether a state needs an oversize permit. |
| load.height | measure | Sometimes | Overall height from the road, at least 24 in, trailer included. Legal height differs by state, so this can change the answer at a state line. Or send load.cargo_height instead. |
| load.cargo_height | measure | Sometimes | The load on its own. The overall height is this plus vehicle.deck_height (or, on an enclosed trailer, the trailer's height; on a single vehicle it is taken as the overall height). Send this or load.height, not both. A piece's own height sent as load.height makes a tall load look legal. |
| load.category | code | No | See load categories. |
| load.description | string | No | Up to 500 characters, for example "Excavator". |
| external_ref | string | No | Your own reference, up to 64 characters. Echoed back. |
| metadata | object | No | Up to 10 keys of 1 to 40 characters. Values are strings of up to 200 characters, numbers, booleans or null. Echoed back, never read by us. |
* Send route.origin and route.destination, or route.states, not both. A measure is { "value": 48, "unit": "ft" } with a unit of ft or in and at most 2 decimal places. A value far too big or too small for its field, such as a length over 500 feet or a 13-inch height, is refused as a likely unit slip. Codes also accept the display names and common aliases shown in the reference, in any case.
A place is an object in one of these forms. To choose between similar names before quoting, use GET /places.
| Field | Type | Required | Notes |
|---|---|---|---|
| city + state | string | No | { "city": "Portland", "state": "OR" }. Only a match in that state counts. Add postal_code to pick between places of the same name in that state. |
| postal_code | string | No | { "postal_code": "97201" }. Only a place with that ZIP code counts. If none has it, the quote is refused with PLACE_NOT_FOUND. |
| lat + lng + state | number | No | { "lat": 45.52, "lng": -122.68, "state": "OR" }. Used as given, once we have checked the point with Mapbox. Coordinates far outside the United States are refused with INVALID_INPUT. A point that isn't inside that state, or isn't in any US state, is refused with PLACE_MISMATCH. |
| query | string | No | { "query": "Portland, OR" }. Free text. A state code or ZIP code at the end narrows it. If it matches places in more than one state and nothing narrows it, the quote is refused with the candidates. |
curl https://trucktabs.com/api/v1/quotes \
-H "Authorization: Bearer $TRUCKTABS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @quote.json{
"permit_types": ["trip", "fuel"],
"route": {
"origin": { "city": "Portland", "state": "OR", "postal_code": "97201" },
"destination": { "city": "Boise", "state": "ID" }
},
"vehicle": {
"vehicle_type": "tractor_trailer",
"trailer_type": "step_deck",
"trailer_length": { "value": 48, "unit": "ft" },
"power_unit_axles": 3,
"trailer_axles": 2
},
"load": {
"category": "construction_farm_equipment",
"gross_weight_lbs": 92000,
"length": { "value": 38, "unit": "ft" },
"width": { "value": 10, "unit": "ft" },
"height": { "value": 13, "unit": "ft" },
"description": "Excavator"
},
"external_ref": "LOAD-284113",
"metadata": { "shipment_id": "284113" }
}Amounts, descriptions and miles in these examples are illustrative, not current rates.
{
"id": "5b0f6c1e-8a7d-4c1e-9f3a-2d6b7c8e9f01",
"reference": "48213",
"status": "quoted",
"currency": "USD",
"subtotal": 424,
"processing_fee": 21.2,
"total": 445.2,
"states": [
{
"state": "OR",
"state_name": "Oregon",
"miles": 211,
"permits": ["trip", "fuel", "osow"],
"amount": 159,
"osow_auto_applied": true,
"osow_not_needed": false,
"osow_reasons": ["width"],
"lines": [
{ "kind": "state_fee", "permit": "trip", "description": "Oregon Trip Permit", "amount": 43 },
{ "kind": "state_fee", "permit": "fuel", "description": "Oregon Fuel Permit", "amount": 56 },
{ "kind": "state_fee", "permit": "osow", "description": "Oregon OS/OW Permit", "amount": 60 }
]
},
{
"state": "ID",
"state_name": "Idaho",
"miles": 219,
"permits": ["trip", "fuel", "osow"],
"amount": 105,
"osow_auto_applied": true,
"osow_not_needed": false,
"osow_reasons": ["width"],
"lines": [ … ]
}
],
"fees": [
{ "kind": "service_fee", "description": "TruckTabs service fee — OR, ID", "amount": 160 },
{ "kind": "processing_fee", "description": "Processing fee (5%)", "amount": 21.2 }
],
"oversize_notice": {
"title": "Oversize/overweight permits added",
"message": "This load is over the legal limits in Oregon and Idaho, so an OS/OW permit is included in each."
},
"advisories": [],
"route": {
"origin": { "label": "Portland, Oregon 97201", "city": "Portland", "state": "OR", "postal_code": "97201", "lat": 45.5152, "lng": -122.6784 },
"destination": { "label": "Boise, Idaho", "city": "Boise", "state": "ID", "postal_code": null, "lat": 43.615, "lng": -116.2023 },
"mileage_source": "route"
},
"repriced": false,
"external_ref": "LOAD-284113",
"metadata": { "shipment_id": "284113" },
"created_at": "2026-09-15T17:42:09.311Z",
"resume_url": "https://trucktabs.com/permits?resume=…"
}A quote our team must look at first comes back as needs_review. That happens for unusual dimensions, very large amounts, or a state we can't price automatically. It has no total, and every amount in the breakdown is null. The carrier can still open resume_url, and nothing is charged until the team confirms the price.
{
"id": "…",
"reference": "48214",
"status": "needs_review",
"currency": "USD",
"estimated_total": 1873.4,
"review_message": "Orders this size get a second look before we charge the card. Submit it and we'll confirm the total with you.",
"states": [
{
"state": "OR",
"amount": null,
"lines": [ { "kind": "state_fee", "permit": "osow", "description": "Oregon OS/OW Permit", "amount": null }, … ],
…
}
],
"fees": [ { "kind": "service_fee", "description": "TruckTabs service fee — OR, ID", "amount": null }, … ],
…
}A legal_load means you asked only for osow and the load is within every state's legal limits, so there is nothing to buy. The carrier can still add OS/OW from the resume link if they want it anyway.
{
"id": "…",
"reference": "48215",
"status": "legal_load",
"currency": "USD",
"total": 0,
"states": [
{ "state": "OR", "permits": [], "amount": 0, "osow_not_needed": true, "lines": [], … }
],
"fees": [],
…
}| Field | Type | Required | Notes |
|---|---|---|---|
| id | string | Always | The quote id, for GET /quotes/{id}. |
| reference | string | Always | Our order number. Short enough for a charge description, and what our team will ask for. |
| status | string | Always | quoted, needs_review or legal_load. On a later read, also ordered or canceled. |
| currency | string | Always | USD. |
| subtotal | number | quoted | State permit fees and service fees. |
| processing_fee | number | quoted | Charged once per order. |
| total | number | quoted, legal_load | What the carrier pays. Never present on needs_review. 0 on legal_load. |
| estimated_total | number | needs_review | Present only when every state priced. An estimate: the team confirms the price before anything is charged. |
| review_message | string | needs_review | Why a person needs to look at it, in a sentence you can show the carrier. |
| states[] | array | Always | Per state: state, state_name, miles, permits, amount (its permit fees, the sum of its lines), osow_auto_applied, osow_not_needed, osow_reasons and lines. With osow asked for, we decide each state: over a limit, OS/OW is added and osow_reasons says which (width, height, length, overhang, weight, or unknown when we have no limits on file); within every limit, osow_not_needed is true and nothing is charged there. |
| load_checked | object | No | What each state was checked against: overall_height_in and gross_weight_lbs, with the parts they came from (deck_height_in, trailer_height_in for an enclosed trailer whose load rides inside, cargo_height_in, empty_weight_lbs, load_weight_lbs; null when you sent the overall figures). Show it to the carrier: their own deck and empty weight can change the answer. The whole block is null once the quote has been re-priced, and absent on quotes made before 2026-10-23. |
| fees[] | array | Always | Order-level lines: service fees and the processing fee. The states' amounts plus these add up to total. |
| amounts on needs_review | No | Every amount in states, lines and fees is null, so nothing adds up to a price the team hasn't confirmed. | |
| oversize_notice | object | No | title and message when we added oversize/overweight permits because the load is over a state's limits, or because a state's limits aren't on file (title “Limits not on file”); otherwise null. Since 2026-10-23 it is set on quotes that asked for oversize/overweight too. |
| advisories[] | array | Always | Plain sentences worth showing the carrier. |
| route | object | Always | The places we priced, and mileage_source: route, straight_line (no drivable route was found; confirmed by hand) or provided (you sent route.states). |
| repriced | boolean | Always | false when created. On a later read, true if the carrier changed the trip since. See Get a quote. |
| resume_url | string | No | A link where the carrier reviews the quote, adds their details and pays. It works like a password to this order: give it only to the carrier. Null once the order is placed or canceled. |
| external_ref, metadata | No | What you sent. | |
| created_at | string | Always | ISO 8601, UTC. |
The response also has a Location header pointing at the quote.
| Status | Code | When |
|---|---|---|
| 422 | INVALID_INPUT | A value is wrong (details.invalid) or missing (details.missing). Both lists are complete. This includes a value too big or small for its field, which is usually a unit slip. |
| 422 | UNSUPPORTED | Something we don't quote yet, such as route.stops. |
| 422 | PLACE_AMBIGUOUS | A place matches more than one state. details.candidates lists them; send the one you mean with its state, or its lat, lng and state. |
| 422 | PLACE_NOT_FOUND | We couldn't find a place, or no place has the postal_code you sent. details.field says which. |
| 422 | PLACE_MISMATCH | Coordinates you sent aren't in the state you sent with them. details.field says which. |
| 422 | NO_STATES | The route doesn't cross any US state we permit. |
| 422 | INCOMPLETE | The quote couldn't be priced with what was sent. The message says what to add. |
| 409 | IDEMPOTENCY_MISMATCH | The Idempotency-Key already created a quote from a different body. |
| 409 | IDEMPOTENCY_IN_PROGRESS | The first request with this key is still running. Retry after a moment. |
| 429 | RATE_LIMITED | Too many writes or quotes this minute. Wait Retry-After seconds. |
| 429 | QUOTE_LIMIT_REACHED | Your daily allowance is used. Every quote that reaches pricing counts. It resets at midnight UTC. |
| 502 / 503 | ROUTING_FAILED, ROUTING_UNAVAILABLE | We couldn't look up places or miles. Retry, or send route.states with miles. |
| 500 / 503 | INTERNAL, QUOTE_FAILED, SAVE_FAILED, UNAVAILABLE | Our problem. Retry with the same Idempotency-Key and you get one quote back. A failure during pricing can leave an unfinished draft on our side, which counts toward the daily allowance. If it persists, send us the request_id. |
{
"error": {
"code": "PLACE_AMBIGUOUS",
"message": "The origin matches places in more than one state. Add the state.",
"details": {
"field": "route.origin",
"candidates": [
{ "label": "Portland, Oregon", "city": "Portland", "state": "OR", "postal_code": null, "lat": 45.5152, "lng": -122.6784 },
{ "label": "Portland, Maine", "city": "Portland", "state": "ME", "postal_code": null, "lat": 43.6591, "lng": -70.2568 }
]
}
},
"request_id": "req_Qm3v8Kx1Tn5bW2pL9sR4"
}GEThttps://trucktabs.com/api/v1/quotes/{id}
Returns a quote your account created, in the same shape as when it was created, with status brought up to date. Nothing is priced again. A quote that belongs to another account is 404 NOT_FOUND.
ordered: the carrier completed the order, or our team took it on. ordered_at is set and resume_url is null.canceled: the order was canceled. canceled_at is set and resume_url is null.resume_url.The carrier can change the trip after opening resume_url, and our team can take an order over. If either moved the price or put the order on hold, repriced is true. The per-state breakdown is still the one you were quoted, with every amount set to null. total is the order's current price, and it is left out while the order is held for review.
curl https://trucktabs.com/api/v1/quotes/5b0f6c1e-8a7d-4c1e-9f3a-2d6b7c8e9f01 \
-H "Authorization: Bearer $TRUCKTABS_API_KEY"