MENU navbar-image

Introduction

Welcome to the Shift Collect API - A RESTful API for managing locker bookings and account access.

This API allows you to manage bookings for lockers at various sites, switch between account contexts, and manage your API tokens.

This API uses Bearer token authentication. Generate API tokens using the POST /v1/tokens/generate endpoint (requires appropriate permissions) and then include the token in the Authorization header as: Bearer {your-token}.

As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile). You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {YOUR_API_TOKEN}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Generate API tokens using the POST /v1/auth/token endpoint. See the Authentication section below for more information.

Accounts

Get the current account context.

requires authentication

Returns detailed information about the currently active account, including reverse flow strategies, locker assignment strategy, service products, users, and settings.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/account" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/account"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/account';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/account'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
        "name": "Acme Corp",
        "description": "A leading logistics company",
        "website": "https://acme.example.com",
        "phone": "+44 20 1234 5678",
        "email": "[email protected]",
        "address": "123 Business Street, London, SW1A 1AA",
        "is_active": true,
        "created_at": "2025-01-01T00:00:00.000000Z",
        "updated_at": "2025-01-01T00:00:00.000000Z",
        "reverse_flow_strategies": [
            {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
                "name": "return_to_sender",
                "description": "Return to Sender",
                "is_active": true,
                "is_default": true
            },
            {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
                "name": "hold_at_sort",
                "description": "Hold at Sort Centre",
                "is_active": true,
                "is_default": false
            }
        ],
        "locker_assignment_strategy": {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
            "name": "Just-in-time Assignment",
            "description": "Assign lockers on day of delivery",
            "is_active": true
        },
        "service_products": [
            {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
                "name": "DC to Locker",
                "description": "Direct delivery from despatch centre to locker",
                "requires_collection": true,
                "requires_delivery": true,
                "requires_locker": true,
                "requires_sorting_hub": false,
                "is_active": true,
                "is_default": true
            }
        ],
        "users": [
            {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FBA",
                "name": "John Doe",
                "created_at": "2025-01-01T00:00:00.000000Z",
                "last_accessed_at": "2025-01-15T10:30:00.000000Z",
                "is_active": true,
                "role": "owner"
            }
        ],
        "settings": [
            {
                "name": "default_currency",
                "description": "Default currency for pricing",
                "type": "string",
                "value": "GBP"
            },
            {
                "name": "vat_rate",
                "description": "VAT rate as decimal (e.g., 0.20 for 20%)",
                "type": "decimal",
                "value": 0.2
            },
            {
                "name": "sla_min_days",
                "description": "Minimum SLA days for delivery",
                "type": "integer",
                "value": 2
            },
            {
                "name": "reverse_flow_allow_consumer_reschedule",
                "description": "Allow consumers to reschedule reverse flow deliveries",
                "type": "boolean",
                "value": true
            },
            {
                "name": "reverse_flow_alternative_address",
                "description": "Alternative address for reverse flow returns",
                "type": "address",
                "value": {
                    "line_1": "123 Return Street",
                    "line_2": null,
                    "line_3": null,
                    "city": "London",
                    "postcode": "SW1A 1AA",
                    "country": "United Kingdom"
                }
            }
        ]
    }
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Request      

GET v1/account

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Authentication

Generate API token from credentials.

requires authentication

