Bullet Express Courier Integration with Voila
This document provides a step-by-step guide to integrating Bullet Express with Voila.
Last updated 2 days ago

Bullet Express Courier Integration with Voila
Bullet Express specialises in whisking pallets and freight across the United Kingdom with admirable gusto; this guide shows you how to wire it into the Voila API so your consignments glide from warehouse to wheels with minimal human button‑mashing.
By the end, you will be able to:
Authenticate Bullet Express against a Voila API User using:
Company,
Testing and
API Key
Fetch available Bullet Express services for your account
Create pallet and freight labels and download the PDF
Track consignments with full event history
Retrieve ETAs and proof of delivery (POD) images
Cancel consignments when required

What this integration does
This tutorial walks you through how to connect Bullet Express to Voila so that you can:
Discover which Bullet Express services and unit codes are available for your account
Create consignments using a recommended
service_identifieror explicitserviceandunit_codeTrack, get ETA information, download POD images, and cancel consignments programmatically
Everything here is designed to be called from your own system; Voila takes care of talking to Bullet Express; you just send tidy JSON and enjoy tidy labels.
Prerequisites
Before you start, make sure you have:
An active Voila account with access to API Accounts
A Bullet Express account (via Vigo)
Your Bullet Express API Key from your Vigo account
Once you have these, you are ready to move from “phoning the depot” to “orchestrating consignments via API”.

Step 1: Set up or access your Voila API Account
Log in to Voila.
From the left sidebar, go to API Accounts.
Either:
Select an existing API User you will use for Bullet Express, or
Click Create API User and provide a name; for example
bullet-express-production.
Click Create, then note down:
API User Name; for example
bullet-express-productionAccount Key
API Token (create one if none exists)
You will use api-user and api-token in the request headers for all Bullet Express API calls.

Step 2: Register Bullet Express as a courier
In this step you bind your Bullet Express credentials to a Voila API User using three key fields: Company, Testing, and API Key.
In the API Accounts page, locate your chosen API User.
In the Registered Auth column, click the + icon.
In the modal:
Choose Bullet Express from the courier dropdown.
Fill in the Bullet Express credentials:
Company Name: a friendly identifier; for example
MyBulletAccountTesting: select whether this auth should use the Bullet Express test environment (
Yes) or live environment (No)API Key: your Bullet Express / Vigo API key
Click Save.
To verify, click View in the Registered Auth column and confirm Bullet Express appears in the list, with your Company Name and Testing flag as expected.
> The Company Name you enter here is important: if you provide one, you must send it as auth_company in your API requests. If you did not set a Company Name, do not include auth_company.
> The Testing setting controls whether Voila talks to Bullet Express’ test or live environment for this API User; use Yes while integrating; use No for production.

Step 3: Get available Bullet Express shipping services
Before creating labels, you can fetch the available Bullet Express services for your account. This returns a list of service and unit_code combinations wrapped in a convenient service_identifier.
Endpoint
POST: https://app.heyvoila.io/api/couriers/v1/BulletExpress/get-services
Headers
api-user: your API User Nameapi-token: your API TokenContent-Type:application/json
Example request body
{
"auth_company": "MyBulletAccount"
} If you did not set a Company Name when registering auth, omit the auth_company field entirely.
Example response
{
"services": [
{
"service_courier": "BulletExpress",
"service_identifier": "eyJzZXJ2aWNlIjoiTkQiLCJ1bml0X2NvZGUiOiJFUCJ9",
"service_name": "Next Day - Euro Pallet (1 working days)",
"service_price": null
},
{
"service_courier": "BulletExpress",
"service_identifier": "eyJzZXJ2aWNlIjoiRUMiLCJ1bml0X2NvZGUiOiJFUCJ9",
"service_name": "Economy - Euro Pallet (3 working days)",
"service_price": null
}
]
}Record the service_identifier for any services you intend to use. It is a base64‑encoded JSON object containing the service code and unit_code. You can pass it directly when creating labels, or decode it if you want to inspect the underlying values.

