# Freight (LTL)

Freight endpoints let you quote, book, track, and manage **less-than-truckload (LTL)** shipments — the freight that is too large to ship as a parcel but does not fill a trailer on its own. A pallet of furniture, a crate of machine parts, or a few drums of coating all travel as LTL.

LTL works differently from parcel shipping, and the API reflects that:

- **You quote before you book.** LTL prices are not published rate cards; they are offers from carriers for your specific freight on a specific lane. Every booking references an offer returned by a quote.
- **You ship handling units, not packages.** The carrier handles pallets, crates, or drums, and each one contains one or more **commodities** — the actual goods, each with its own freight class.
- **Extra services are priced in.** Liftgates, inside delivery, and appointment scheduling are **accessorials**, and they change the price of the quote.
- **The Bill of Lading is the shipping document.** Booking generates a Bill of Lading (BOL) rather than a label, along with pallet labels and other paperwork.


## The Workflow

1. [List freight provider accounts](/apis/shipengine/docs/freight/freight-quote-book#list-freight-provider-accounts) — look up the `freight_provider_account_id` of the freight provider connection to work through.
2. [Get freight quotes](/apis/shipengine/docs/freight/freight-quote-book#get-freight-quotes) — describe the freight and receive offers from the carriers available through your freight provider.
3. [Book a freight shipment](/apis/shipengine/docs/freight/freight-quote-book#book-a-freight-shipment) — book one of those offers and get back a BOL number and documents.
4. [Download the documents](/apis/shipengine/docs/freight/freight-manage#list-freight-shipment-documents) — print the Bill of Lading and pallet labels for the driver.
5. [Track the shipment](/apis/shipengine/docs/freight/freight-manage#get-freight-shipment-tracking) — follow it from pickup to delivery.


## Requirements

- **An active freight provider connection.** Freight is quoted and booked through a freight provider account connected to your account, identified by a `freight_provider_account_id`. Every freight request requires one, and requests that reference an inactive or unknown connection return a `400 Bad Request`.
- **An API key.** All requests are authenticated with your [API key](/apis/shipengine/docs/guides/auth#api-keys), included in the `API-Key` header of every request.


Finding your freight_provider_account_id
Freight provider connections are set up in the ShipStation API dashboard. Call [List freight provider accounts](/apis/shipengine/docs/freight/freight-quote-book#list-freight-provider-accounts) to retrieve the `freight_provider_account_id` of each active connection. Like most ShipStation API identifiers, it is an `se-` prefixed string such as `se-4821`.

## Two Ways to Describe a Shipment

Both [Get freight quotes](/apis/shipengine/docs/freight/freight-quote-book#get-freight-quotes) and [Book a freight shipment](/apis/shipengine/docs/freight/freight-quote-book#book-a-freight-shipment) accept the shipment in one of two ways.

| Mode | How you use it | What you send |
|  --- | --- | --- |
| **Inline** | You are quoting freight that does not yet exist as a shipment in ShipStation API. | `ship_from`, `ship_to`, and `handling_units`. Booking inline creates a shipment record for you and marks it as shipped. |
| **Linked to a shipment** | The freight already exists as a shipment on your account, created through [Create a Shipment](/apis/shipengine/docs/shipping/create-a-shipment) or an order source. | `shipment_id`. The origin comes from the shipment's ship-from warehouse and the destination from the order's ship-to address, so `ship_from` and `ship_to` must be omitted. |


In the linked mode, the freight details you send when quoting — `handling_units`, `accessorials`, and `insurance` — are saved as the shipment's freight configuration. Booking that shipment reuses the saved configuration, so `handling_units` must be omitted when booking. Quote a shipment before you book it: booking a `shipment_id` that has no saved freight configuration returns a `400 Bad Request`.

## Units of Measure

The freight endpoints use a single fixed unit for each measurement. There are no unit fields to set.

| Measurement | Unit |
|  --- | --- |
| Dimensions (`length`, `width`, `height`) | Inches |
| Weight (`weight`) | Pounds |
| Money (`total_charges`, `insured_value`) | US dollars |
| Temperature (`flashpoint_temperature`) | Degrees Fahrenheit |


## Handling Units and Commodities

A **handling unit** is what the carrier physically moves and counts. A **commodity** is a line of goods inside it.


```json
{
  "handling_units": [
    {
      "type": "pallet",
      "quantity": 2,
      "length": 48,
      "width": 40,
      "height": 52,
      "stackable": false,
      "commodities": [
        {
          "description": "Assembled oak dining chairs",
          "quantity": 24,
          "weight": 310,
          "freight_class": "125",
          "packaging_type": "carton"
        }
      ]
    }
  ]
}
```

This describes **two identical pallets**, each 48 × 40 × 52 inches, each holding 24 cartons of chairs weighing 310 lb in total. Dimensions, weight, and commodities always describe **one** handling unit — the carrier multiplies by `quantity`.

`stackable` matters more than it looks: non-stackable freight occupies trailer height that cannot be sold to anyone else, and carriers price accordingly.

### Freight Class

Every commodity needs a `freight_class` — an NMFTA classification from `50` (dense, durable, cheap to ship) to `500` (light, bulky, or fragile). Class is derived from density, stowability, handling, and liability, and it is one of the largest factors in the price of an LTL shipment.

Valid values are `50`, `55`, `60`, `65`, `70`, `77.5`, `85`, `92.5`, `100`, `110`, `125`, `150`, `175`, `200`, `250`, `300`, `400`, and `500`. Send them as strings.

Understating freight class is the most common cause of a post-delivery invoice adjustment. Carriers reweigh and reclassify freight, and the corrected charge is billed back to you. If you are unsure of a class, check with your carrier or freight provider before quoting.

## Accessorials

Accessorials are extra services, and they are priced into the quote. You send them **only on the quote request** — there is no `accessorials` field on the booking request. The offer you book already carries the accessorials it was priced with, which is why `offer_id` and `quote_request_id` are all the booking needs to identify what you are buying.

So get them right before you quote. Adding a liftgate after the fact means requesting fresh quotes and booking a new offer; there is no way to attach an accessorial to an offer that was priced without it.

| Accessorial | When you need it |
|  --- | --- |
| `liftgate_pickup`, `liftgate_delivery` | The location has no loading dock, so the driver needs a liftgate to raise or lower the freight. |
| `inside_pickup`, `inside_delivery` | The freight must be moved into or out of the building rather than left at the dock or curb. |
| `appointment_delivery` | The consignee requires a scheduled delivery appointment. |
| `notify_before_delivery` | The carrier must call ahead before delivering. |
| `hold_at_terminal` | The consignee collects the freight from the destination terminal. |
| `carrier_terminal_pickup` | You drop the freight at the carrier's terminal instead of having it collected. |
| `sort_and_segregate` | The carrier must sort or separate the freight at delivery. |
| `protection_from_cold`, `protection_from_heat` | The freight must not freeze or overheat in transit. |
| `grocery_consolidation_pickup`, `grocery_consolidation_delivery` | The origin or destination is a grocery consolidation facility. |
| `tradeshow_pickup`, `tradeshow_delivery` | The freight is collected from or delivered to a tradeshow. Provide the tradeshow `name` and `booth_number`. |


### Location Type

Also set `location_type` when the location is not a plain commercial address. Residential, limited-access, construction, and similar locations carry their own surcharges, and declaring them up front keeps the quote accurate. It appears in three places: on `ship_from` and `ship_to` when quoting, and on `pickup_details` when booking.

`airport`, `carrier_terminal`, `commercial`, `construction`, `container_freight_station`, `distribution_center`, `government_facility`, `limited_access`, `pier_port_wharf`, `residential`, `secured_access`, `trade_show`

The value is validated against that list, so a typo is rejected with a `400 Bad Request` rather than quietly dropped. Omitting the field entirely is always allowed.

pickup_details.location_type on a linked shipment
When you book with a `shipment_id`, `ship_from` is not part of the request, so `pickup_details.location_type` is the only way to tell the carrier how to classify the origin. Set it there if the pickup location is not a plain commercial address.

## Insurance

Carrier liability is capped per pound and is usually far below what the freight is actually worth — the `max_liability_new` and `max_liability_used` values on each quote show the cap for that offer. Add an `insurance` object to the quote to price cargo insurance alongside the freight charges:


```json
{
  "insurance": {
    "insured_value": 18500,
    "item_condition": "new",
    "commodity_category": "furniture",
    "marks_numbers": "NG-2026-0417"
  }
}
```

Offers then include `insured_amount`, `insurance_premium` (already part of `total_charges`), and an `insurance_certificate_number` once the insurer issues one. Insured values are in USD.

The presence of the `insurance` object is what requests insurance — there is no boolean to set. Two consequences worth knowing:

- **Always send `insured_value`.** It is not validated, so an `insurance` object without it requests insurance with a declared value of `0` rather than returning an error.
- **Insurance is priced at quote time**, like accessorials. There is no `insurance` field on the booking request, so adding coverage after the fact means re-quoting.


## Hazardous Materials

A commodity carrying a `hazardous_materials` object is flagged as hazmat, and the details are printed on the Bill of Lading. Hazmat freight requires the full set of regulatory fields:


```json
{
  "description": "Paint, flammable",
  "quantity": 8,
  "weight": 420,
  "freight_class": "70",
  "hazardous_materials": {
    "identification_number_type": "un",
    "identification_number": "UN1263",
    "proper_shipping_name": "Paint",
    "hazard_class": "3",
    "subsidiary_hazard_classes": ["8"],
    "packing_group": "iii",
    "emergency_contact_name": "Chemtrec",
    "emergency_contact_phone": "+1 800 424 9300",
    "emergency_response_reference": "CCN12345",
    "flashpoint_temperature": 73
  }
}
```

- `identification_number_type` is `un` or `na`.
- `packing_group` is `i`, `ii`, `iii`, or `none`.
- `subsidiary_hazard_classes` must not contain duplicates or repeat the primary `hazard_class`.
- `emergency_contact_name` and `emergency_contact_phone` must reach a 24-hour response service.


See the [Full API Reference](/apis/shipengine/openapi) for the full list of accepted hazard classes.

## Statuses

Freight shipments carry a coarse `status` and, when tracking, a finer-grained `tracking_status` from the carrier.

| `status` | Meaning |
|  --- | --- |
| `pending` | The shipment exists but is not yet with the carrier. |
| `quoted` | Offers have been retrieved but none has been booked. |
| `booked` | The carrier has accepted the shipment and a Bill of Lading exists. |
| `in_transit` | The freight is moving, from dispatch through to out for delivery. |
| `delivered` | The freight has been delivered. |
| `cancelled` | The booking was cancelled. |
| `exception` | Something went wrong, or the status could not be determined. |


Several carrier tracking statuses roll up into one shipment status. `dispatched`, `in_route_to_pickup`, `at_pickup`, `out_for_delivery`, and `at_delivery` all appear as `in_transit`. Read `tracking_status` when you need the detail.

Response fields with no value are returned as `null` rather than omitted. A freshly booked shipment, for example, has no `pro_number` until the carrier assigns one, so the field is present with a value of `null`. Check for `null` rather than for the absence of a key.

The JSON examples on these pages are abbreviated to the fields being discussed, so a real response will usually contain more keys than the example shows.