Authenticates a user with email and password and returns an API token. This is useful for programmatic access without needing to login via the SPA first. Requires Admin or Owner role in the specified account.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/auth/token" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"[email protected]\",
    \"password\": \"password123\",
    \"account_id\": \"01ARZ3NDEKTSV4RRFFQ69G5FAW\",
    \"token_name\": \"Production API Token\",
    \"expires_at\": \"2025-12-31T23:59:59Z\"
}"
const url = new URL(
    "https://api.shiftcollect.com/v1/auth/token"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "[email protected]",
    "password": "password123",
    "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
    "token_name": "Production API Token",
    "expires_at": "2025-12-31T23:59:59Z"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/auth/token';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'email' => '[email protected]',
            'password' => 'password123',
            'account_id' => '01ARZ3NDEKTSV4RRFFQ69G5FAW',
            'token_name' => 'Production API Token',
            'expires_at' => '2025-12-31T23:59:59Z',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/auth/token'
payload = {
    "email": "[email protected]",
    "password": "password123",
    "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
    "token_name": "Production API Token",
    "expires_at": "2025-12-31T23:59:59Z"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Example response (HTTP 200):


{
    "token": "1|abcdefghijklmnopqrstuvwxyz1234567890",
    "token_name": "Production API Token",
    "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
    "user": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
        "name": "John Doe",
        "email": "[email protected]"
    },
    "abilities": [
        "*"
    ],
    "expires_at": "2025-12-31T23:59:59.000000Z",
    "created_at": "2025-01-01T00:00:00.000000Z",
    "message": "API token generated successfully"
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised. Only admins and owners can generate API tokens."
}
 

Example response (HTTP 403):


{
    "message": "User does not belong to the specified account."
}
 

Example response (HTTP 422):


{
    "message": "The given data was invalid.",
    "errors": {
        "email": [
            "The email field is required."
        ],
        "password": [
            "The password field is required."
        ],
        "account_id": [
            "The account id field is required."
        ],
        "expires_at": [
            "The expires at must be a date after now."
        ]
    }
}
 

Example response (HTTP 422):


{
    "message": "The given data was invalid.",
    "errors": {
        "email": [
            "These credentials do not match our records."
        ]
    }
}
 

Request      

POST v1/auth/token

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

User email address Example: [email protected]

password   string     

User password Example: password123

account_id   string     

Account ID (ULID) for which to generate the token Example: 01ARZ3NDEKTSV4RRFFQ69G5FAW

token_name   string  optional    

Name for the token (optional, defaults to "API Token") Example: Production API Token

expires_at   string  optional    

Expiration date and time (ISO 8601). Must be in the future Example: 2025-12-31T23:59:59Z

Booking Tracking

Track a booking by reference ID (public endpoint).

requires authentication

Allows public users to track their booking using reference ID and email or phone verification. At least one of email or phone must be provided.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/tracking/booking" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"id\": \"BK691B2516D28B2\",
    \"email\": \"[email protected]\",
    \"phone\": \"+447700900123\"
}"
const url = new URL(
    "https://api.shiftcollect.com/v1/tracking/booking"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "id": "BK691B2516D28B2",
    "email": "[email protected]",
    "phone": "+447700900123"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/tracking/booking';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'id' => 'BK691B2516D28B2',
            'email' => '[email protected]',
            'phone' => '+447700900123',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/tracking/booking'
payload = {
    "id": "BK691B2516D28B2",
    "email": "[email protected]",
    "phone": "+447700900123"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Example response (HTTP 200):


{
    "reference_id": "BK65A3F2E1",
    "package_info": {
        "tracking_number": "BK65A3F2E1",
        "status": "confirmed",
        "locker_assignments": [
            {
                "locker_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
                "locker_name": "A1-01",
                "cabinet": {
                    "id": "01ARZ3NDEKTSV4RRFFQ69G5FAU",
                    "name": "Cabinet A1",
                    "status": "active"
                },
                "starts_at": "2025-01-15T09:00:00.000000Z",
                "ends_at": "2025-01-20T17:00:00.000000Z"
            }
        ],
        "items_count": 3,
        "items": [
            {
                "name": "Item 1",
                "quantity": 1
            }
        ]
    },
    "events": [],
    "last_updated": "2025-01-15T09:00:00.000000Z"
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Example response (HTTP 422):


{
    "message": "The given data was invalid."
}
 

Request      

POST v1/tracking/booking

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

id   string     

Booking ID, reference ID, label/tracking ID, or ticket reference Example: BK691B2516D28B2

email   string  optional    

Customer email address for verification (required if phone is not provided) Example: [email protected]

phone   string  optional    

Customer phone number for verification (required if email is not provided) Example: +447700900123

Track a booking by ID (authenticated endpoint).

requires authentication

Allows authenticated accounts to track their bookings.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/bookings/architecto/tracking" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/architecto/tracking"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/architecto/tracking';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/architecto/tracking'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "reference_id": "BK65A3F2E1",
    "package_info": {
        "tracking_number": "BK65A3F2E1",
        "status": "confirmed",
        "locker_assignments": [
            {
                "locker_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
                "locker_name": "A1-01",
                "cabinet": {
                    "id": "01ARZ3NDEKTSV4RRFFQ69G5FAU",
                    "name": "Cabinet A1",
                    "status": "active"
                },
                "starts_at": "2025-01-15T09:00:00.000000Z",
                "ends_at": "2025-01-20T17:00:00.000000Z"
            }
        ],
        "items_count": 3,
        "items": [
            {
                "name": "Item 1",
                "quantity": 1
            }
        ]
    },
    "events": [],
    "last_updated": "2025-01-15T09:00:00.000000Z"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Request      

GET v1/bookings/{id}/tracking

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking. Example: architecto

Bookings

Display a listing of bookings.

requires authentication

Returns a paginated list of bookings for the current account context. You can filter by status. When we have more detail about why the booking is in its current state, each booking includes status_reason_id, status_reason, and status_reason_label; the same fields appear on each status_history entry where they were recorded.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/bookings?status=confirmed&per_page=20&page=1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings"
);

const params = {
    "status": "confirmed",
    "per_page": "20",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'status' => 'confirmed',
            'per_page' => '20',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings'
params = {
  'status': 'confirmed',
  'per_page': '20',
  'page': '1',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()

Example response (HTTP 200):


{
    "data": [
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
            "reference_id": "SC689PUY74F",
            "service_product_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
            "service_product": "DC to Locker",
            "status_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
            "status": "confirmed",
            "status_reason_id": null,
            "status_reason": null,
            "status_reason_label": null,
            "site_id": "01ARZ3NDEKTSV4RRFFQ69G5FAS",
            "site": {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FAS",
                "name": "Example Locker Site",
                "city": "London",
                "postcode": "N1 1AA"
            },
            "status_history": [
                {
                    "id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
                    "status": "confirmed",
                    "label": "Confirmed",
                    "description": "Booking has been confirmed",
                    "status_reason_id": null,
                    "status_reason": null,
                    "status_reason_label": null,
                    "status_changed_at": "2025-01-01T00:00:00.000000Z"
                }
            ],
            "items": [
                {
                    "id": "01ARZ3NDEKTSV4RRFFQ69G5FAB",
                    "name": "Box of documents",
                    "description": "Important business documents",
                    "quantity": 1,
                    "weight": 5.5,
                    "height": 30,
                    "width": 40,
                    "depth": 50,
                    "volume": 60000,
                    "created_at": "2025-01-01T00:00:00.000000Z",
                    "updated_at": "2025-01-01T00:00:00.000000Z"
                }
            ],
            "collections": [],
            "labels": [
                {
                    "id": "01ARZ3NDEKTSV4RRFFQ69G5FAD",
                    "zpl_available": true,
                    "pdf_available": true,
                    "png_available": false,
                    "zpl": "https://api.shiftcollect.com/v1/labels/1234567890/zpl",
                    "pdf": "https://api.shiftcollect.com/v1/labels/1234567890/pdf",
                    "png": "https://api.shiftcollect.com/v1/labels/1234567890/png"
                }
            ],
            "locker_reservation_window": null,
            "locker_assignments": null,
            "authorisations": null,
            "contacts": [
                {
                    "id": "01ARZ3NDEKTSV4RRFFQ69G5FAC",
                    "name": "Jane Doe",
                    "email": "[email protected]",
                    "phone": "+447700900123"
                }
            ],
            "returns": null,
            "created_at": "2025-01-01T00:00:00.000000Z",
            "updated_at": "2025-01-01T00:00:00.000000Z",
            "created_by": "01ARZ3NDEKTSV4RRFFQ69G5FAY"
        }
    ],
    "pagination": {
        "current_page": 1,
        "per_page": 15,
        "total": 100,
        "last_page": 7,
        "from": 1,
        "to": 15,
        "has_more_pages": true
    },
    "links": {
        "first": "http://example.com/api/v1/bookings?page=1",
        "last": "http://example.com/api/v1/bookings?page=7",
        "prev": null,
        "next": "http://example.com/api/v1/bookings?page=2"
    }
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Request      

GET v1/bookings

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter bookings by status Example: confirmed

per_page   integer  optional    

Number of items per page (max 100) Example: 20

page   integer  optional    

Page number Example: 1

Store a newly created booking.

requires authentication

Creates a new booking associated with the current account context and the authenticated user. The booking will be processed according to the specified service product.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/bookings" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"service_product_id\": \"01ARZ3NDEKTSV4RRFFQ69G5FAX\",
    \"reference_id\": \"ORD-12345\",
    \"collection\": {
        \"site_id\": \"01ARZ3NDEKTSV4RRFFQ69G5FAY\",
        \"despatch_centre_id\": \"01ARZ3NDEKTSV4RRFFQ69G5FAY\",
        \"address\": {
            \"recipient\": \"John Smith\",
            \"line_1\": \"123 Main Street\",
            \"line_2\": \"Flat 3\",
            \"line_3\": \"Building A\",
            \"city\": \"Plymouth\",
            \"postcode\": \"SW1A 1AA\",
            \"country\": \"United Kingdom\",
            \"phone\": \"+44 1234 567890\",
            \"email\": \"[email protected]\",
            \"special_instructions\": \"Leave at side gate\",
            \"latitude\": -89,
            \"longitude\": -179
        },
        \"t_earliest_collection\": \"2025-01-15T09:00:00Z\",
        \"t_latest_collection\": \"2025-01-15T17:00:00Z\"
    },
    \"delivery\": {
        \"site_id\": \"01ARZ3NDEKTSV4RRFFQ69G5FAZ\",
        \"despatch_centre_id\": \"01ARZ3NDEKTSV4RRFFQ69G5FAZ\",
        \"address\": {
            \"recipient\": \"John Smith\",
            \"line_1\": \"123 Main Street\",
            \"line_2\": \"Flat 3\",
            \"line_3\": \"Building A\",
            \"city\": \"Plymouth\",
            \"postcode\": \"SW1A 1AA\",
            \"country\": \"United Kingdom\",
            \"phone\": \"+44 1234 567890\",
            \"email\": \"[email protected]\",
            \"special_instructions\": \"Leave at side gate\",
            \"latitude\": -90,
            \"longitude\": -179
        }
    },
    \"locker_reservation_window\": {
        \"site_id\": \"01ARZ3NDEKTSV4RRFFQ69G5FAZ\",
        \"t_earliest_arrival\": \"2025-01-15T09:00:00Z\",
        \"t_latest_collection\": \"2025-01-22T17:00:00Z\"
    },
    \"items\": [
        {
            \"name\": \"Box of documents\",
            \"description\": \"Important business documents\",
            \"quantity\": 1,
            \"weight\": 5.5,
            \"height\": 30,
            \"width\": 40,
            \"depth\": 50,
            \"value\": 27
        }
    ],
    \"authorisation_group_ids\": [
        \"01ARZ3NDEKTSV4RRFFQ69G5FAY\"
    ],
    \"locker_user_ids\": [
        \"01ARZ3NDEKTSV4RRFFQ69G5FAZ\"
    ],
    \"contacts\": [
        {
            \"name\": \"John Doe\",
            \"email\": \"[email protected]\",
            \"phone\": \"+447700900123\"
        }
    ]
}"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "service_product_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
    "reference_id": "ORD-12345",
    "collection": {
        "site_id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
        "despatch_centre_id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
        "address": {
            "recipient": "John Smith",
            "line_1": "123 Main Street",
            "line_2": "Flat 3",
            "line_3": "Building A",
            "city": "Plymouth",
            "postcode": "SW1A 1AA",
            "country": "United Kingdom",
            "phone": "+44 1234 567890",
            "email": "[email protected]",
            "special_instructions": "Leave at side gate",
            "latitude": -89,
            "longitude": -179
        },
        "t_earliest_collection": "2025-01-15T09:00:00Z",
        "t_latest_collection": "2025-01-15T17:00:00Z"
    },
    "delivery": {
        "site_id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
        "despatch_centre_id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
        "address": {
            "recipient": "John Smith",
            "line_1": "123 Main Street",
            "line_2": "Flat 3",
            "line_3": "Building A",
            "city": "Plymouth",
            "postcode": "SW1A 1AA",
            "country": "United Kingdom",
            "phone": "+44 1234 567890",
            "email": "[email protected]",
            "special_instructions": "Leave at side gate",
            "latitude": -90,
            "longitude": -179
        }
    },
    "locker_reservation_window": {
        "site_id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
        "t_earliest_arrival": "2025-01-15T09:00:00Z",
        "t_latest_collection": "2025-01-22T17:00:00Z"
    },
    "items": [
        {
            "name": "Box of documents",
            "description": "Important business documents",
            "quantity": 1,
            "weight": 5.5,
            "height": 30,
            "width": 40,
            "depth": 50,
            "value": 27
        }
    ],
    "authorisation_group_ids": [
        "01ARZ3NDEKTSV4RRFFQ69G5FAY"
    ],
    "locker_user_ids": [
        "01ARZ3NDEKTSV4RRFFQ69G5FAZ"
    ],
    "contacts": [
        {
            "name": "John Doe",
            "email": "[email protected]",
            "phone": "+447700900123"
        }
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'service_product_id' => '01ARZ3NDEKTSV4RRFFQ69G5FAX',
            'reference_id' => 'ORD-12345',
            'collection' => [
                'site_id' => '01ARZ3NDEKTSV4RRFFQ69G5FAY',
                'despatch_centre_id' => '01ARZ3NDEKTSV4RRFFQ69G5FAY',
                'address' => [
                    'recipient' => 'John Smith',
                    'line_1' => '123 Main Street',
                    'line_2' => 'Flat 3',
                    'line_3' => 'Building A',
                    'city' => 'Plymouth',
                    'postcode' => 'SW1A 1AA',
                    'country' => 'United Kingdom',
                    'phone' => '+44 1234 567890',
                    'email' => '[email protected]',
                    'special_instructions' => 'Leave at side gate',
                    'latitude' => -89,
                    'longitude' => -179,
                ],
                't_earliest_collection' => '2025-01-15T09:00:00Z',
                't_latest_collection' => '2025-01-15T17:00:00Z',
            ],
            'delivery' => [
                'site_id' => '01ARZ3NDEKTSV4RRFFQ69G5FAZ',
                'despatch_centre_id' => '01ARZ3NDEKTSV4RRFFQ69G5FAZ',
                'address' => [
                    'recipient' => 'John Smith',
                    'line_1' => '123 Main Street',
                    'line_2' => 'Flat 3',
                    'line_3' => 'Building A',
                    'city' => 'Plymouth',
                    'postcode' => 'SW1A 1AA',
                    'country' => 'United Kingdom',
                    'phone' => '+44 1234 567890',
                    'email' => '[email protected]',
                    'special_instructions' => 'Leave at side gate',
                    'latitude' => -90,
                    'longitude' => -179,
                ],
            ],
            'locker_reservation_window' => [
                'site_id' => '01ARZ3NDEKTSV4RRFFQ69G5FAZ',
                't_earliest_arrival' => '2025-01-15T09:00:00Z',
                't_latest_collection' => '2025-01-22T17:00:00Z',
            ],
            'items' => [
                [
                    'name' => 'Box of documents',
                    'description' => 'Important business documents',
                    'quantity' => 1,
                    'weight' => 5.5,
                    'height' => 30.0,
                    'width' => 40.0,
                    'depth' => 50.0,
                    'value' => 27,
                ],
            ],
            'authorisation_group_ids' => [
                '01ARZ3NDEKTSV4RRFFQ69G5FAY',
            ],
            'locker_user_ids' => [
                '01ARZ3NDEKTSV4RRFFQ69G5FAZ',
            ],
            'contacts' => [
                [
                    'name' => 'John Doe',
                    'email' => '[email protected]',
                    'phone' => '+447700900123',
                ],
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings'
payload = {
    "service_product_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
    "reference_id": "ORD-12345",
    "collection": {
        "site_id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
        "despatch_centre_id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
        "address": {
            "recipient": "John Smith",
            "line_1": "123 Main Street",
            "line_2": "Flat 3",
            "line_3": "Building A",
            "city": "Plymouth",
            "postcode": "SW1A 1AA",
            "country": "United Kingdom",
            "phone": "+44 1234 567890",
            "email": "[email protected]",
            "special_instructions": "Leave at side gate",
            "latitude": -89,
            "longitude": -179
        },
        "t_earliest_collection": "2025-01-15T09:00:00Z",
        "t_latest_collection": "2025-01-15T17:00:00Z"
    },
    "delivery": {
        "site_id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
        "despatch_centre_id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
        "address": {
            "recipient": "John Smith",
            "line_1": "123 Main Street",
            "line_2": "Flat 3",
            "line_3": "Building A",
            "city": "Plymouth",
            "postcode": "SW1A 1AA",
            "country": "United Kingdom",
            "phone": "+44 1234 567890",
            "email": "[email protected]",
            "special_instructions": "Leave at side gate",
            "latitude": -90,
            "longitude": -179
        }
    },
    "locker_reservation_window": {
        "site_id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
        "t_earliest_arrival": "2025-01-15T09:00:00Z",
        "t_latest_collection": "2025-01-22T17:00:00Z"
    },
    "items": [
        {
            "name": "Box of documents",
            "description": "Important business documents",
            "quantity": 1,
            "weight": 5.5,
            "height": 30,
            "width": 40,
            "depth": 50,
            "value": 27
        }
    ],
    "authorisation_group_ids": [
        "01ARZ3NDEKTSV4RRFFQ69G5FAY"
    ],
    "locker_user_ids": [
        "01ARZ3NDEKTSV4RRFFQ69G5FAZ"
    ],
    "contacts": [
        {
            "name": "John Doe",
            "email": "[email protected]",
            "phone": "+447700900123"
        }
    ]
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Example response (HTTP 201):


{
    "message": "Booking created successfully",
    "booking_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
    "reference_id": "BK-2025-001",
    "service": "DC to Locker",
    "label_ids": [
        "1111111119",
        "1111111120"
    ]
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 422):


{
    "message": "The given data was invalid.",
    "errors": {
        "delivery": [
            "The delivery field is required."
        ],
        "collection.address_id": [
            "The collection address id field is required when collection is present."
        ],
        "locker_reservation_window.site_id": [
            "The locker reservation window site must match the delivery site."
        ]
    }
}
 

Request      

POST v1/bookings

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

service_product_id   string  optional    

Service product identifier. Accepts either the service product ULID or exact name. If not provided, the account's default service will be used. The service_product_id of an existing record in the account_service_products table. Example: 01ARZ3NDEKTSV4RRFFQ69G5FAX

reference_id   string  optional    

Optional custom reference ID for the booking. Must be unique within the account. If not provided, a reference ID will be auto-generated. Must not be greater than 255 characters. Example: ORD-12345

collection   object  optional    

Collection details (required if service requires collection). Must contain exactly one of: site_id, despatch_centre_id, or address.

site_id   string  optional    

The site ID from where the booking will be collected (if collecting from a locker site). This field is required when none of collection.despatch_centre_id and collection.address are present. The id of an existing record in the sites table. Example: 01ARZ3NDEKTSV4RRFFQ69G5FAY

despatch_centre_id   string  optional    

The despatch centre ID from where the booking will be collected. This field is required when none of collection.site_id and collection.address are present. The id of an existing record in the despatch_centres table. Example: 01ARZ3NDEKTSV4RRFFQ69G5FAY

address   object  optional    

Inline address object for collection (if not using site_id or despatch_centre_id). This field is required when none of collection.site_id and collection.despatch_centre_id are present.

recipient   string  optional    

Recipient name. Must not be greater than 255 characters. Example: John Smith

line_1   string  optional    

Address line 1. This field is required when collection.address is present. Must not be greater than 255 characters. Example: 123 Main Street

line_2   string  optional    

Address line 2. Must not be greater than 255 characters. Example: Flat 3

line_3   string  optional    

Address line 3. Must not be greater than 255 characters. Example: Building A

city   string  optional    

City. Must not be greater than 255 characters. Example: Plymouth

postcode   string  optional    

Postcode. This field is required when collection.address is present. Must not be greater than 20 characters. Example: SW1A 1AA

country   string  optional    

Country. Must not be greater than 255 characters. Example: United Kingdom

phone   string  optional    

Phone number. Must not be greater than 50 characters. Example: +44 1234 567890

email   string  optional    

Email address. Must be a valid email address. Must not be greater than 255 characters. Example: [email protected]

special_instructions   string  optional    

Special delivery instructions. Must not be greater than 1000 characters. Example: Leave at side gate

latitude   number  optional    

Must be between -90 and 90. Example: -89

longitude   number  optional    

Must be between -180 and 180. Example: -179

t_earliest_collection   string  optional    

Earliest collection time (ISO 8601 datetime). Must be a valid date. Must be a date after now. Example: 2025-01-15T09:00:00Z

t_latest_collection   string  optional    

Latest collection time (ISO 8601 datetime). Must be a valid date. Must be a date after collection.t_earliest_collection. Example: 2025-01-15T17:00:00Z

delivery   object  optional    

Delivery details (required if service product requires delivery). Must contain exactly one of: site_id, despatch_centre_id, or address.

site_id   string  optional    

The site ID to where items will be delivered (if to a locker site). This field is required when none of delivery.despatch_centre_id and delivery.address are present. The id of an existing record in the sites table. Example: 01ARZ3NDEKTSV4RRFFQ69G5FAZ

despatch_centre_id   string  optional    

The despatch centre ID to where items will be delivered. This field is required when none of delivery.site_id and delivery.address are present. The id of an existing record in the despatch_centres table. Example: 01ARZ3NDEKTSV4RRFFQ69G5FAZ

address   object  optional    

Inline address object for delivery (if not using site_id or despatch_centre_id). This field is required when none of delivery.site_id and delivery.despatch_centre_id are present.

recipient   string  optional    

Recipient name. Must not be greater than 255 characters. Example: John Smith

line_1   string  optional    

Address line 1. This field is required when delivery.address is present. Must not be greater than 255 characters. Example: 123 Main Street

line_2   string  optional    

Address line 2. Must not be greater than 255 characters. Example: Flat 3

line_3   string  optional    

Address line 3. Must not be greater than 255 characters. Example: Building A

city   string  optional    

City. Must not be greater than 255 characters. Example: Plymouth

postcode   string  optional    

Postcode. This field is required when delivery.address is present. Must not be greater than 20 characters. Example: SW1A 1AA

country   string  optional    

Country. Must not be greater than 255 characters. Example: United Kingdom

phone   string  optional    

Phone number. Must not be greater than 50 characters. Example: +44 1234 567890

email   string  optional    

Email address. Must be a valid email address. Must not be greater than 255 characters. Example: [email protected]

special_instructions   string  optional    

Special delivery instructions. Must not be greater than 1000 characters. Example: Leave at side gate

latitude   number  optional    

Must be between -90 and 90. Example: -90

longitude   number  optional    

Must be between -180 and 180. Example: -179

locker_reservation_window   object  optional    

Locker reservation window details.

site_id   string  optional    

The site ID for the locker reservation window. This field is required when locker_reservation_window is present. The id of an existing record in the sites table. Example: 01ARZ3NDEKTSV4RRFFQ69G5FAZ

t_earliest_arrival   string  optional    

Earliest booking arrival time at the locker site (ISO 8601). This field is required when locker_reservation_window is present. Must be a valid date. Must be a date after now. Example: 2025-01-15T09:00:00Z

t_latest_collection   string  optional    

Latest customer collection time from the locker site (ISO 8601). This field is required when locker_reservation_window is present. Must be a valid date. Must be a date after locker_reservation_window.t_earliest_arrival. Example: 2025-01-22T17:00:00Z

contacts   object[]  optional    

Array of contacts to notify about tracking events and booking updates.

name   string  optional    

Contact name. Must not be greater than 255 characters. Example: John Doe

email   string  optional    

Contact email. This field is required when contacts.*.phone is not present. Must be a valid email address. Example: [email protected]

phone   string  optional    

Contact phone. Must be in international format (e.g. +447700900123). This field is required when contacts.*.email is not present. Example: +447700900123

items   object[]     

Array of items for the booking.

name   string  optional    

Name of the item. This field is required when items is present. Must not be greater than 255 characters. Example: Box of documents

description   string  optional    

Description of the item. Max 1000 characters. Must not be greater than 1000 characters. Example: Important business documents

quantity   integer  optional    

Quantity of items. Must be >= 1. This field is required when items is present. Must be at least 1. Example: 1

weight   number  optional    

Weight in kilograms. Must be at least 0.1. This field is required when items is present. Must be at least 0.1. Example: 5.5

height   number  optional    

Height in centimeters. Must be at least 0.1. This field is required when items is present. Must be at least 0.1. Example: 30

width   number  optional    

Width in centimeters. Must be at least 0.1. This field is required when items is present. Must be at least 0.1. Example: 40

depth   number  optional    

Depth in centimeters. Must be at least 0.1. This field is required when items is present. Must be at least 0.1. Example: 50

value   number  optional    

Must be at least 0. Example: 27

authorisation_group_ids   string[]  optional    

Authorisation group ID. The id of an existing record in the authorisation_groups table.

locker_user_ids   string[]  optional    

Locker user ID. The id of an existing record in the locker_users table.

Display the specified booking.

requires authentication

Returns full details for one booking in your current account context. When available, status_reason_id, status_reason, and status_reason_label describe why the booking has its current overall status; each past step in status_history can include the same three fields where they were recorded.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
        "reference_id": "SC689PUY74F",
        "service_product_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
        "service_product": "DC to Locker",
        "status_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
        "status": "confirmed",
        "status_reason_id": null,
        "status_reason": null,
        "status_reason_label": null,
        "site_id": "01ARZ3NDEKTSV4RRFFQ69G5FAS",
        "site": {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAS",
            "name": "Example Locker Site",
            "city": "London",
            "postcode": "N1 1AA"
        },
        "status_history": [
            {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
                "status": "confirmed",
                "label": "Confirmed",
                "description": "Booking has been confirmed",
                "status_reason_id": null,
                "status_reason": null,
                "status_reason_label": null,
                "status_changed_at": "2025-01-01T00:00:00.000000Z"
            }
        ],
        "items": [
            {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FAB",
                "name": "Box of documents",
                "description": "Important business documents",
                "quantity": 1,
                "weight": 5.5,
                "height": 30,
                "width": 40,
                "depth": 50,
                "volume": 60000,
                "created_at": "2025-01-01T00:00:00.000000Z",
                "updated_at": "2025-01-01T00:00:00.000000Z"
            }
        ],
        "collections": [],
        "labels": [
            {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FAD",
                "zpl_available": true,
                "pdf_available": true,
                "png_available": false
            }
        ],
        "locker_reservation_window": null,
        "locker_assignments": null,
        "authorisations": null,
        "contacts": [
            {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FAC",
                "name": "Jane Doe",
                "email": "[email protected]",
                "phone": "+447700900123"
            }
        ],
        "returns": null,
        "created_at": "2025-01-01T00:00:00.000000Z",
        "updated_at": "2025-01-01T00:00:00.000000Z",
        "created_by": "01ARZ3NDEKTSV4RRFFQ69G5FAY"
    }
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Request      

GET v1/bookings/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Cancel the specified booking (DELETE).

requires authentication

Cancels a booking, removing it from active status. The booking must belong to the current account context. Only bookings with status Pending, Confirmed, In Progress or Ready for Collection can be cancelled.

Example request:

curl --request DELETE \
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('DELETE', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "message": "Booking cancelled",
    "booking_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
    "reference_id": "BK-2025-001",
    "status": "cancelled"
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Example response (HTTP 422):


{
    "message": "Booking cannot be cancelled. Only bookings with status Pending, Confirmed, In Progress or Ready for Collection can be cancelled."
}
 

Request      

DELETE v1/bookings/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking to cancel Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Cancel the specified booking (POST shortcut).

requires authentication

Shortcut for cancelling a booking. Same behaviour as DELETE /v1/bookings/{id}. Only bookings with status Pending, Confirmed, In Progress or Ready for Collection can be cancelled.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/cancel" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/cancel"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/cancel';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/cancel'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "message": "Booking cancelled",
    "booking_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
    "reference_id": "BK-2025-001",
    "status": "cancelled"
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Example response (HTTP 422):


{
    "message": "Booking cannot be cancelled. Only bookings with status Pending, Confirmed, In Progress or Ready for Collection can be cancelled."
}
 

Request      

POST v1/bookings/{id}/cancel

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking to cancel Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Send booking confirmation to customer.

requires authentication

Sends booking confirmation notification via email and SMS. This includes any access codes if the booking has locker assignments.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-booking-confirmation" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-booking-confirmation"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-booking-confirmation';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-booking-confirmation'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "message": "Booking confirmation notification queued successfully",
    "recipients": [
        {
            "email": "[email protected]",
            "phone": "+44 1234 567890"
        }
    ]
}
 

Example response (HTTP 400):


{
    "message": "No customer contact information available"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Request      

POST v1/bookings/{id}/send-booking-confirmation

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Send booking reminder email.

requires authentication

Sends a reminder email before the booking starts with all access codes.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-reminder" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-reminder"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-reminder';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-reminder'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "message": "Reminder notification queued successfully",
    "recipients": [
        {
            "email": "[email protected]",
            "phone": "+44 1234 567890"
        }
    ]
}
 

Example response (HTTP 400):


{
    "message": "No customer contact information available"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Request      

POST v1/bookings/{id}/send-reminder

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Send expiry reminder email.

requires authentication

Sends a reminder email before the booking expires.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-expiry-reminder" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-expiry-reminder"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-expiry-reminder';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-expiry-reminder'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "message": "Expiry reminder notification queued successfully",
    "recipients": [
        {
            "email": "[email protected]",
            "phone": "+44 1234 567890"
        }
    ]
}
 

Example response (HTTP 400):


{
    "message": "No customer contact information available"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Request      

POST v1/bookings/{id}/send-expiry-reminder

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Send cancellation notice email.

requires authentication

Sends a cancellation notice when a booking is cancelled.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-cancellation-notice" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"cancellation_reason\": \"Customer request\"
}"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-cancellation-notice"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "cancellation_reason": "Customer request"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-cancellation-notice';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'cancellation_reason' => 'Customer request',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/send-cancellation-notice'
payload = {
    "cancellation_reason": "Customer request"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Example response (HTTP 200):


{
    "message": "Cancellation notice queued successfully",
    "recipients": [
        {
            "email": "[email protected]",
            "phone": "+44 1234 567890"
        }
    ]
}
 

Example response (HTTP 400):


{
    "message": "No customer contact information available"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Request      

POST v1/bookings/{id}/send-cancellation-notice

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Body Parameters

cancellation_reason   string  optional    

Reason for cancellation Example: Customer request

Get labels by booking ID.

requires authentication

Returns concatenated label data for all labels in a booking. Currently only ZPL concatenation is supported.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/labels" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"format\": \"architecto\"
}"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/labels"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "format": "architecto"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/labels';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'format' => 'architecto',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/labels'
payload = {
    "format": "architecto"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, json=payload)
response.json()

Example response (HTTP 401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "Unauthenticated."
}
 

Request      

GET v1/bookings/{id}/labels

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Body Parameters

format   string  optional    

Example: architecto

Must be one of:
  • zpl
  • pdf
  • png

Add or update booking authorisations.

requires authentication

Adds or extends authorisation groups and locker users for an existing booking. The booking must belong to the current account context.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/authorisation" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"authorisation_group_ids\": [
        \"01ARZ3NDEKTSV4RRFFQ69G5FAY\"
    ],
    \"locker_user_ids\": [
        \"01ARZ3NDEKTSV4RRFFQ69G5FAZ\"
    ]
}"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/authorisation"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "authorisation_group_ids": [
        "01ARZ3NDEKTSV4RRFFQ69G5FAY"
    ],
    "locker_user_ids": [
        "01ARZ3NDEKTSV4RRFFQ69G5FAZ"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/authorisation';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'authorisation_group_ids' => [
                '01ARZ3NDEKTSV4RRFFQ69G5FAY',
            ],
            'locker_user_ids' => [
                '01ARZ3NDEKTSV4RRFFQ69G5FAZ',
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/authorisation'
payload = {
    "authorisation_group_ids": [
        "01ARZ3NDEKTSV4RRFFQ69G5FAY"
    ],
    "locker_user_ids": [
        "01ARZ3NDEKTSV4RRFFQ69G5FAZ"
    ]
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Example response (HTTP 200):


{
    "message": "Authorisations updated"
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Example response (HTTP 422):


{
    "message": "The given data was invalid.",
    "errors": {
        "authorisation_group_ids.0": [
            "The selected authorisation group ids.0 is invalid."
        ]
    }
}
 

Request      

POST v1/bookings/{id}/authorisation

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Body Parameters

authorisation_group_ids   string[]  optional    

Array of authorisation group IDs to add

locker_user_ids   string[]  optional    

Array of locker user IDs to add

Generate lock codes for booking lockers.

requires authentication

Generates access codes for all lockers associated with a booking. The booking must belong to the current account context.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/generate-codes" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/generate-codes"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/generate-codes';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/bookings/01ARZ3NDEKTSV4RRFFQ69G5FAV/generate-codes'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "message": "Lock codes generated",
    "codes": [
        {
            "locker_id": "01ARZ3NDEKTSV4RRFFQ69G5FAC",
            "code": "1234"
        }
    ]
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Booking not found"
}
 

Request      

POST v1/bookings/{id}/generate-codes

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the booking Example: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Cabinets

Display a listing of cabinets.

requires authentication

Returns a list of cabinets with optional filtering by status or location.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/cabinets?status=active&latitude=51.5074&longitude=-0.1278&radius=10" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/cabinets"
);

const params = {
    "status": "active",
    "latitude": "51.5074",
    "longitude": "-0.1278",
    "radius": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/cabinets';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'status' => 'active',
            'latitude' => '51.5074',
            'longitude' => '-0.1278',
            'radius' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/cabinets'
params = {
  'status': 'active',
  'latitude': '51.5074',
  'longitude': '-0.1278',
  'radius': '10',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()

Example response (HTTP 200):


{
    "data": [
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAA",
            "name": "Cabinet A1",
            "status": "active",
            "total_active_lockers": 10,
            "total_available_lockers": 5,
            "active_locker_ids": [
                "01ARZ3NDEKTSV4RRFFQ69G5FC1",
                "01ARZ3NDEKTSV4RRFFQ69G5FC2"
            ]
        }
    ]
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Request      

GET v1/cabinets

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by cabinet status name Example: active

latitude   number  optional    

Latitude for location-based search (must include longitude and radius) Example: 51.5074

longitude   number  optional    

Longitude for location-based search (must include latitude and radius) Example: -0.1278

radius   number  optional    

Radius in kilometers for location-based search (must include latitude and longitude) Example: 10

Display the specified cabinet.

requires authentication

Returns detailed information about a specific cabinet.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/cabinets/01ARZ3NDEKTSV4RRFFQ69G5FAA" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/cabinets/01ARZ3NDEKTSV4RRFFQ69G5FAA"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/cabinets/01ARZ3NDEKTSV4RRFFQ69G5FAA';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/cabinets/01ARZ3NDEKTSV4RRFFQ69G5FAA'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAA",
        "name": "Cabinet A1",
        "status": "active",
        "total_active_lockers": 10,
        "total_available_lockers": 5,
        "active_locker_ids": [
            "01ARZ3NDEKTSV4RRFFQ69G5FC1",
            "01ARZ3NDEKTSV4RRFFQ69G5FC2"
        ]
    }
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Cabinet not found"
}
 

Request      

GET v1/cabinets/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the cabinet Example: 01ARZ3NDEKTSV4RRFFQ69G5FAA

Collection Schedules

Display a listing of collection schedules for a despatch centre.

requires authentication

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW/collection-schedules" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW/collection-schedules"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW/collection-schedules';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW/collection-schedules'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": [
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
            "despatch_centre_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
            "name": "Weekday Morning Collection",
            "description": "Standard weekday collection window",
            "collection_start_time": "08:00:00",
            "collection_end_time": "10:00:00",
            "days_of_week": [
                0,
                1,
                2,
                3,
                4
            ],
            "is_active": true
        }
    ]
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Despatch centre not found or does not belong to your account"
}
 

