Reservations

Create reservation

POST /v2/bob-box/reservations Bearer token

Reserve a compartment at a location for a parcel. The response carries the compartment and all three PINs.

Compartment matching and allow_auto_upgrade are documented once, below, under How the parcel is sized. Read that before you build.

Request

Method: POST URL: https://api.bobbox.co.za/v2/bob-box/reservations

Request body parameters

Key Description Type Required
location_id The location to reserve a compartment at. integer Yes
reservation_type client-dropoff, client-pickup, or client-dropoff-and-pickup. string Yes
parcel_length Parcel length in cm. float See sizing below
parcel_width Parcel width in cm. float See sizing below
parcel_height Parcel height in cm. float See sizing below
volumetric_weight Parcel volumetric weight in kg — length × width × height ÷ 4000, sides in cm. Used for sizing only when no dimensions are sent. float See sizing below
allow_auto_upgrade Use a larger compartment when none of the matched size is free. Defaults to false. See How the parcel is sized. boolean No
reference Your own reference for this reservation (e.g. an order number). Max 255 bytes — accented and non-Latin letters use more than one byte each. string No
contact_name Name of the person collecting or dropping off. Max 255 bytes — accented and non-Latin letters use more than one byte each. string No
contact_number Contact number for the optional Bob Box collection SMS. Must be a valid South African number — see the note below. string No
parcel_count How many parcels go in the compartment. Must not be negative. integer No
custom_data Arbitrary JSON object for your own metadata. Echoed back on every read of this reservation and on every webhook. Max 4096 bytes when serialized. object No
bob_box_location_id Deprecated — alias for location_id, still accepted. Use location_id. integer No
custom_reference Deprecated — alias for reference, still accepted. Use reference. string No

location_id and bob_box_location_id are interchangeable; send one of them. Same for reference and custom_reference. When both are sent, the new name wins.

Field limits

Requests that exceed these limits will be rejected with a 400.

Field Limit
reference 255 characters
contact_name 255 characters
custom_data 4 KB (4096 bytes) serialized

The character limits are counted in bytes, so accented and non-Latin letters use more than one each. The limit on reference applies to custom_reference too.

How the parcel is sized

This is the single reference for compartment matching. Other pages link here rather than repeat it.

Bob Box works in two steps:

  1. Match a size. The parcel is matched to the smallest size it fits — Small, then Medium, then Large. Both sets of sides are sorted before they are compared, so orientation does not matter, and neither does the order you send the dimensions in.
  2. Claim a compartment. The first free compartment of the matched size is claimed.
Compartment sizes

Every compartment is the same width and length. Only the height changes.

Size Width (cm) Length (cm) Height (cm) Max volumetric weight (kg)
Small 32 56 10 4.48
Medium 32 56 24 10.752
Large 32 56 52 23.296

Volumetric factor: 4000. Volumetric weight in kg is length × width × height ÷ 4000, with the sides in centimetres. Each max_volumetric_weight above is just that compartment's own volume put through the same formula, so a parcel that fits a size by its sides also fits it by volumetric weight.

allow_auto_upgrade decides what happens when step 2 finds nothing. It defaults to false, so the reservation fails when the matched size is full — even when bigger compartments are standing empty. Send true and Bob Box falls back to any larger size instead.

Send allow_auto_upgrade: true unless you have a reason not to. It is also the only setting under which compartment_availability predicts this call exactly — see "Checking space before you reserve" in the Workflows section.

What to send

No single sizing field is required, but always send one of these three sets:

  • The three dimensions — the normal case.

  • volumetric_weight with no dimensions — each compartment is checked against its own max_volumetric_weight instead of against the parcel's sides.

    Watch out: step 1 still reads the dimensions, and with none sent it always lands on Small. With the default allow_auto_upgrade: false you can only ever get a Small compartment, however heavy the parcel is. Sizing by weight needs allow_auto_upgrade: true.

  • Nothing at all — the request still succeeds and takes the smallest compartment. Only do this when you know the parcel is small.

A parcel larger than the biggest compartment is rejected with 400 Parcel dimensions exceed maximum compartment size before any compartment is checked.

Contact number format

The number is parsed as a South African number and must be valid for that region. Both +27833771167 and 0833771167 are accepted, and the value is stored in E.164 form. A valid number from another country is rejected.

Reference uniqueness

A reference must be unique among active reservations at the same location. Reusing one that is still tied to an active reservation there returns 400 There is already an active reservation with this reference at this location. The same reference is fine at a different location, or once the earlier reservation has finished.

If you send no reference but your custom_data contains a string field named shipment_reference, that value is used as the reservation's reference. That fallback is not length-checked and not uniqueness-checked — send reference directly if you need either.

Response

Status code: 200 OK Content-Type: application/json

All three PINs are returned here, immediately. They are read off the compartment as it is claimed — you do not need to poll or call the PINs endpoint to get them.

Response body example:

See the example response.

Response fields

