# Bob Go API

The Bob Go API lets you rate, book and track shipments across every courier
Bob Go supports, using a single integration. Everything the dashboard does is
available over the same HTTP API.

What you can build with it:

- **Rates at checkout**: show your buyers the shipping options you have
  configured in Bob Go, priced by your own rules, while they are still in the
  cart.
- **Order synchronisation**: push orders into Bob Go as they are placed, and
  keep them in step as they change.
- **Shipment submission**: quote couriers, book the one you want, and pull the
  waybill.
- **Tracking**: read a shipment's tracking events, or have Bob Go push them to
  you as they happen.

## Base URLs

| Environment | Base URL |
| --- | --- |
| Sandbox | `https://api.sandbox.bobgo.co.za/v2` |
| Production | `https://api.bobgo.co.za/v2` |

Sandbox is a full copy of the platform wired to test courier accounts. Nothing
you create there is collected or billed, and the account is pre-loaded with
credit so you can create as many test shipments as you like. Sign up at
[sandbox.bobgo.co.za](https://sandbox.bobgo.co.za/) and create a key under
**Settings → API keys**.

Going live is a two-line change: swap the base URL and swap the API key. The
request and response shapes are identical.

In this Bruno collection the environment sets `BobGoHost` to the host, and each
request carries the `/v2` prefix in its path.

## Conventions

- All requests and responses are JSON. Send `content-type: application/json` on
  any request with a body.
- Paths are prefixed with `/v2`, e.g. `POST /v2/rates`, `POST /v2/shipments`.
- Field names are `snake_case`.
- Money is a number in ZAR (rands), e.g. `152.35`. Amounts are VAT-inclusive
  unless the field name says otherwise (`rate_amount` vs `rate_amount_excl_vat`).
- Dimensions are centimetres (`_cm`), weights are kilograms (`_kg`).
- Timestamps are RFC 3339, e.g. `2026-09-05T08:30:00Z`.
- An array in the query string is written as a JSON array with no spaces, e.g.
  `?status=["in-transit","delivered"]`. A space after a comma is not rejected,
  but the value then matches nothing: a list comes back empty, and a waybill
  request finds no shipments to print.
- Every request acts on the account your API key belongs to. Bob Go reads the
  account from the key, so you do not send `account_id`, and a value you do send
  is replaced rather than checked.

Objects that appear in more than one endpoint, addresses and parcels, always
have the same shape wherever they appear. See
[Important concepts](../4_Important%20concepts/README.md).

## Listing and pagination

List endpoints accept `limit` and `offset`, plus `order_by` and `order`
(`asc` / `desc`). `order` only applies together with `order_by`: send both, and
the sort is ascending unless `order` is `desc`. Without `order_by` the
endpoint's default sort is used and `order` is ignored. Responses wrap
the page in an object with a `count` of the whole result set, not of the page.
This is `GET /v2/shipments?limit=2` on an account with 348 shipments, trimmed to
two fields a shipment:

```json
{
  "shipments": [
    { "id": 4455123, "tracking_reference": "UATCG4X7Q2" },
    { "id": 4455122, "tracking_reference": "UATCG4X7PZ" }
  ],
  "count": 348
}
```

Defaults and caps are per endpoint, so read the page for the one you are
calling. `GET /v2/orders` and `GET /v2/shipments` both default to 100 and cap
at 100. `GET /v2/packages` has no default and no cap: it returns every package
on the account unless you set `limit` yourself.

## Errors

Failures return an HTTP status code and a single message field:

```json
{
  "message": "Collection contact name is required."
}
```

| Status | Meaning |
| --- | --- |
| `400` | The request failed validation. The message says what. |
| `401` | The API key was not recognised. See [Authentication](../2_Authentication/README.md#failures). |
| `403` | No API key was sent, the key may not make this call, or your plan does not include API access. |
| `404` | No such path, or no such record on your account. Some endpoints report a missing record as a `400` or an empty list instead. |
| `423` | The account has been closed. |
| `429` | Too many requests on this key. Back off and retry. |
| `500` | Something failed on our side. A few validation failures also come back as `500`, so read the message before retrying. |
| `503` | The request ran past the gateway's limit of about 30 seconds, usually while waiting on a courier. The body is `{"message": "Service Unavailable"}`. Retry later. |

Treat the message as human-readable text for your logs and support team, not as
a stable machine-readable code. Branch on the status code instead.