Request      

GET v1/despatch-centres/{despatchCentreId}/collection-schedules

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

despatchCentreId   string     

The ID of the despatch centre Example: 01ARZ3NDEKTSV4RRFFQ69G5FAW

Display the specified collection schedule.

requires authentication

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW/collection-schedules/01ARZ3NDEKTSV4RRFFQ69G5FAX" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW/collection-schedules/01ARZ3NDEKTSV4RRFFQ69G5FAX"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW/collection-schedules/01ARZ3NDEKTSV4RRFFQ69G5FAX';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW/collection-schedules/01ARZ3NDEKTSV4RRFFQ69G5FAX'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
        "despatch_centre_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
        "name": "Weekday Morning Collection",
        "description": "Standard weekday collection window",
        "collection_start_time": "08:00:00",
        "collection_end_time": "10:00:00",
        "days_of_week": [
            0,
            1,
            2,
            3,
            4
        ],
        "effective_from": "2025-01-01T00:00:00Z",
        "effective_until": null,
        "is_active": true
    }
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Collection schedule not found"
}
 

Request      

GET v1/despatch-centres/{despatchCentreId}/collection-schedules/{collectionScheduleId}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

despatchCentreId   string     

The ID of the despatch centre Example: 01ARZ3NDEKTSV4RRFFQ69G5FAW

