Developers

Eaglix API

Price, book and track Eaglix jobs from your own systems, and get signed webhooks when they change. Version 1.

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/json on 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 example 2026-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_KEY

A 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."
}
StatusMeaning
200, 201It worked. 201 means a new job was booked.
400Something in your request needs fixing. The message names the field.
401Missing, invalid or revoked API key.
403Your account or key is not allowed to do this right now.
404No such job for your company, or no such endpoint.
405Wrong method for this endpoint, for example GET on a cancel. The Allow header lists the right one.
409The 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, 503A problem on our side. Wait a little and retry with the same Idempotency-Key.

List services

GET/api/v1/servicesThe services you can book

Freight 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.

Request
curl https://www.eaglixfreight.co.uk/api/v1/services \
  -H "Authorization: Bearer ek_live_YOUR_KEY"
Response (shortened)
{
  "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

POST/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.

Request
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}'
Response
{
  "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

POST/api/v1/jobsBook at the instant price

The 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.

FieldRequiredWhat to send
serviceYesA service name from GET /api/v1/services.
collection_postcodeYesUK postcode, for example B11 2AA.
collection_addressNoStreet address, unit or building name. Up to 200 characters.
delivery_postcodeYes, except Waste Removal and ClearanceUK postcode.
delivery_addressNoUp to 200 characters.
dateYesCollection day as YYYY-MM-DD, today or later (UK time).
timeNoCollection time, for example 09:00 or "AM". Up to 20 characters.
vansNoNumber of vans, 1 to 10. Defaults to 1.
sizeFor moves and clearancesOne of the options listed for the service.
accessNoOne of: House or ground floor, Flat with a lift, Flat, stairs only, Not sure.
weightNoTotal weight in kilograms, as a number. For freight services.
goodsNoWhat we are moving, for example "2 pallets of boxed stock". Up to 300 characters.
notesNoAnything the driver should know: loading bay, tail lift, booking-in slot. Up to 1000 characters.
referenceNoYour order or PO number. Shown on the job and on invoices. Up to 60 characters.
contact_nameNoPerson on site. Defaults to the key owner.
contact_phoneNoTheir 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.

Request
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"
}'
Response: 201 Created
{
  "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 header Idempotent-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

GET/api/v1/jobsYour company's jobs, newest first

Includes every job booked for your company: in the portal, through the API, and by our team.

Query parameterWhat it does
statusOnly these statuses, comma separated, for example status=assigned,en_route_delivery.
updated_sinceOnly 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.
limitJobs per page, 1 to 200. Defaults to 50.
cursorThe next_cursor value from the previous page. Leave it out for the first page.
Request
curl "https://www.eaglixfreight.co.uk/api/v1/jobs?status=accepted,assigned&limit=2" \
  -H "Authorization: Bearer ek_live_YOUR_KEY"
Response
{
  "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

GET/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.

Request
curl https://www.eaglixfreight.co.uk/api/v1/jobs/EF-7K2M9QX4LP \
  -H "Authorization: Bearer ek_live_YOUR_KEY"
Response
{
  "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

POST/api/v1/jobs/{ref}/cancelCancel before a driver is booked

You 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.

Request
curl -X POST https://www.eaglixfreight.co.uk/api/v1/jobs/EF-7K2M9QX4LP/cancel \
  -H "Authorization: Bearer ek_live_YOUR_KEY"
Response
{
  "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"
  }
}
Response: 409 Conflict
{
  "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.

statusWhat it means
quote_requestedA 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.
quotedEaglix 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.
acceptedBooked 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.
assignedA driver is booked for every van. From here, call us to cancel.
en_route_collectionThe driver has set off to the collection address.
collectedThe goods have been collected.
en_route_deliveryThe driver is on the way to the delivery address (or the disposal site for clearances).
deliveredDelivered, with proof of delivery recorded. This is final.
cancelledThe 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.

EventWhen it is sent
job.createdA 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_changedA 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.
pingYou pressed Send test in the portal.

Each event is a POST with a JSON body and these headers:

  • Eaglix-Event: the event type, for example job.status_changed.
  • Eaglix-Signature: t=1793265309,v1=5f2b..., where t is the time it was signed (Unix seconds) and v1 the signature.
  • User-Agent: Eaglix-Webhooks/1.0.
Body
{
  "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.

Node.js
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_since no 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.