Key Description Type
id Unique identifier for the reservation. integer
status waiting-for-dropoff, waiting-for-pickup, expired, cancelled, returned, complete, or cleared. string
type The reservation type you requested, echoed back. string
bob_box_location The location the compartment was reserved at. See the location fields below — this is a narrower object than the one GET /account/locations returns. object
locker_id ID of the physical locker unit at the location. integer
compartment The compartment claimed for this reservation. object
contact_name The contact name, if you sent one. string
contact_number The contact number in E.164 form, if you sent one. string
reference Your reference for this reservation. string
custom_reference Deprecated — the same value as reference. Read reference. string
custom_data Your metadata object, if you sent one. object
dropoff_pin PIN for the person placing the parcel in the locker. See Using the PINs. string
pickup_pin PIN for the person collecting the parcel. See Using the PINs. string
return_pin PIN for removing a parcel that was never collected. See Using the PINs. string
flat_fee_end_time When the flat-fee window ends and overstay charges begin. Derived from your account's flat-fee duration. string (datetime)
intervention_time When the reservation becomes eligible for manual intervention (clearing the compartment). Sits after flat_fee_end_time. string (datetime)
time_dropped_off When the parcel was dropped off. null until it happens. string (datetime)
time_completed When the parcel was collected. null until it happens. string (datetime)
time_created When the reservation was created. string (datetime)
time_modified When the reservation was last modified. string (datetime)

Compartment fields:

Key Description Type
number Compartment number within the locker. integer
width / length / height Compartment internal dimensions in cm. Fixed per size — see Compartment sizes. float
max_volumetric_weight Maximum volumetric weight the compartment holds, in kg. float
type Compartment size, capitalised: Small, Medium, or Large. string

Location fields (as returned here):

Key Description Type
id Unique identifier for the location. integer
name Internal location name. string
human_name Display name for the location. string
type bob-box-locker. An account user cannot reserve at a bob-box-counter. string
description Free-text description, e.g. where in the building it is. string
structured_address Structured address fields. Same shape as on GET /account/locations — see the Locations section for the field list. object
address Human-readable address string. string
structured_trading_hours Per-day opening and closing times. Empty start/end time means closed that day. object
trading_hours Human-readable trading hours string. string
image_urls Location image URLs. array of string
status The location's status, e.g. active. string
compartment_errors Reasons no compartment could serve the request, when applicable. array of string
decommissioning_date When the location will be decommissioned. ISO 8601 UTC timestamp, only present when status is decommissioning. See "Decommissioning locations" in the Locations section. string (datetime)

This object does not carry s3_image_url, s3_image_urls, hidden, compartment_availability, time_created, or time_modified — those belong to GET /account/locations only.

Using the PINs

Store the three PINs on your side and use them however suits your workflow: show them in your app, send them by SMS or email, print them on a label, or pass them to your courier's system. Each PIN is for a different person at a different point in the parcel's journey:

PIN Who uses it When
Drop-off PIN The person placing the parcel in the locker. To open the compartment and put the parcel in.
Pickup PIN The person collecting the parcel. To open the compartment and take the parcel out.
Return PIN Your courier, or the Bob Box team if the compartment needs to be cleared. When the parcel was never collected and has to be taken out of the locker.

Things to know:

  • Only give each PIN to the person who needs it. Anyone with a PIN can open the compartment at that point, so the PIN is what keeps the parcel safe.
  • The drop-off PIN only works until the parcel is dropped off. After that, the locker no longer accepts it.
  • PINs can change. If a reservation's PINs are reissued, the Pin reissued webhook sends you the new PINs, so subscribe to it if you store PINs. Replace the PINs you stored, and make sure anyone you already sent them to gets the new ones. See the Webhooks section.

Errors

Status Message Cause
400 location_id is required Neither location_id nor bob_box_location_id was sent.
400 reservation_type is required reservation_type was missing.
400 invalid reservation_type Not one of the three accepted values.
400 could not find location for location ID No location with that ID.
400 location is not active The location is draft, prospect, inactive, or decommissioned.
400 location is being decommissioned and is no longer accepting new reservations The location's decommissioning date has passed.
400 Parcel dimensions exceed maximum compartment size The parcel does not fit the largest compartment.
400 Parcel matched compartment size <size>; no <sizes> compartments available at this location Nothing free in the sizes that were checked. With allow_auto_upgrade: false only the matched size is checked.
400 There is already an active reservation with this reference at this location. Duplicate reference at that location.
400 cannot create a reservation for a counter An account user tried to reserve at a bob-box-counter location.
400 invalid contact_number contact_number could not be read as a phone number at all.
400 contact_number must be a valid South African phone number The number was read, but is not valid for South Africa.
400 invalid custom_data custom_data could not be turned into JSON.
400 parcel_count cannot be negative A negative parcel_count was sent.
400 account_id is required when creating a reservation Your token carries no account, so there is nothing to bill the reservation to.
400 counter is not available A counter location has reached its shipment limit.
400 reference must be at most 255 characters / contact_name must be at most 255 characters / custom_data must be at most 4096 bytes when serialized A length limit was exceeded.
403 this location is not available for the specified account The location is held for another account. Treat it as if it does not exist.
403 account is frozen New reservations are limited while an account is frozen. Existing reservations are unaffected.