collectionScheduleId   string     

The ID of the collection schedule Example: 01ARZ3NDEKTSV4RRFFQ69G5FAX

Despatch Centres

Display a listing of despatch centres.

requires authentication

Returns a paginated list of despatch centres for the current account.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/despatch-centres?per_page=20&page=1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/despatch-centres"
);

const params = {
    "per_page": "20",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/despatch-centres';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'per_page' => '20',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/despatch-centres'
params = {
  'per_page': '20',
  'page': '1',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()

Example response (HTTP 200):


{
    "data": [
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
            "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
            "name": "Main Warehouse",
            "description": "Primary despatch centre",
            "address_id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
            "address": {
                "id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
                "line_1": "123 Industrial Way",
                "line_2": null,
                "city": "London",
                "postcode": "SW1A 1AA",
                "latitude": 51.5074,
                "longitude": -0.1278
            },
            "access_instructions": "Enter through main gate",
            "special_instructions": null,
            "contact_phone": "+44 20 1234 5678",
            "contact_email": "[email protected]",
            "is_active": true
        }
    ],
    "pagination": {
        "current_page": 1,
        "per_page": 50,
        "total": 10,
        "last_page": 1
    }
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Request      

GET v1/despatch-centres

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

per_page   integer  optional    

Number of items per page (max 100) Example: 20

page   integer  optional    

Page number Example: 1

Display the specified despatch centre.

requires authentication

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/despatch-centres/01ARZ3NDEKTSV4RRFFQ69G5FAW'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
        "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
        "name": "Main Warehouse",
        "description": "Primary despatch centre",
        "address_id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
        "address": {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
            "line_1": "123 Industrial Way",
            "line_2": null,
            "city": "London",
            "postcode": "SW1A 1AA",
            "latitude": 51.5074,
            "longitude": -0.1278
        },
        "access_instructions": "Enter through main gate",
        "is_active": true
    }
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Despatch centre not found"
}
 

