# Get checkout rates

`POST /v2/rates-at-checkout`

**Base URLs:**

- Sandbox: `https://api.sandbox.bobgo.co.za`
- Production: `https://api.bobgo.co.za`

**Authentication:** Bearer token required (see the Authentication page).

## Example request

```bash
curl -X POST 'https://api.sandbox.bobgo.co.za/v2/rates-at-checkout' \
  -H "content-type: application/json" \
  -H "Authorization: Bearer <API_KEY>" \
  -d '{
  "collection_address": {
    "type": "business",
    "company": "Bob Go",
    "street_address": "125 Dallas Avenue",
    "local_area": "Newlands",
    "city": "Pretoria",
    "zone": "Gauteng",
    "country": "ZA",
    "code": "0181"
  },
  "delivery_address": {
    "type": "residential",
    "street_address": "5 Long Street",
    "local_area": "Cape Town City Centre",
    "city": "Cape Town",
    "zone": "Western Cape",
    "country": "ZA",
    "code": "8001"
  },
  "items": [
    {
      "description": "Coffee mug",
      "sku": "MUG-01",
      "price": 200,
      "quantity": 1,
      "length_cm": 17,
      "width_cm": 8,
      "height_cm": 5,
      "weight_kg": 0.4
    }
  ],
  "order_total_price": 200,
  "handling_time": 2
}'
```