Step 4 (optional): Create Bullet Express service presets
Service presets let you save courier‑specific settings and reuse them simply by sending a dc_service_id in your API calls. This keeps your integration tidy, and makes it easy to switch services without changing your own code.
4.1 Create a Bullet Express preset
In the Voila sidebar, go to Couriers → Service Presets.
Search for Bullet Express and select it.
Choose the API Account you set up earlier.
Click Create Preset and fill in:
Name; for example
Bullet Next Day Euro PalletDC Service ID; a unique ID; for example
BULLET-ND-EPOptional notes (for your own reference)
In the courier‑specific section, configure:
service_identifier: base64‑encoded service fromget-services(recommended)service: service code; for example"ND"; alternative toservice_identifierunit_code: unit code; for example"EP"; alternative toservice_identifiersurcharges: array of surcharge codes; for example["AM", "LF"]consignment_type:1(Collection),2(Delivery; default),3(Collection and Delivery)item_type: default item type for parcels; for example"FULL"or"HALF"is_residential: whether the delivery address is residentialcustomer_paperwork_required: whether paperwork is required at delivery
Configure Transit Times (required): set minimum and maximum transit days per zone.
Save the preset.
4.2 Example preset patterns
Some useful presets you might define:
Next Day Euro Pallet
dc_service_id:BULLET-ND-EPservice:"ND"unit_code:"EP"
Economy with AM surcharge
dc_service_id:BULLET-EC-AMservice:"EC"unit_code:"EP"surcharges:["AM"]
Presets are applied when you send dc_service_id in the shipment object; you can still override individual fields in the courier object.

Step 5: Create a Bullet Express shipping label
Once your API User and Bullet Express auth (Company Name, Testing Yes/No, API Key) are configured, you can start generating labels.
Endpoint
POST: https://app.heyvoila.io/api/couriers/v1/BulletExpress/create-label
Headers
api-user: your API User Nameapi-token: your API TokenContent-Type:application/json
5.1 Request using service_identifier (recommended)
The easiest and most robust approach is to use the service_identifier you received from the get-services endpoint.
{
"auth_company": "MyBulletAccount",
"request_id": "unique-request-id",
"shipment": {
"collection_date": "2026-03-21",
"reference": "REF123",
"ship_from": {
"company_name": "Warehouse Ltd",
"address_1": "123 Industrial Estate",
"postcode": "AB1 2CD",
"country_iso": "GB",
"phone": "0123456789"
},
"ship_to": {
"company_name": "Customer Co",
"address_1": "456 Business Park",
"postcode": "XY9 8ZA",
"country_iso": "GB",
"phone": "09876543210",
"email": "receiving@customer.co"
},
"parcels": [
{
"weight": 150,
"quantity": 2
}
],
"courier": {
"service_identifier": "eyJzZXJ2aWNlIjoiTkQiLCJ1bml0X2NvZGUiOiJFUCJ9"
}
}
}service_identifier is a base64‑encoded JSON object containing the Bullet Express service and unit_code values for this consignment.
5.2 Request using service and unit_code directly
If you do not want to use service_identifier, you can send the service code and unit code explicitly and add surcharges and options as needed.
{
"auth_company": "MyBulletAccount",
"request_id": "unique-request-id",
"shipment": {
"collection_date": "2026-03-21",
"reference": "REF123",
"delivery_instructions": "Use rear loading bay",
"ship_from": {
"company_name": "Warehouse Ltd",
"address_1": "123 Industrial Estate",
"postcode": "AB1 2CD",
"country_iso": "GB",
"phone": "0123456789"
},
"ship_to": {
"company_name": "Customer Co",
"address_1": "456 Business Park",
"postcode": "XY9 8ZA",
"country_iso": "GB",
"phone": "09876543210",
"email": "receiving@customer.co"
},
"parcels": [
{
"weight": 150,
"quantity": 2,
"item_type": "FULL"
}
],
"courier": {
"service": "ND",
"unit_code": "EP",
"surcharges": ["AM"],
"consignment_type": 2,
"delivery_time": "10:00",
"is_residential": false,
"customer_paperwork_required": false
}
}
}5.3 Request using a preset (`dc_service_id`)
If you have created a preset, you can reference it directly using dc_service_id. The preset holds the service configuration so that your integration does not need to.
{
"auth_company": "MyBulletAccount",
"request_id": "unique-request-id",
"shipment": {
"dc_service_id": "BULLET-ND-EP",
"collection_date": "2026-03-21",
"reference": "REF123",
"ship_from": {
"company_name": "Warehouse Ltd",
"address_1": "123 Industrial Estate",
"postcode": "AB1 2CD",
"country_iso": "GB",
"phone": "0123456789"
},
"ship_to": {
"company_name": "Customer Co",
"address_1": "456 Business Park",
"postcode": "XY9 8ZA",
"country_iso": "GB",
"phone": "09876543210"
},
"parcels": [
{
"weight": 150,
"quantity": 2
}
]
}
}
Preset values are automatically applied; you can still override individual fields by including them in the courier object.
5.4 Successful response
A successful label creation returns something similar to:
{
"type": "PDF",
"request_id": "unique-request-id",
"tracking_codes": ["BULLET123456"],
"tracking_urls": [],
"label_url": "https://s3.../label.pdf",
"uri": "https://s3.../label.pdf",
"courier_specifics": {
"consignment_number": "CON12345",
"warnings": []
}
}
You can store the tracking_codes, consignment_number, and label_url in your own system so that users can see and download labels and track consignments.