Request      

GET v1/despatch-centres/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the despatch centre Example: 01ARZ3NDEKTSV4RRFFQ69G5FAW

Labels

Get ZPL label data by label ID.

requires authentication

Returns the ZPL (Zebra Programming Language) data for a specific label. The label must belong to a booking in the current account context.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/labels/1111111119/zpl" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/labels/1111111119/zpl"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/labels/1111111119/zpl';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/labels/1111111119/zpl'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "label": "^XA^FO50,50^FDTest ZPL Label^FS^XZ"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Label not found"
}
 

Example response (HTTP 404):


{
    "message": "ZPL data not available for this label"
}
 

Request      

GET v1/labels/{id}/zpl

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the label Example: 1111111119

Get PDF label data by label ID.

requires authentication

Returns the PDF binary data for a specific label. The label must belong to a booking in the current account context.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/labels/1111111119/pdf" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/labels/1111111119/pdf"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/labels/1111111119/pdf';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/labels/1111111119/pdf'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200, PDF binary data with Content-Type: application/pdf):



 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Label not found"
}
 

Example response (HTTP 404):


{
    "message": "PDF data not available for this label"
}
 

Request      

GET v1/labels/{id}/pdf

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the label Example: 1111111119

Get PNG label data by label ID.

requires authentication

