# Rate shopping

URL: https://docs.lastscanned.com/rates

`/v1/rates` asks every source we quote from for its price to ship one parcel between two postal codes, and returns the quotes side by side, cheapest first, with delivery days and the surcharges itemized. Nothing is bought: a quote is information only.

Every quote says where its price came from (`source`) and what kind of price it is (`rateType`):

| `rateType` | Meaning |
|---|---|
| `retail` | The carrier's own counter or website price. |
| `list` | The carrier's published list rate. |
| `reseller` | A postage reseller's own price, usually below retail. You get it by shipping through that reseller. |

Today's source is **Stamps.com** (`source: "stamps"`): USPS, UPS and FedEx at Stamps.com's prices, for **US domestic** shipments. More sources, including carriers' own retail rates, are being added. Stamps.com's FedEx rates are sometimes missing; when a source leaves out a carrier it covers, `errors` says so.

People can use it at **[lastscanned.com/rates](https://lastscanned.com/rates)**; agents call the same thing over the API or MCP.

---

## Request

```bash
curl "https://api.lastscanned.com/v1/rates?from=94107&to=10001&weight=2.5&residential=true" \
  -H "Authorization: Bearer lsk_live_..."
```

Or `POST /v1/rates` with the same fields as JSON:

```bash
curl -X POST https://api.lastscanned.com/v1/rates \
  -H "Authorization: Bearer lsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"from": "94107", "to": "10001", "weight": 2.5, "length": 12, "width": 10, "height": 6, "residential": true}'
```

| Field | Required | Notes |
|---|---|---|
| `from` | yes | Origin postal code. In the US, a 5-digit ZIP (ZIP+4 is cut to 5). |
| `to` | yes | Destination postal code. |
| `fromCountry`, `toCountry` | no | Two-letter country codes. Default `US`. |
| `weight` | yes | Above 0, at most 150 lb. |
| `weightUnit` | no | `lb` (default), `oz`, `kg` or `g`. |
| `length`, `width`, `height` | no | All three or none; each at most 108 in. Carriers price large, light boxes by size. |
| `dimensionUnit` | no | `in` (default) or `cm`. |
| `residential` | no | `true` when delivering to a home. Default `false`. |
| `carriers` | no | Only these carrier ids (comma-separated, or an array in JSON). Omit for every carrier we can quote. |

In JSON, `from` and `to` may also be objects: `{"postalCode": "SW1A 1AA", "country": "GB", "residential": true}`.

## Response

| Field | Meaning |
|---|---|
| `quotes[]` | One per carrier service, cheapest first. |
| `quotes[].carrier` | Our carrier id, the same one `GET /v1/track` uses. |
| `quotes[].service`, `serviceCode` | The service's name and the source's code for it. When a price needs carrier packaging, `:<packaging>` is appended to the code. |
| `quotes[].amount`, `currency` | The quoted total, surcharges included. |
| `quotes[].breakdown[]` | `{description, amount}`: the parts of the total as the source itemizes them (base price, fuel, residential, demand surcharges). |
| `quotes[].rateType`, `source` | What kind of price it is (above), and where it came from. |
| `quotes[].packaging` | Carrier packaging the price requires, such as `small_flat_rate_box` or `fedex_one_rate`; `null` for your own box. |
| `quotes[].restricted` | What the service may carry when it is limited (Media Mail: books and media only), else `null`. |
| `quotes[].deliveryDays`, `estimatedDelivery` | Business days and date (`YYYY-MM-DD`) the carrier quotes, or `null`. |
| `quotes[].guaranteed` | Whether the carrier guarantees the date. |
| `cheapest`, `fastest` | `source:carrier:serviceCode` of the lowest price and of the fewest quoted days, for your own box: restricted services and prices that need carrier packaging are never picked. |
| `errors[]` | `{source, carriers, code, message}` for each source that couldn't quote (`carrier_error`, `timeout`, `not_covered` for a route it doesn't serve), each carrier a source left out (`no_rates`), and carriers no source covers (`source: null`). One failing never fails the rest. |
| `quotedAt` | When the quotes were made. A shipment is re-quoted at most every 6 hours. |

When **no** carrier could quote, the answer is `502 no_quotes` with the same `errors`, and nothing is spent.

Quotes in different currencies are never ranked against each other: they are grouped by currency, then sorted by price.

## Cost

A quote request costs the same as tracking one number:

- **No key:** 5 free quotes a day per IP (not from cloud or hosting networks).
- **API key:** one lookup of the account's day.
- **MPP:** one credit, the same credits tracking spends.

## MCP

The `get_rates` tool takes the same fields in snake_case (`from`, `to`, `weight`, `weight_unit`, `from_country`, `to_country`, `residential`, `length`, `width`, `height`, `dimension_unit`, `carriers`) and returns the same JSON. It is listed only while a rate source is on.