Step 6: Track a consignment
Once a label has been created, you can retrieve tracking events for the consignment.
Endpoint
POST: https://app.heyvoila.io/api/couriers/v1/BulletExpress/track
Request body
{
"auth_company": "MyBulletAccount",
"tracking_code": "BULLET123456"
} Example response
{
"tracking_code": "BULLET123456",
"consignment_number": "CON12345",
"customer_reference": "REF123",
"events": [
{
"code": "DEL",
"description": "Delivered",
"timestamp": "2026-03-22T10:30:00",
"item_number": 1,
"pod_name": "J Smith"
},
{
"code": "OFD",
"description": "Out for delivery",
"timestamp": "2026-03-22T07:15:00",
"item_number": 1,
"pod_name": null
}
]
}

Step 7: Get ETA
You can fetch the estimated delivery window for a consignment.
Endpoint
POST: https://app.heyvoila.io/api/couriers/v1/BulletExpress/get-eta
Request body
{
"auth_company": "MyBulletAccount",
"tracking_code": "BULLET123456"
}Example response
{
"tracking_code": "BULLET123456",
"consignment_number": "CON12345",
"etas": [
{
"start_window": "2026-03-22T09:00:00",
"end_window": "2026-03-22T12:00:00",
"uploaded": "2026-03-22T06:00:00"
}
]
}
Step 8: Get proof of delivery (POD)
You can retrieve POD images for a delivered consignment.
Endpoint
POST: https://app.heyvoila.io/api/couriers/v1/BulletExpress/get-pod
Request body
{
"auth_company": "MyBulletAccount",
"tracking_code": "BULLET123456"
}Example response
{
"tracking_code": "BULLET123456",
"consignment_number": "CON12345",
"images": [
{
"image_type": "POD",
"uid": "img-uuid-123",
"data": "base64-encoded-image-data...",
"mime_type": "image/png",
"filename": "pod_image.png",
"captured": "2026-03-22T10:30:00",
"uploaded": "2026-03-22T10:35:00"
}
]
}
Step 9: Cancel a consignment
If a consignment has been created in error, you can request cancellation.
Endpoint
POST: https://app.heyvoila.io/api/couriers/v1/BulletExpress/cancel-label
Request body
{
"auth_company": "MyBulletAccount",
"tracking_code": "BULLET123456"
}Example response
{
"success": true
}
Request field reference (for implementers)
Top level fields
auth_company(string, conditional): Company Name used when registering Bullet Express. Omit this if you did not set a Company Name in auth.request_id(string, optional): your unique request identifier.dc_service_id(string, optional): ID of the service preset to apply.
shipment object
collection_date(date, required):YYYY-MM-DD; must be today or later.delivery_date(date, optional):YYYY-MM-DD.reference(string, optional): customer reference; maximum 16 characters.reference_2(string, optional): secondary reference; maximum 16 characters.delivery_instructions(string, optional): maximum 96 characters; split across four lines for the carrier.
ship_from and ship_to objects
company_nameorname(string, required): at least one must be provided; maximum 30 characters.address_1(string, required): maximum 30 characters.address_2,address_3,address_4(string, optional): maximum 30 characters each.postcode(string, required): maximum 10 characters.country_iso(string, required): ISO‑2 country code; for example"GB"; converted to ISO‑3 internally.phone(string, optional): 9 to 16 characters.mobile(string, optional): used for SMS notifications if provided.email(string, optional): maximum 50 characters.
parcels array (required; minimum 1)
weight(number, required): weight in kilograms; minimum0.1.quantity(integer, optional): number of items; default1.item_type(string, optional): for example"FULL"or"HALF"; defaults to"FULL"or the courier‑levelitem_type.
Courier‑specific fields (`courier` object)
service_identifier(string, recommended): base64‑encoded service fromget-services; contains bothserviceandunit_code.service(string, required if noservice_identifier): service code; for example"ND"for Next Day.unit_code(string, required if noservice_identifier): unit code; for example"EP"for Euro Pallet; maximum 2 characters.surcharges(array, optional): surcharge codes; for example["AM", "LF"].consignment_type(integer, optional):1= Collection;2= Delivery (default);3= Collection and Delivery.consignment_number(string, optional): custom consignment reference; maximum 7 characters; falls back toreferencethenorder_id.delivery_time(string, optional):HH:MMformat.collection_time(string, optional):HH:MMformat.customer_paperwork_required(boolean, optional): whether paperwork is required at delivery; defaultfalse.total_spaces(integer, optional): override calculated pallet spaces.cubic_metres(number, optional): total cubic metres of the shipment.item_type(string, optional): default item type for all parcels; for example"FULL"; default"FULL".is_residential(boolean, optional): whether the delivery address is residential; defaultfalse.delivery_notes(object, optional):{ "Line1", "Line2", "Line3", "Line4" }; maximum 48 characters per line.collection_notes(object, optional): same structure asdelivery_notes.

Common errors and how to fix them
API User not authenticated
Check
api-userandapi-tokenheaders are correct.Confirm the API User is enabled.
not authenticated for this courier
Bullet Express credentials may be missing.
auth_companymight not match the registered Company Name.Confirm the Testing Yes/No flag on your auth matches the environment you expect.
service and unit_code required
Either provide
service_identifierfromget-services, or bothserviceandunit_codeexplicitly.
collection_date must be today or later
Ensure
collection_dateis not in the past.
Unsupported country code
Bullet Express primarily supports UK deliveries; check
country_isois a valid ISO‑2 code such as"GB".
Address field too long
Address lines are capped at 30 characters; postcode at 10; delivery notes at 48 per line.
consignment_number max 7 characters
If you use a custom consignment number, keep it to seven characters or fewer.
For authentication issues, verify:
api-useris your API User Name.api-tokenis a valid token from the Tokens table.Bullet Express courier credentials are registered and active, including API Key.
auth_companyexactly matches the Company Name (if used).The Testing Yes/No setting is correct for the environment you intend to hit.