Returns the PNG binary data for a specific label. The label must belong to a booking in the current account context.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/labels/1111111119/png" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/labels/1111111119/png"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/labels/1111111119/png';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/labels/1111111119/png'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200, PNG binary data with Content-Type: image/png):



 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Label not found"
}
 

Example response (HTTP 404):


{
    "message": "PNG data not available for this label"
}
 

Request      

GET v1/labels/{id}/png

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the label Example: 1111111119

Reverse Flows

Display a listing of reverse flow strategies.

requires authentication

Returns a paginated list of reverse flow strategies available to the current account context. Reverse flow strategies define how items should be handled when they cannot be delivered or collected from lockers.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/reverse-flows?per_page=20&page=1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/reverse-flows"
);

const params = {
    "per_page": "20",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/reverse-flows';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'per_page' => '20',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/reverse-flows'
params = {
  'per_page': '20',
  'page': '1',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()

Example response (HTTP 200):


{
    "data": [
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
            "name": "return_to_sender",
            "description": "Return to Sender",
            "is_active": true,
            "is_default": true
        },
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
            "name": "hold_at_sort",
            "description": "Hold at Sort Centre",
            "is_active": true,
            "is_default": false
        },
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
            "name": "allow_redelivery",
            "description": "Allow Redelivery",
            "is_active": true,
            "is_default": false
        }
    ],
    "pagination": {
        "current_page": 1,
        "per_page": 20,
        "total": 3,
        "last_page": 1,
        "from": 1,
        "to": 3,
        "has_more_pages": false
    },
    "links": {
        "first": "http://example.com/api/v1/reverse-flows?page=1",
        "last": "http://example.com/api/v1/reverse-flows?page=1",
        "prev": null,
        "next": null
    }
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Request      

GET v1/reverse-flows

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

per_page   integer  optional    

Number of items per page (max 100) Example: 20

page   integer  optional    

Page number Example: 1

Service Products

Display a listing of service products.

requires authentication

Returns a paginated list of service products available to the current account context. Service products define the types of booking services that can be used.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/service-products?per_page=20&page=1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/service-products"
);

const params = {
    "per_page": "20",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/service-products';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'per_page' => '20',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/service-products'
params = {
  'per_page': '20',
  'page': '1',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()

Example response (HTTP 200):


{
    "data": [
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ",
            "name": "direct_locker_delivery",
            "description": "Bookings are collected from a despatch centre and delivered directly to a locker",
            "requires_collection": true,
            "requires_delivery": true,
            "requires_locker": true,
            "requires_sorting_hub": false,
            "is_active": true,
            "is_default": true
        },
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
            "name": "sorted_locker_delivery",
            "description": "Bookings are collected from a despatch centre, taken to our sort centre and then delivered to a locker optimally",
            "requires_collection": true,
            "requires_delivery": true,
            "requires_locker": true,
            "requires_sorting_hub": true,
            "is_active": true,
            "is_default": false
        }
    ],
    "pagination": {
        "current_page": 1,
        "per_page": 20,
        "total": 2,
        "last_page": 1,
        "from": 1,
        "to": 2,
        "has_more_pages": false
    },
    "links": {
        "first": "http://example.com/api/v1/service-products?page=1",
        "last": "http://example.com/api/v1/service-products?page=1",
        "prev": null,
        "next": null
    }
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Request      

GET v1/service-products

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

per_page   integer  optional    

Number of items per page (max 100) Example: 20

page   integer  optional    

Page number Example: 1

Serviceability

List all serviceable postcode prefixes.

requires authentication

Returns a unique list of postcode prefixes mapped to active zones.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/serviceable-postcodes" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/serviceable-postcodes"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/serviceable-postcodes';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/serviceable-postcodes'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": [
        "E1",
        "N1",
        "SW1",
        "SE1"
    ]
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Request      

GET v1/serviceable-postcodes

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Sites

Display a listing of sites with search and pagination.

requires authentication

Returns a paginated list of active sites. Supports location-based filtering to find sites within a specified radius of coordinates.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/sites?latitude=51.5074&longitude=-0.1278&radius=10&postcode=SW1A+1AA&per_page=20&page=1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/sites"
);

const params = {
    "latitude": "51.5074",
    "longitude": "-0.1278",
    "radius": "10",
    "postcode": "SW1A 1AA",
    "per_page": "20",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/sites';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'latitude' => '51.5074',
            'longitude' => '-0.1278',
            'radius' => '10',
            'postcode' => 'SW1A 1AA',
            'per_page' => '20',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/sites'
params = {
  'latitude': '51.5074',
  'longitude': '-0.1278',
  'radius': '10',
  'postcode': 'SW1A 1AA',
  'per_page': '20',
  'page': '1',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()

Example response (HTTP 200):


{
    "data": [
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
            "name": "Downtown Location",
            "address": {
                "recipient": null,
                "line_1": "123 Main St",
                "line_2": null,
                "line_3": "Greater London",
                "city": "London",
                "postcode": "SW1A 1AA",
                "country": "United Kingdom",
                "contact_phone": null,
                "contact_email": null,
                "special_instructions": null,
                "latitude": 51.5074,
                "longitude": -0.1278
            },
            "latitude": 51.5074,
            "longitude": -0.1278,
            "access_instructions": "Enter through main entrance",
            "height_restriction": 2.5,
            "is_active": true,
            "distance": {
                "km": 2.5,
                "mi": 1.55
            },
            "site_access_codes": [
                {
                    "name": "Customer access code 1",
                    "code": "AB12CD34"
                }
            ]
        }
    ],
    "pagination": {
        "current_page": 1,
        "per_page": 50,
        "total": 25,
        "last_page": 1,
        "from": 1,
        "to": 25,
        "has_more_pages": false
    },
    "links": {
        "first": "http://example.com/api/v1/sites?page=1",
        "last": "http://example.com/api/v1/sites?page=1",
        "prev": null,
        "next": null
    }
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Request      

GET v1/sites

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

latitude   number  optional    

Latitude for location-based search (must include longitude and radius) Example: 51.5074

longitude   number  optional    

Longitude for location-based search (must include latitude and radius) Example: -0.1278

radius   number  optional    

Radius in kilometers for location-based search (must include latitude and longitude, or postcode when enabled) Example: 10

postcode   string  optional    

Postcode for location-based search (this may incur extra usage fees when enabled) Example: SW1A 1AA

per_page   integer  optional    

Number of items per page (max 100) Example: 20

page   integer  optional    

Page number Example: 1

Display the specified site.

requires authentication

Returns detailed information about a specific site.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/sites/01ARZ3NDEKTSV4RRFFQ69G5FAW" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/sites/01ARZ3NDEKTSV4RRFFQ69G5FAW"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/sites/01ARZ3NDEKTSV4RRFFQ69G5FAW';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/sites/01ARZ3NDEKTSV4RRFFQ69G5FAW'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
        "name": "Downtown Location",
        "address": {
            "recipient": null,
            "line_1": "123 Main St",
            "line_2": null,
            "line_3": "Greater London",
            "city": "London",
            "postcode": "SW1A 1AA",
            "country": "United Kingdom",
            "contact_phone": null,
            "contact_email": null,
            "email": null,
            "special_instructions": null,
            "latitude": 51.5074,
            "longitude": -0.1278
        },
        "latitude": 51.5074,
        "longitude": -0.1278,
        "access_instructions": "Enter through main entrance",
        "height_restriction": 2.5,
        "is_active": true,
        "site_access_codes": [
            {
                "name": "Customer access code 1",
                "code": "AB12CD34"
            }
        ]
    }
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Site not found"
}
 

Request      

GET v1/sites/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the site Example: 01ARZ3NDEKTSV4RRFFQ69G5FAW

Token Management

Generate API token for account.

requires authentication

