FreightCake
Walkthroughs

Rate shop an LTL shipment

Quote a freight shipment across every carrier in your network, compare landed cost, and pick the winner.

This walkthrough takes a single shipment request — origin, destination, items — and returns a ranked list of carrier quotes. By the end you’ll be able to:

  • Submit a multi-item LTL rate request
  • Read the response envelope and per-carrier quote shape
  • Filter by service level, transit time, and accessorials
  • Pick a winning quote ID to feed into Create a shipment

The full request

import { fc } from './freightcake'

const result = await fc.quotes.create({
  origin: {
    city: 'Chicago',
    state: 'IL',
    zip: '60601',
  },
  destination: {
    city: 'Los Angeles',
    state: 'CA',
    zip: '90001',
  },
  items: [
    {
      description: 'Pallet of widgets',
      weight: 500, // lbs
      freight_class: '70',
      length: 48, // in
      width: 40,
      height: 48,
      pieces: 1,
      packaging: 'pallet',
    },
  ],
  accessorials: ['liftgate_destination', 'residential_destination'],
})

console.log(`${result.data.length} carriers quoted`)
for (const quote of result.data) {
  console.log(
    `${quote.carrier_name} — $${(quote.net_charge_cents / 100).toFixed(2)} — ${quote.delivery_estimate}`,
  )
}

Equivalent raw fetch

const res = await fetch('https://api.freightcake.com/api/v1/quotes', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.FREIGHTCAKE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    origin: { city: 'Chicago', state: 'IL', zip: '60601' },
    destination: { city: 'Los Angeles', state: 'CA', zip: '90001' },
    items: [
      {
        weight: 500,
        freight_class: '70',
        length: 48,
        width: 40,
        height: 48,
      },
    ],
    accessorials: ['liftgate_destination', 'residential_destination'],
  }),
})

if (!res.ok) {
  throw new Error(`Quote failed: ${res.status} ${await res.text()}`)
}
const result = await res.json()

Reading the response

A successful response is a list envelope of quote objects:

{
  "object": "list",
  "url": "/v1/quotes",
  "data": [
    {
      "object": "quote",
      "id": 1042,
      "carrier_name": "Sandbox Freight Co",
      "carrier_scac": "SBFC",
      "service_level": "Standard LTL",
      "net_charge_cents": 17500,
      "fuel_surcharge_cents": 4200,
      "accessorial_charges_cents": 8500,
      "total_charge_cents": 30200,
      "delivery_estimate": "3-5 business days",
      "transit_days_min": 3,
      "transit_days_max": 5,
      "expires_at": "2026-05-22T00:00:00.000Z"
    }
  ],
  "has_more": false
}

Notable fields:

  • total_charge_cents is the landed cost including fuel and accessorials. This is the number to compare across carriers — net_charge_cents alone is misleading.
  • expires_at defaults to ~24 hours. Re-quote anything older than its expiration before booking.
  • transit_days_min / transit_days_max come from carrier published transit matrices; they exclude pickup day and weekends.

Picking a winner

The simplest strategy — cheapest landed cost — is one line:

const winner = result.data
  .slice()
  .sort((a, b) => a.total_charge_cents - b.total_charge_cents)[0]

For production traffic, weight by carrier scorecard data:

const winner = result.data
  .map((q) => ({
    quote: q,
    score:
      q.total_charge_cents +
      // Penalize slow transit by ~$10 per extra day.
      (q.transit_days_max ?? 5) * 1000 +
      // Penalize carriers below 95% on-time over the trailing 90 days.
      (q.carrier_on_time_pct && q.carrier_on_time_pct < 0.95 ? 5000 : 0),
  }))
  .sort((a, b) => a.score - b.score)[0].quote

Pass winner.id straight into the next step.

Common gotchas

  • Freight class is required for LTL. If you don’t know the class, use the class lookup endpoint or the SDK helper fc.quotes.estimateClass({ density }).
  • Residential addresses change the carrier list. A residential delivery filters out carriers that only run commercial freight. Always set residential_destination if applicable — surprise reclasses get expensive.
  • Quotes expire. Trying to book against an expired quote returns quote_expired. Re-quote and book in one user session.

Next steps

On this page