# Get pickup point locations

`GET /v2/locations?lat=-25.785641&lng=28.278871&stacked_height=5&stacked_width=38&stacked_length=40&total_weight=2`

**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 GET 'https://api.sandbox.bobgo.co.za/v2/locations?lat=-25.785641&lng=28.278871&stacked_height=5&stacked_width=38&stacked_length=40&total_weight=2' \
  -H "Authorization: Bearer <API_KEY>"
```

A pickup point is a locker or a staffed counter that stands in for a street
address. It can be either end of a shipment: collect from one, deliver to one,
or both.

This endpoint finds them: by id, near a point, or inside a map area, optionally
leaving out the ones a parcel will not physically fit into. Pickup points do
not have their own rating or booking endpoints. They work through
[Rates at checkout](../5_Rates%20at%20checkout/rates_at_checkout.md),
[Rates](../8_Rates/rates.md) and [Shipments](../9_Shipments/shipment_create.md),
with a couple of extra fields identifying the location instead of an address.

## Two ways in

### Let checkout choose

The buyer picks a pickup point option at checkout, and you carry the
identifiers through the rest of the flow.

1. Call `POST /v2/rates-at-checkout`. Pickup point options come back with
   `type: "pickup-point"`.
2. The chosen rate carries `pickup_point_location_id` and `provider_slug`.
3. Pass those into `POST /v2/rates` as `collection_pickup_point_location_id` or
   `delivery_pickup_point_location_id`, plus `pickup_point_provider_slug`.
4. Pass the same location id into `POST /v2/shipments`, plus `provider_slug`.

A checkout rate for a pickup point looks like this:

```json
{
  "id": 1731,
  "service_name": "Bob Box Locker - Shiplogic Locker Test (approx. 0.0 km)",
  "service_code": "bobgo_PP_1731_102_107_2",
  "total_price": 44.61,
  "type": "pickup-point",
  "pickup_point_location_id": 107,
  "provider_slug": "demo",
  "service_level_code": "BOXL-S"
}
```

### Choose the location yourself

Show the buyer a map or a list, and drive it from `GET /v2/locations`.

1. Fetch locations near the buyer, or inside the map area they are looking at.
2. Take the `id` of the one they pick, together with its `provider_slug`.
3. Rate with that `id` as `collection_pickup_point_location_id` or
   `delivery_pickup_point_location_id`, and `pickup_point_provider_slug`.
4. Book with the same location id and `provider_slug`.

## Fields when rating

| Scenario | Send |
| --- | --- |
| Pickup point → door | `collection_pickup_point_location_id`, `pickup_point_provider_slug` |
| Door → pickup point | `delivery_pickup_point_location_id`, `pickup_point_provider_slug` |
| Pickup point → pickup point | both location ids, plus `pickup_point_provider_slug` |

## Fields when booking

| Scenario | Send |
| --- | --- |
| Pickup point → door | `collection_pickup_point_location_id`, `provider_slug` |
| Door → pickup point | `delivery_pickup_point_location_id`, `provider_slug` |
| Pickup point → pickup point | both location ids, plus `provider_slug` |

Both ends must belong to the **same** provider. A shipment cannot run between
two pickup points served by different couriers, and rating a mixed pair gives
prices you cannot book.

## Finding locations

Use one of these three ways:

| | Send |
| --- | --- |
| One specific location | `location_id` **and** `provider_slug` |
| Near an address | `lat` **and** `lng` |
| Inside a map area | `min_lat`, `max_lat`, `min_lng`, `max_lng`, all four |

`lat` and `lng` must be sent together, or the call fails with
`Invalid coordinates.` `provider_slug` is required whenever `location_id` is
used. Sending more than one way does not fail: `location_id` is used first,
then the map area, then `lat` and `lng`, and the rest is ignored.

A Bob Box locker scheduled for removal from the network stops appearing in
searches 10 days before its removal date, so a buyer is not offered a locker
that will be gone before their parcel reaches it. A lookup by `location_id`
still returns it, so a locker somebody has already chosen keeps resolving.

| Name | Type | Notes |
| --- | --- | --- |
| `location_id` | integer | A specific location. |
| `provider_slug` | string | Restrict to one provider. Required with `location_id`. |
| `provider_slugs` | array of string | Restrict to several providers. `provider_slug` overrides it. |
| `lat`, `lng` | number | Search around this point. |
| `min_lat`, `max_lat`, `min_lng`, `max_lng` | number | Bounding-box search. |
| `limit` | integer | Cap the number of locations returned **per provider**, so `limit=1` with two providers returns two. Leave it out and each provider applies its own limit. |

## Filtering by parcel size

These four are optional, and only take effect when **all four** are sent. Where
there is more than one parcel, send the stacked dimensions, because locker
compartments are checked against a single footprint. Only Bob Box lockers are
checked this way.

| Name | Notes |
| --- | --- |
| `stacked_height` | Total stacked height, in cm. |
| `stacked_width` | Total stacked width, in cm. |
| `stacked_length` | Total stacked length, in cm. |
| `total_weight` | Combined weight of all parcels, in kg. |

## Response

```json
{
  "locations": [
    {
      "id": 107,
      "name": "Shiplogic Locker Test",
      "human_name": "Bob Box Locker - Menlyn",
      "type": "locker",
      "lat": -25.785641,
      "lng": 28.278871,
      "address": "125 Dallas Avenue, Menlyn, Pretoria",
      "full_address": "125 Dallas Avenue, Menlyn, Pretoria, 0181, GP",
      "trading_hours": "24/7",
      "provider_slug": "demo",
      "pickup_point_provider_name": "Bob Box",
      "pickup_point_provider_logo_url": "https://ik.imagekit.io/.../bobbox.png",
      "image_url": null,
      "active": true,
      "compartment_errors": []
    }
  ],
  "count": 1
}
```

`id` and `provider_slug` are the pair you carry into the rate request and the
shipment. `compartment_errors` explains why a location was returned but cannot
take your parcel. Check it before offering the location to a buyer. It is null
for providers that do not report compartments.

A provider that fails to answer is left out rather than failing the call, so an
empty `locations` array does not always mean there is nothing nearby.