Creates a new API token for the authenticated user in the current account context. Requires Admin or Owner role in the current account.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/tokens/generate" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"token_name\": \"Production API Token\",
    \"expires_at\": \"2025-12-31T23:59:59Z\"
}"
const url = new URL(
    "https://api.shiftcollect.com/v1/tokens/generate"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token_name": "Production API Token",
    "expires_at": "2025-12-31T23:59:59Z"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/tokens/generate';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'token_name' => 'Production API Token',
            'expires_at' => '2025-12-31T23:59:59Z',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/tokens/generate'
payload = {
    "token_name": "Production API Token",
    "expires_at": "2025-12-31T23:59:59Z"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Example response (HTTP 201):


{
    "token": "1|abcdefghijklmnopqrstuvwxyz1234567890",
    "token_name": "Production API Token",
    "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
    "abilities": [
        "*"
    ],
    "expires_at": "2025-12-31T23:59:59.000000Z",
    "created_at": "2025-01-01T00:00:00.000000Z",
    "message": "API token generated successfully"
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 422):


{
    "message": "The given data was invalid.",
    "errors": {
        "token_name": [
            "The token name field is required."
        ],
        "expires_at": [
            "The expires at must be a date after now."
        ]
    }
}
 

Request      

POST v1/tokens/generate

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token_name   string     

Name for the token Example: Production API Token

expires_at   string  optional    

Expiration date and time (ISO 8601). Must be in the future Example: 2025-12-31T23:59:59Z

List API tokens for account.

requires authentication

Returns all API tokens for the authenticated user in the current account context. Requires Admin or Owner role in the current account.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/tokens?search=Production" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/tokens"
);

const params = {
    "search": "Production",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/tokens';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'search' => 'Production',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/tokens'
params = {
  'search': 'Production',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()

Example response (HTTP 200):


{
    "tokens": [
        {
            "id": 1,
            "name": "Production API Token",
            "abilities": [
                "*"
            ],
            "last_used_at": "2025-01-15T10:30:00.000000Z",
            "created_at": "2025-01-01T00:00:00.000000Z",
            "expires_at": "2025-12-31T23:59:59.000000Z"
        }
    ],
    "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
    "total": 1
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Request      

GET v1/tokens

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

search   string  optional    

Search tokens by name Example: Production

Revoke a specific API token.

requires authentication

Revokes a single API token by ID. Requires Admin or Owner role in the current account.

Example request:

curl --request DELETE \
    "https://api.shiftcollect.com/v1/tokens/1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/tokens/1"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/tokens/1';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/tokens/1'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('DELETE', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "message": "API token revoked successfully"
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Token not found"
}
 

Request      

DELETE v1/tokens/{tokenId}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

tokenId   string     

The ID of the token to revoke Example: 1

Revoke all API tokens for account.

requires authentication

Revokes all API tokens for the authenticated user in the current account context. Requires Admin or Owner role in the current account.

Example request:

curl --request DELETE \
    "https://api.shiftcollect.com/v1/tokens" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/tokens"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/tokens';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/tokens'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('DELETE', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "message": "All API tokens revoked successfully",
    "deleted_count": 5
}
 

Example response (HTTP 400):


{
    "message": "No account context set"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Request      

DELETE v1/tokens

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Webhooks

Display a listing of webhooks.

requires authentication

Returns all webhooks for the authenticated user's account.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/webhooks" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/webhooks"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/webhooks';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/webhooks'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": [
        {
            "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
            "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
            "name": "Production Webhook",
            "description": "Webhook for production events",
            "url": "https://example.com/webhook",
            "secret": "whsec_...",
            "event_types": [
                "booking.created",
                "booking.cancelled"
            ],
            "is_active": true,
            "created_at": "2024-01-01T00:00:00.000000Z",
            "updated_at": "2024-01-01T00:00:00.000000Z"
        }
    ]
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Request      

GET v1/webhooks

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Create a new webhook.

requires authentication

Creates a webhook for the authenticated user's account. A unique secret will be generated automatically.

Example request:

curl --request POST \
    "https://api.shiftcollect.com/v1/webhooks" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Production Webhook\",
    \"description\": \"Webhook for production events\",
    \"url\": \"https:\\/\\/example.com\\/webhook\",
    \"event_types\": [
        \"booking.created\",
        \"booking.cancelled\"
    ],
    \"is_active\": false
}"
const url = new URL(
    "https://api.shiftcollect.com/v1/webhooks"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Production Webhook",
    "description": "Webhook for production events",
    "url": "https:\/\/example.com\/webhook",
    "event_types": [
        "booking.created",
        "booking.cancelled"
    ],
    "is_active": false
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/webhooks';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Production Webhook',
            'description' => 'Webhook for production events',
            'url' => 'https://example.com/webhook',
            'event_types' => [
                'booking.created',
                'booking.cancelled',
            ],
            'is_active' => false,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/webhooks'
payload = {
    "name": "Production Webhook",
    "description": "Webhook for production events",
    "url": "https:\/\/example.com\/webhook",
    "event_types": [
        "booking.created",
        "booking.cancelled"
    ],
    "is_active": false
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Example response (HTTP 201):


{
    "data": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
        "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
        "name": "Production Webhook",
        "description": "Webhook for production events",
        "url": "https://example.com/webhook",
        "secret": "whsec_...",
        "event_types": [
            "booking.created",
            "booking.cancelled"
        ],
        "is_active": true,
        "created_at": "2024-01-01T00:00:00.000000Z",
        "updated_at": "2024-01-01T00:00:00.000000Z"
    }
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 422):


{
    "message": "The given data was invalid."
}
 

Request      

POST v1/webhooks

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Name of the webhook Example: Production Webhook

description   string  optional    

Description of the webhook Example: Webhook for production events

url   string     

URL to send webhook events Example: https://example.com/webhook

event_types   string[]     

Array of event types to subscribe to

is_active   boolean  optional    

Example: false

Display the specified webhook.

requires authentication

Returns detailed information about a specific webhook.

Example request:

curl --request GET \
    --get "https://api.shiftcollect.com/v1/webhooks/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/webhooks/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/webhooks/architecto';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/webhooks/architecto'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "data": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
        "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
        "name": "Production Webhook",
        "description": "Webhook for production events",
        "url": "https://example.com/webhook",
        "secret": "whsec_...",
        "event_types": [
            "booking.created",
            "booking.cancelled"
        ],
        "is_active": true,
        "created_at": "2024-01-01T00:00:00.000000Z",
        "updated_at": "2024-01-01T00:00:00.000000Z"
    }
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Webhook not found"
}
 

Request      

GET v1/webhooks/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the webhook. Example: architecto

webhook   string     

The ID of the webhook Example: 01ARZ3NDEKTSV4RRFFQ69G5FAW

Update the specified webhook.

requires authentication

Updates a webhook's details.

Example request:

