Overview
The Eaglix API lets your warehouse, WMS, ERP or fleet system price and book Eaglix jobs, follow them through to delivery, and get told the moment anything changes. Jobs you book through the API appear in your Eaglix portal like any other booking, at the same prices.
- Base URL:
https://www.eaglixfreight.co.uk/api/v1 - Requests and responses are JSON, with snake_case field names. Send
Content-Type: application/jsonon POST requests. - HTTPS only. The API is for server to server calls: keep your key on your server, never in a browser or mobile app.
- Prices are in pounds sterling (GBP), including our fee. Dates are UK dates (
YYYY-MM-DD), times in responses are ISO 8601 in UTC, for example2026-10-28T10:14:02.381+00:00.
Getting an API key
You need a business account on the Eaglix portal. An account owner or admin signs in to the portal, opens API & webhooks (on a phone it is under More), and creates a key. Give it a name that says where it is used, for example "Warehouse WMS".
The full key (it starts ek_live_) is shown once. Copy it into your system's secret store straight away. We only keep a fingerprint of it, so we can't show it again. If a key is lost or leaked, revoke it on the same page and create a new one: it stops working immediately.
A key books on behalf of your company, as the person who created it. If that person leaves your team, the key carries on working on behalf of the account owner.
Authentication
Send your key in the Authorization header of every request:
Authorization: Bearer ek_live_YOUR_KEYA missing, wrong or revoked key gets 401. If online booking is paused on your account, you get 403: call us to have it switched back on.
Errors
Every error has the same shape, with a message that says what went wrong and how to fix it:
{
"error": "\"delivery_postcode\" is required for this service."
}| Status | Meaning |
|---|---|
| 200, 201 | It worked. 201 means a new job was booked. |
| 400 | Something in your request needs fixing. The message names the field. |
| 401 | Missing, invalid or revoked API key. |
| 403 | Your account or key is not allowed to do this right now. |
| 404 | No such job for your company, or no such endpoint. |
| 405 | Wrong method for this endpoint, for example GET on a cancel. The Allow header lists the right one. |
| 409 | The job is in a state that does not allow this, for example cancelling once a driver is booked, or cancelling a job that is already cancelled. |
| 500, 503 | A problem on our side. Wait a little and retry with the same Idempotency-Key. |
List services
/api/v1/servicesThe services you can bookFreight services (Parcel, Pallet, Part Load, Full Load, Multi Drop, Same Day Courier, Temperature Controlled, Other) are priced on distance, and you can send a weight. Moves and clearances (Man and Van, Domestic Removals, Commercial and Office Relocation, Furniture and Appliance Delivery, Waste Removal and Clearance) are priced on size instead: you must send a size from that service's list. Use the exact names this endpoint returns.
curl https://www.eaglixfreight.co.uk/api/v1/services \
-H "Authorization: Bearer ek_live_YOUR_KEY"{
"groups": [
{
"label": "Freight and courier",
"services": [
{
"name": "Pallet",
"priced_by": "distance",
"size": null,
"accepts_weight": true,
"needs_delivery": true
}
]
},
{
"label": "Moves and clearances",
"services": [
{
"name": "Man and Van",
"priced_by": "size",
"size": {
"label": "Load size",
"options": [
"A few items",
"Quarter van load",
"Half van load",
"Full van load",
"Not sure"
]
},
"accepts_weight": false,
"needs_delivery": true
}
]
}
],
"access_options": [
"House or ground floor",
"Flat with a lift",
"Flat, stairs only",
"Not sure"
],
"max_vans": 10
}Get a price
/api/v1/quotesInstant price. Nothing is saved or booked.Send the same body you would send to book (see below). The date is optional here and defaults to today. miles is the estimated road distance, or null for Waste Removal and Clearance (no delivery address) and when we can't place one of the postcodes.
curl https://www.eaglixfreight.co.uk/api/v1/quotes \
-H "Authorization: Bearer ek_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"service":"Pallet","collection_postcode":"B11 2AA","delivery_postcode":"M17 1HG","weight":450}'{
"price": 186.4,
"currency": "GBP",
"miles": 89
}The price you are quoted is the price you are charged if you book the same job, unless our price list changes in between.
Book a job
/api/v1/jobsBook at the instant priceThe job is confirmed straight away at the instant price and offered to our drivers. It is created with status accepted and its price already set: there is no quote step. You get 201 with the job and its public tracking link, which you can pass on to the person receiving the goods.
| Field | Required | What to send |
|---|---|---|
service | Yes | A service name from GET /api/v1/services. |
collection_postcode | Yes | UK postcode, for example B11 2AA. |
collection_address | No | Street address, unit or building name. Up to 200 characters. |
delivery_postcode | Yes, except Waste Removal and Clearance | UK postcode. |
delivery_address | No | Up to 200 characters. |
date | Yes | Collection day as YYYY-MM-DD, today or later (UK time). |
time | No | Collection time, for example 09:00 or "AM". Up to 20 characters. |
vans | No | Number of vans, 1 to 10. Defaults to 1. |
size | For moves and clearances | One of the options listed for the service. |
access | No | One of: House or ground floor, Flat with a lift, Flat, stairs only, Not sure. |
weight | No | Total weight in kilograms, as a number. For freight services. |
goods | No | What we are moving, for example "2 pallets of boxed stock". Up to 300 characters. |
notes | No | Anything the driver should know: loading bay, tail lift, booking-in slot. Up to 1000 characters. |
reference | No | Your order or PO number. Shown on the job and on invoices. Up to 60 characters. |
contact_name | No | Person on site. Defaults to the key owner. |
contact_phone | No | Their phone number. Defaults to the key owner or your company number. |
You can also send the nested shape the API returns (collection.postcode, delivery.address, contact.name) or the camelCase names our portal uses (collectionPostcode, jobSize, items). Unknown fields are ignored.
curl https://www.eaglixfreight.co.uk/api/v1/jobs \
-H "Authorization: Bearer ek_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: PO-55821" \
-d '{
"service": "Pallet",
"collection_postcode": "B11 2AA",
"collection_address": "Unit 4, Tyseley Industrial Estate, Birmingham",
"delivery_postcode": "M17 1HG",
"delivery_address": "Goods In, 12 Trafford Park Road, Manchester",
"date": "2026-11-03",
"time": "09:00",
"weight": 450,
"goods": "2 pallets of boxed stock",
"notes": "Tail lift needed at delivery",
"reference": "PO-55821",
"contact_name": "Sam Patel",
"contact_phone": "07700 900123"
}'{
"job": {
"id": "6f1c2a9e-4b7d-4c1e-9a53-2d8f0b7e1c44",
"tracking_code": "EF-7K2M9QX4LP",
"status": "accepted",
"service": "Pallet",
"collection": {
"address": "Unit 4, Tyseley Industrial Estate, Birmingham",
"postcode": "B11 2AA"
},
"delivery": {
"address": "Goods In, 12 Trafford Park Road, Manchester",
"postcode": "M17 1HG"
},
"date": "2026-11-03",
"time": "09:00",
"vans": 1,
"size": null,
"access": null,
"goods": "2 pallets of boxed stock, 450 kg",
"notes": "Tail lift needed at delivery",
"reference": "PO-55821",
"contact": {
"name": "Sam Patel",
"phone": "07700 900123"
},
"price": 186.4,
"currency": "GBP",
"created_at": "2026-10-28T10:14:02.381+00:00",
"updated_at": "2026-10-28T10:14:02.381+00:00"
},
"tracking_url": "https://www.eaglixfreight.co.uk/track/EF-7K2M9QX4LP"
}delivery is null for Waste Removal and Clearance. goods includes the weight you sent. price is the total for all vans.
Idempotency: safe retries
Networks drop. To make sure a retry never books the same job twice, send an Idempotency-Key header with every booking: any value up to 100 characters that is unique to that booking in your system, such as your order number or a UUID.
- The first request with a key books the job and returns
201. - Any later request from your company with the same key books nothing. It returns
200, the original job (as it is now), and the headerIdempotent-Replayed: true. This is true even if the body is different, so use a new key for a new booking. - Keys never expire, and are separate for each company.
- Without the header, every request books a new job.
List jobs
/api/v1/jobsYour company's jobs, newest firstIncludes every job booked for your company: in the portal, through the API, and by our team.
| Query parameter | What it does |
|---|---|
status | Only these statuses, comma separated, for example status=assigned,en_route_delivery. |
updated_since | Only jobs changed at or after this ISO 8601 time, for example 2026-11-03T09:00:00Z, or a date such as 2026-11-03 (midnight UTC). Good for catching up after downtime. |
limit | Jobs per page, 1 to 200. Defaults to 50. |
cursor | The next_cursor value from the previous page. Leave it out for the first page. |
curl "https://www.eaglixfreight.co.uk/api/v1/jobs?status=accepted,assigned&limit=2" \
-H "Authorization: Bearer ek_live_YOUR_KEY"{
"jobs": [
{
"id": "6f1c2a9e-4b7d-4c1e-9a53-2d8f0b7e1c44",
"tracking_code": "EF-7K2M9QX4LP",
"status": "accepted",
"service": "Pallet",
"collection": {
"address": "Unit 4, Tyseley Industrial Estate, Birmingham",
"postcode": "B11 2AA"
},
"delivery": {
"address": "Goods In, 12 Trafford Park Road, Manchester",
"postcode": "M17 1HG"
},
"date": "2026-11-03",
"time": "09:00",
"vans": 1,
"size": null,
"access": null,
"goods": "2 pallets of boxed stock, 450 kg",
"notes": "Tail lift needed at delivery",
"reference": "PO-55821",
"contact": {
"name": "Sam Patel",
"phone": "07700 900123"
},
"price": 186.4,
"currency": "GBP",
"created_at": "2026-10-28T10:14:02.381+00:00",
"updated_at": "2026-10-28T10:14:02.381+00:00"
},
{
"...": "next job"
}
],
"next_cursor": "MjAyNi0xMC0yOFQwOTo1..."
}When next_cursor is null you have reached the last page. Treat the cursor as an opaque value: don't build or change it.
Get one job
/api/v1/jobs/{ref}One job, its timeline and drivers{ref} is the job id or its tracking code (EF-...). You can only see your own company's jobs. For each van you get the driver's first name and van type, never their contact details.
events is the job's timeline, oldest first by at, and always in the same order each time you fetch it. For a job booked in the portal or through the API it starts with three entries recorded at the moment of booking, a few milliseconds apart and always in this order: quote_requested, then quoted, then accepted. The job itself is accepted from the start, and its status is never one of the earlier two. A status can appear more than once: a job with several vans gets an entry for each van as it moves on, and accepted comes up again if a van still needs a driver once bidding ends or a driver is taken off the job.
curl https://www.eaglixfreight.co.uk/api/v1/jobs/EF-7K2M9QX4LP \
-H "Authorization: Bearer ek_live_YOUR_KEY"{
"job": {
"id": "6f1c2a9e-4b7d-4c1e-9a53-2d8f0b7e1c44",
"tracking_code": "EF-7K2M9QX4LP",
"status": "en_route_delivery",
"service": "Pallet",
"collection": {
"address": "Unit 4, Tyseley Industrial Estate, Birmingham",
"postcode": "B11 2AA"
},
"delivery": {
"address": "Goods In, 12 Trafford Park Road, Manchester",
"postcode": "M17 1HG"
},
"date": "2026-11-03",
"time": "09:00",
"vans": 1,
"size": null,
"access": null,
"goods": "2 pallets of boxed stock, 450 kg",
"notes": "Tail lift needed at delivery",
"reference": "PO-55821",
"contact": {
"name": "Sam Patel",
"phone": "07700 900123"
},
"price": 186.4,
"currency": "GBP",
"created_at": "2026-10-28T10:14:02.381+00:00",
"updated_at": "2026-10-28T10:14:02.381+00:00",
"status_label": "On the way to the delivery address"
},
"events": [
{
"status": "quote_requested",
"at": "2026-10-28T10:14:02.402+00:00"
},
{
"status": "quoted",
"at": "2026-10-28T10:14:02.455+00:00"
},
{
"status": "accepted",
"at": "2026-10-28T10:14:02.456+00:00"
},
{
"status": "assigned",
"at": "2026-10-28T11:02:40.917+00:00"
},
{
"status": "en_route_collection",
"at": "2026-11-03T07:41:12.004+00:00"
},
{
"status": "collected",
"at": "2026-11-03T08:58:30.775+00:00"
},
{
"status": "en_route_delivery",
"at": "2026-11-03T09:05:19.220+00:00"
}
],
"drivers": [
{
"van_number": 1,
"first_name": "Daniel",
"van_type": "LWB",
"status": "en_route_delivery"
}
],
"tracking_url": "https://www.eaglixfreight.co.uk/track/EF-7K2M9QX4LP"
}The tracking link shows the van live on a map while it is on the way. Anyone with the link can open it, so share it with care.
Cancel a job
/api/v1/jobs/{ref}/cancelCancel before a driver is bookedYou can cancel while the job is accepted (or still being quoted) and no van has a driver yet. On a job with more than one van the status stays accepted until every van has a driver, so one booked van is enough to stop an online cancel. Once a driver is on it you get 409: call us on 07438 569191 instead, so the driver isn't left waiting at the collection.
curl -X POST https://www.eaglixfreight.co.uk/api/v1/jobs/EF-7K2M9QX4LP/cancel \
-H "Authorization: Bearer ek_live_YOUR_KEY"{
"job": {
"id": "6f1c2a9e-4b7d-4c1e-9a53-2d8f0b7e1c44",
"tracking_code": "EF-7K2M9QX4LP",
"status": "cancelled",
"service": "Pallet",
"collection": {
"address": "Unit 4, Tyseley Industrial Estate, Birmingham",
"postcode": "B11 2AA"
},
"delivery": {
"address": "Goods In, 12 Trafford Park Road, Manchester",
"postcode": "M17 1HG"
},
"date": "2026-11-03",
"time": "09:00",
"vans": 1,
"size": null,
"access": null,
"goods": "2 pallets of boxed stock, 450 kg",
"notes": "Tail lift needed at delivery",
"reference": "PO-55821",
"contact": {
"name": "Sam Patel",
"phone": "07700 900123"
},
"price": 186.4,
"currency": "GBP",
"created_at": "2026-10-28T10:14:02.381+00:00",
"updated_at": "2026-10-28T10:14:02.381+00:00"
}
}{
"error": "A driver is already booked on this job. Call 07438 569191 to cancel."
}Job statuses
A job moves forward through these statuses. delivered and cancelled are final.
| status | What it means |
|---|---|
quote_requested | A quote was asked for by phone or on our website and Eaglix is pricing it. Jobs booked in the portal or through the API never have this status, though their timeline lists it at the moment of booking. |
quoted | Eaglix has sent a price and is waiting for you to accept it. Jobs booked in the portal or through the API never have this status, though their timeline lists it at the moment of booking. |
accepted | Booked and confirmed at the agreed price, and our drivers are being offered the job. Portal and API bookings start here. You can still cancel, as long as no van has a driver yet. |
assigned | A driver is booked for every van. From here, call us to cancel. |
en_route_collection | The driver has set off to the collection address. |
collected | The goods have been collected. |
en_route_delivery | The driver is on the way to the delivery address (or the disposal site for clearances). |
delivered | Delivered, with proof of delivery recorded. This is final. |
cancelled | The job was cancelled, by you or by Eaglix. This is final. |
On jobs with more than one van, each van in drivers has its own status; the job's status shows the overall progress. New statuses may be added in future, so handle values you don't recognise without failing.
Webhooks
Rather than asking us for updates, let us tell you. In the portal, on the API & webhooks page, add the HTTPS address on your server that should receive events. You get a signing secret (it starts whsec_): keep it on your server like an API key. The Send test button sends a ping so you can check your endpoint.
| Event | When it is sent |
|---|---|
job.created | A job is created for your company, however it was booked. data.status is the status it was created with: accepted for portal and API bookings, quote_requested for a quote asked for by phone or on our website. previous_status is null. |
job.status_changed | A job moves to a new status, including cancelled. A portal or API booking is already accepted when it is created, so its first one is usually assigned. |
ping | You pressed Send test in the portal. |
Each event is a POST with a JSON body and these headers:
Eaglix-Event: the event type, for examplejob.status_changed.Eaglix-Signature:t=1793265309,v1=5f2b..., wheretis the time it was signed (Unix seconds) andv1the signature.User-Agent:Eaglix-Webhooks/1.0.
{
"id": "evt_10432",
"type": "job.status_changed",
"created_at": "2026-11-03T09:05:19.220+00:00",
"data": {
"status": "en_route_delivery",
"previous_status": "collected",
"job": {
"id": "6f1c2a9e-4b7d-4c1e-9a53-2d8f0b7e1c44",
"tracking_code": "EF-7K2M9QX4LP",
"status": "en_route_delivery",
"service": "Pallet",
"collection": {
"address": "Unit 4, Tyseley Industrial Estate, Birmingham",
"postcode": "B11 2AA"
},
"delivery": {
"address": "Goods In, 12 Trafford Park Road, Manchester",
"postcode": "M17 1HG"
},
"date": "2026-11-03",
"time": "09:00",
"vans": 1,
"size": null,
"access": null,
"goods": "2 pallets of boxed stock, 450 kg",
"notes": "Tail lift needed at delivery",
"reference": "PO-55821",
"contact": {
"name": "Sam Patel",
"phone": "07700 900123"
},
"price": 186.4,
"currency": "GBP",
"created_at": "2026-10-28T10:14:02.381+00:00",
"updated_at": "2026-10-28T10:14:02.381+00:00"
}
}
}data.status is the change this event is about. data.job is the job as it is when the event is sent, so it may already be further on. Events can arrive more than once or out of order: use id to skip ones you have already handled, and created_at to put them in order.
Checking the signature
Always check the signature before trusting an event. Work out HMAC SHA256 of t, a full stop, then the raw request body ("t.rawBody"), using your signing secret as the key. Compare the hex result with v1. Use the body exactly as it arrived: parsing and re-encoding the JSON changes it and the check will fail.
import crypto from 'node:crypto';
// header is the Eaglix-Signature value, rawBody is the body exactly as received (a string)
export function verifyEaglixSignature(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map(p => p.trim().split('=')));
const t = Number(parts.t);
if (!t || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false; // too old: possible replay
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express: read the raw body, check it, reply fast, then do the work
app.post('/webhooks/eaglix', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
if (!verifyEaglixSignature(raw, req.get('Eaglix-Signature') ?? '', process.env.EAGLIX_WEBHOOK_SECRET)) {
return res.status(400).send('Bad signature');
}
res.sendStatus(200);
handleEaglixEvent(JSON.parse(raw)); // your code: look up the job by data.job.id and update it
});Replying and retries
- Reply with any 2xx status within 8 seconds. Do slow work after replying, or in a queue.
- We don't follow redirects: give us the final address.
- If you don't reply in time or reply with anything else, we try again after 1 minute, 5 minutes, 15 minutes, 1 hour, 4 hours and 12 hours: 7 tries over about 17 hours. After that the event is marked failed. Use
GET /api/v1/jobs?updated_since=...to catch up after an outage. - Events normally arrive within a minute or two of the change.
Rate expectations
We don't set hard limits yet, but please keep to these so the service stays quick for everyone:
- Up to about 60 requests a minute per company, in short bursts.
- Use webhooks for updates. If you must poll, ask for changed jobs with
updated_sinceno more than once a minute. - Retry errors with a growing wait (for example 2, 4, then 8 seconds), and always with the same Idempotency-Key.
We may slow down or pause a key that goes well beyond this, and we'll contact you first where we can. Planning large volumes? Talk to us.
Support
Questions, a test account, or help with an integration: call 07438 569191 or email info@eaglixfreight.co.uk. Include the job's tracking code and the time of the request if something went wrong. Never send us your API key or signing secret.