Returns the shipping options a buyer should be offered, priced by the rules you
have configured in Bob Go. This is the cart endpoint, not for quoting a
consignment you are about to book. For that, use
[`POST /v2/rates`](../8_Rates/rates.md); the difference is explained in
[Important concepts](../4_Important%20concepts/README.md#rates-at-checkout-vs-rates).

What comes back is shaped entirely by your Bob Go setup: service levels you have
enabled, the zones you serve, weight and order-value bands, markups and
surcharges. Each service level is priced by the kind of rate you configured for
it:

| Configured as | How it is priced |
| --- | --- |
| Dynamic | Priced live off the courier's rate for the lane. |
| Fixed | A flat price you set, regardless of the parcel. |
| Courier | The courier's own price, passed through. |
| Formula | Calculated from a formula you configure. |

This is configuration only. The response does not say which kind priced an
option; its `type` says how the parcel reaches the buyer.

An area you have excluded returns no rate for that lane. When nothing matches,
`rates` comes back as `null` or as an empty array, depending on where the match
failed, so treat the two the same. That is a valid answer, not an error, and
your checkout should handle it by hiding delivery rather than showing a price
of zero.

## Request

Three things are validated: `collection_address`, `delivery_address` and a
non-empty `items` array. The account must also be active, or the call fails
with `Account is not active.` Everything else is optional.

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `collection_address` | object | yes | Where the goods ship from. See [address fields](../4_Important%20concepts/README.md#addresses). |
| `delivery_address` | object | yes | The buyer's address. |
| `items` | array | yes | The cart, at least one entry. See [item fields](#item-fields). |
| `declared_value` | number | no | Total value of the cart in ZAR, drives liability cover. Defaults to `order_total_price` when you send it, otherwise to the item total. |
| `handling_time` | integer | no | Business days between the order landing and the parcel being ready. Overrides the service level's own handling time, and shifts the delivery dates that come back. |
| `channel_id` | integer | no | The Bob Go channel the cart belongs to. Defaults to your manual channel. A channel key always uses its own channel. |
| `order_total_price` | number | no | Cart total after discounts and taxes. Use this to drive order-value rules. |
| `delivery_type` | array of string | no | Only return some kinds of option: `door` keeps door delivery and in-store pickup, `locker` and `counter` keep pickup point options at locations of that type. Also accepts `pickup-point`, which keeps pickup point service levels but matches no location on its own. |

### Fields this endpoint ignores

These parse without complaint and have no effect on the rates you get back.
Do not build against them.

| Field | Why |
| --- | --- |
| `total_weight_kg` | Always recalculated from `items`, using the greater of actual and volumetric weight. |
| `total_price` | Always recalculated: from `order_total_price` if you sent it, otherwise from `items`. |
| `currency` | Every rate comes back `ZAR` regardless. |
| `channel_order_ref` | Not carried through, so it does not reach the rates-at-checkout history either. |
| `collection_pickup_point_location_id` | Shipping **from** a pickup point is not wired up on this endpoint yet. Delivery **to** one works and needs no request field: pickup point options come back on their own. See [Pickup points](../12_Pickup%20points/locations.md). |
| `collection_pickup_point_provider_slug` | As above. |
| `customer_address` | Not used. The buyer's address is `delivery_address`. |
| `store_location` | Set by Bob Go for in-store pickup, not by you. |
| `rule_engine_fields` | Set by Bob Go while your checkout rules run, not by you. |

### Item fields

None of these are enforced, but the quote is only as good as what you send: an
item with no dimensions or no weight weakens the rate for the whole cart.

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `description` | string | send it | Matched by rules that look at the description. |
| `price` | number | send it | Unit price in ZAR. |
| `length_cm` | number | send it | |
| `width_cm` | number | send it | |
| `height_cm` | number | send it | |
| `weight_kg` | number | send it | An item left at `0` picks up the default item weight from your rates at checkout settings, if you have turned that on. |
| `quantity` | integer | no | Defaults to `1`. An explicit `0` drops the item from dimension stacking, but it still counts once towards price and weight. |
| `vendor` | string | no | |
| `sku` | string | no | |
| `shipping_class` | string | no | Matched by rules that price by shipping class. |

Dimensions are per unit. Bob Go stacks them across the cart to work out the
parcel it would take to ship the order.

## Response

```json
{
  "rates": [
    {
      "id": 1731,
      "service_name": "Economy",
      "service_code": "bobgo_1731_102_0",
      "total_price": 89.00,
      "base_rate": 79.00,
      "currency": "ZAR",
      "description": "Delivery in 3-5 business days",
      "min_delivery_date": "2026-09-09",
      "max_delivery_date": "2026-09-11",
      "type": "door",
      "service_level_priority": 1,
      "liability_cover": {
        "declared_value": 1500,
        "price": 10.00,
        "provider_liability_cover": false
      },
      "surcharges": []
    }
  ],
  "count": 1
}
```

| Field | Notes |
| --- | --- |
| `id` | The Bob Go rate the buyer picked. Keep it. `0` on the fallback rate. |
| `service_name` | Display this to the buyer. |
| `service_code` | Always prefixed `bobgo_`, and not stable: it ends with the option's position in this response (`_0`, `_1`, ...), so the same service can come back with a different code on the next call. Do not store it as a key. Send it back on the order as you received it, so the fulfilment side knows what was sold. |
| `total_price` | What to charge the buyer, in ZAR, including liability cover and surcharges. |
| `base_rate` | The price before liability cover and surcharges. |
| `min_delivery_date` / `max_delivery_date` | Estimated window, already adjusted for `handling_time`. Null if you have turned delivery dates off in your checkout settings. |
| `type` | How the parcel reaches the buyer: `door`, `pickup-point` or `in-store-pickup`. |
| `service_level_priority` | The order you configured. Sort ascending to present them as intended. |
| `liability_cover` | What the cover costs and whether the courier carries it. |
| `surcharges` | Named amounts already included in `total_price`. Empty or null when none apply. |
| `pickup_point_location_id` | Pickup point options only. Carry it through to the rate request and the shipment. |
| `pickup_point_location_distance` | Pickup point options only: how far the location is from the delivery address, in km. |
| `collection_pickup_point_location_id` | Present when one of your checkout rules ships this option from a pickup point. |
| `in_store_pickup_location_distance` | In-store pickup options only: how far the store is from the buyer, in km. |
| `provider_slug`, `service_level_code` | The courier and service level behind the rate. Present on pickup point rates, where you need them to book, and on door rates priced from a courier. |

Rates are quotes for the buyer, not bookings. Nothing is reserved, and the
amount you are charged when you book the shipment comes from the courier, not
from here.