curl --request PUT \
    "https://api.shiftcollect.com/v1/webhooks/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Updated Webhook\",
    \"description\": \"Updated description\",
    \"url\": \"https:\\/\\/example.com\\/webhook\",
    \"event_types\": [
        \"booking.created\"
    ],
    \"is_active\": true
}"
const url = new URL(
    "https://api.shiftcollect.com/v1/webhooks/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Updated Webhook",
    "description": "Updated description",
    "url": "https:\/\/example.com\/webhook",
    "event_types": [
        "booking.created"
    ],
    "is_active": true
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/webhooks/architecto';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Updated Webhook',
            'description' => 'Updated description',
            'url' => 'https://example.com/webhook',
            'event_types' => [
                'booking.created',
            ],
            'is_active' => true,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/webhooks/architecto'
payload = {
    "name": "Updated Webhook",
    "description": "Updated description",
    "url": "https:\/\/example.com\/webhook",
    "event_types": [
        "booking.created"
    ],
    "is_active": true
}
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('PUT', url, headers=headers, json=payload)
response.json()

Example response (HTTP 200):


{
    "data": {
        "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
        "account_id": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
        "name": "Updated Webhook",
        "description": "Updated description",
        "url": "https://example.com/webhook",
        "secret": "whsec_...",
        "event_types": [
            "booking.created"
        ],
        "is_active": true,
        "created_at": "2024-01-01T00:00:00.000000Z",
        "updated_at": "2024-01-01T00:00:00.000000Z"
    }
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Webhook not found"
}
 

Example response (HTTP 422):


{
    "message": "The given data was invalid."
}
 

Request      

PUT v1/webhooks/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the webhook. Example: architecto

webhook   string     

The ID of the webhook Example: 01ARZ3NDEKTSV4RRFFQ69G5FAW

Body Parameters

name   string  optional    

Name of the webhook Example: Updated Webhook

description   string  optional    

Description of the webhook Example: Updated description

url   string  optional    

URL to send webhook events Example: https://example.com/webhook

event_types   string[]  optional    

Array of event types to subscribe to

is_active   boolean  optional    

Whether the webhook is active Example: true

Delete the specified webhook.

requires authentication

Permanently deletes a webhook.

Example request:

curl --request DELETE \
    "https://api.shiftcollect.com/v1/webhooks/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.shiftcollect.com/v1/webhooks/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.shiftcollect.com/v1/webhooks/architecto';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://api.shiftcollect.com/v1/webhooks/architecto'
headers = {
  'Authorization': 'Bearer {YOUR_API_TOKEN}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('DELETE', url, headers=headers)
response.json()

Example response (HTTP 200):


{
    "message": "Webhook deleted successfully"
}
 

Example response (HTTP 401):


{
    "message": "Unauthenticated."
}
 

Example response (HTTP 403):


{
    "message": "This action is unauthorised."
}
 

Example response (HTTP 404):


{
    "message": "Webhook not found"
}
 

Request      

DELETE v1/webhooks/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the webhook. Example: architecto

webhook   string     

The ID of the webhook Example: 01ARZ3NDEKTSV4RRFFQ69G5FAW

Webhooks and booking status reasons

This guide explains how webhooks work when you connect your own systems to Shift Collect, and how status reasons appear in webhook payloads and when you list or retrieve bookings through the API.

If you build order tracking, customer notifications, or reporting on top of our API, use the fields below to stay in sync with booking and parcel progress—including why something happened when we provide that level of detail.


How webhooks work


Shape of every webhook

Each delivery shares the same outer structure. Details specific to the event sit inside data.

{
  "id": "01JC...",
  "event": "booking.status_changed",
  "timestamp": 1709123456,
  "account_id": "01JC...",
  "webhook_id": "01JC...",
  "data": {
    "booking_id": "01JC...",
    "reference_id": "BK123456",
    "timestamp": "2024-02-29T14:30:00.000000Z",
    ...
  }
}
Field Type Description
id string Unique id for this notification. If you see the same id again, you can treat it as a duplicate and skip reprocessing.
event string Which event occurred, for example booking.status_changed.
timestamp integer Unix time (seconds) when we created this notification.
account_id string Your Shift Collect account id.
webhook_id string The id of the webhook configuration that received this delivery.
data object Fields for this event only (see the sections below).

Verifying requests are from Shift Collect

Every request includes:

To verify: compute HMAC-SHA256 using your webhook secret and the raw request body bytes, then compare the result (as hex) to the value after sha256=. Compare in a timing-safe way so the check cannot be abused to guess your secret.


Events you can subscribe to

When you create or update a webhook, you choose from the event types we support. The table shows whether we currently send that event. You may subscribe to types we do not send yet so your integration is ready when we add them.

Event type What it means We send this today
booking.created A new booking was created. Yes
booking.status_changed The overall status of the booking changed. Yes
booking.cancelled The booking was cancelled. Yes
labels.status_changed The status of one parcel label on a booking changed. Yes
booking.updated Other booking details changed (planned for a future release). No
locker.accessed Someone opened a locker door we associate with your flow (planned for a future release). No

Status reasons (extra detail on a booking)

Alongside the overall booking status (status, such as not_delivered or completed), we sometimes include why that status applies, using three fields:

Field When to use it
status_reason_id A stable id for that reason in our system, or null if there is no extra reason.
status_reason A stable code you can store, branch on, or use in reports (for example customer_not_home).
status_reason_label Text suitable to show to colleagues or customers (for example “Customer Not Home”).

Reasons always match the current overall status—we only send combinations that are valid for your booking. The clearest example is a failed delivery: you will usually see both not_delivered and a specific status_reason. Other stages may include a reason when the journey provides one (for example how a delivery was completed).


booking.status_changed

When you receive it

A booking can have one or more labels (parcels). Each label has its own timeline. The booking status is the combined picture across all labels on that booking.

We send booking.status_changed only when that combined status actually changes—for example from pending to confirmed, or to not_delivered after tracking updates.

Fields inside data

Field Type Description
booking_id string Our id for the booking.
reference_id string The reference you sent when the booking was created, if you provided one.
timestamp string When we recorded the change (ISO 8601).
status string The new overall booking status. See the table under “Overall booking status values”.
status_reason_id string | null Present when a specific reason applies.
status_reason string | null Stable reason code, when present.
status_reason_label string | null Human-friendly reason text, when present.

Example when a delivery could not be completed and we know the customer was not home:

{
  "id": "01JC...",
  "event": "booking.status_changed",
  "timestamp": 1709123456,
  "account_id": "01JC...",
  "webhook_id": "01JC...",
  "data": {
    "booking_id": "01JCXYZ...",
    "reference_id": "BK123456",
    "timestamp": "2024-02-29T14:30:00.000000Z",
    "status": "not_delivered",
    "status_reason_id": "01SR...",
    "status_reason": "customer_not_home",
    "status_reason_label": "Customer Not Home"
  }
}

Example when the overall status changes but no extra reason is attached:

{
  "data": {
    "booking_id": "01JCXYZ...",
    "reference_id": "BK123456",
    "timestamp": "2024-02-29T14:30:00.000000Z",
    "status": "ready_for_collection",
    "status_reason_id": null,
    "status_reason": null,
    "status_reason_label": null
  }
}

Overall booking status values

These are the exact string values you will see in data.status.

Status What it usually means for you
pending Awaiting confirmation.
confirmed Confirmed; collection will follow your schedule with us.
collected Collected by our network.
arrived_at_sort Arrived at the sorting facility.
customer_collection_arranged Customer collection from a locker has been arranged.
out_for_delivery Out for delivery.
ready_for_collection Ready for the recipient to collect from a locker.
completed The journey is complete from our perspective (for example collected from a locker).
cancelled Cancelled.
not_collected Not collected.
not_delivered Not delivered—check status_reason / status_reason_label when present.
collection_not_arranged Collection was not arranged.
customer_did_not_collect The recipient did not collect in time.
returned_to_sender Returned toward the sender or your despatch point.
returned_to_sort Returned to the sorting facility.
unknown We could not map the situation to a clearer status from the information available.

labels.status_changed

When you receive it

Whenever one label’s status changes (for example after a scan or a tracking update), we:

That lets you show parcel-level tracking in your own app while still using booking.status_changed for a single “headline” status per order.

Fields inside data

Field Type Description
label_id string The label whose status changed.
booking_id string The booking that owns this label.
reference_id string Your booking reference from creation, if you supplied one.
timestamp string When we recorded the label change (ISO 8601).
status string The new status for this label. See “Parcel label status values”.
booking_status_reason_id string | null When we can tie this update to an overall-booking reason, its id.
booking_status_reason string | null Same situation: stable reason code for the booking, when known.
booking_status_reason_label string | null Same situation: display text for that booking reason, when known.
note string Short context (for example that the update came from tracking).

On rare timing edges, you might see reason fields here before the next GET /v1/bookings/{id} shows them on the booking object. You can rely on the webhook payload for what happened at that moment.

Example:

{
  "id": "01LC...",
  "event": "labels.status_changed",
  "timestamp": 1709123456,
  "account_id": "01AC...",
  "webhook_id": "01WH...",
  "data": {
    "label_id": "01LBXYZ...",
    "booking_id": "01BKXYZ...",
    "reference_id": "BK123456",
    "timestamp": "2024-02-29T14:30:00.000000Z",
    "status": "not_delivered",
    "booking_status_reason_id": "01SR...",
    "booking_status_reason": "customer_not_home",
    "booking_status_reason_label": "Customer Not Home",
    "note": "Status updated via tracking event"
  }
}

Parcel label status values

These are the exact string values you will see in data.status for label events.

Status What it usually means
pending Awaiting processing.
confirmed Confirmed.
collected Collected (for example from sender or depot).
arrived_at_sort Arrived at sort.
out_for_delivery Out for delivery.
ready_for_collection Ready for collection at a locker.
completed This parcel’s journey is complete.
failed Something went wrong; see note and any booking reason fields when we provide them.
not_collected Not collected.
not_delivered Not delivered.
returned_to_sender Returned toward sender or your despatch centre.
returned_to_sort Returned to sort.
customer_did_not_collect Recipient did not collect.
unknown We could not map the situation to a clearer status from the information available.

Other events

booking.created

Sent when a new booking is successfully created through the API.

data contains:

Field Type
booking_id string
reference_id string
timestamp string (ISO 8601)

There is no status or reason on this event. Listen for booking.status_changed for later updates.


booking.cancelled

Sent when a booking is cancelled through the API.

data contains:

Field Type
booking_id string
reference_id string
timestamp string (ISO 8601)

Listing and retrieving bookings

GET /v1/bookings and GET /v1/bookings/{id} return the same overall and reason fields you see on webhooks, plus history:

Field Description
status Current overall booking status.
status_reason_id, status_reason, status_reason_label Extra detail for the current status, when we have it.
status_history Past steps; each step includes status and the same three reason fields when they were recorded at that time.

Practical tip: use status_reason (and status) in your logic and data warehouse; use status_reason_label when you need wording suitable for a screen or email.


Trying webhooks in sandbox

In sandbox environments you can use our simulate status endpoint to move a booking through statuses and trigger the same webhooks you would see in production when the overall status or labels change. See POST /v1/sandbox/bookings/{id}/simulate-status in the main API documentation (only where sandbox is enabled).

Always verify X-Webhook-Signature using the secret shown when you created the webhook—both in tests and in production.