← Haulbase

The Haulbase API

Read your bookings, equipment, customers and invoices from your own software. Read-only: a key can never change anything in your account.

Available on the Unlimited plan. Create keys in your dashboard under Integrations → API keys.

Authentication

Send your key as a bearer token on every request. Keys look like hb_ followed by 48 hexadecimal characters.

curl https://haulbase.ca/api/v1/bookings \
  -H "Authorization: Bearer hb_your_key_here"

A key reads your whole yard. Treat it like a password: keep it on a server, never in a browser, a phone app, or anything you publish. We store only a fingerprint of it, so we can never show it to you again - if you lose one, revoke it and create another.

Responses

Every endpoint returns the same shape: a data array and a paging object.

{
  "data": [ { "id": "…", "…": "…" } ],
  "paging": { "limit": 50, "offset": 0, "total": 128, "has_more": true }
}

Rows come back newest first. Page with limit and offset, and stop when has_more is false - not when a page comes back short, which can happen for other reasons.

Query parameters

ParameterDefaultWhat it does
limit50Rows per page, 1 to 200. Anything higher is capped at 200.
offset0Rows to skip.
fromEarliest date, YYYY-MM-DD. Any other format is ignored rather than guessed at.
toLatest date, YYYY-MM-DD.

Endpoints

BookingsGET /api/v1/bookings

Every rental: dates, totals, what was paid and how, deposits, delivery, and the platform fee. (from / to filter on start_date)

idunit_idcustomer_idcustomer_namecustomer_phonecustomer_emailstart_dateend_datetotalpaid_amountstatuspayment_statuspaid_methodpaid_attax_ratetax_labeldeposit_amountdeposit_statusdelivery_requesteddelivery_feedelivery_distance_kmaccessories_totaldiscount_amountpromo_labelcommission_amountproduct_typequantitymeter_outmeter_inoverage_unitsoverage_amountcreated_at

EquipmentGET /api/v1/units

Your fleet: rates, category, meter readings, service dates and asset details. (from / to filter on created_at)

idyard_idlabelnicknametypecategoryproduct_typestatusdaily_rateweekly_ratemonthly_ratedamage_deposit_amountdescriptionbilling_modeusage_meter_typeincluded_units_per_dayoverage_unit_ratecurrent_meter_readingunit_of_measuremin_orderorder_incrementserial_numberyearmakemodellicence_platelast_servicenext_serviceretired_atcreated_at

CustomersGET /api/v1/customers

Your customer list and how many times each has rented. (from / to filter on created_at)

idnamephoneemailrentalscreated_at

InvoicesGET /api/v1/invoices

Numbered invoices with status, due dates and what has been paid. (from / to filter on created_at)

idbooking_idinvoice_numbercustomer_namecustomer_phonecustomer_emailamounttaxabletax_ratetax_labelnotestatusdue_datesent_atpaid_atwritten_off_atpaid_methodcreated_at

Fields not listed above are deliberately not exposed - signed agreements, driving licence details and payment-processor identifiers stay inside Haulbase.

Rate limits

120 requests per 60 seconds per key. Going over returns 429 with a Retry-After header saying how many seconds to wait. Counting is per key, not per address, so one busy integration cannot throttle another.

Errors

StatusMeans
401Missing, malformed, revoked or unknown key.
402The account’s plan no longer includes API access.
429Rate limited. Wait for the number of seconds in Retry-After.
500Our fault. Retry with a backoff.

Errors are JSON: { "error": "…" }. If an account drops to a plan without API access, the key keeps working for a short grace period and the responses carry an X-Haulbase-Notice header saying when it stops - so an overnight sync warns you rather than simply failing one morning.

Paging through everything

let offset = 0;
const all = [];

while (true) {
  const res = await fetch(
    `https://haulbase.ca/api/v1/bookings?limit=200&offset=${offset}`,
    { headers: { Authorization: `Bearer ${process.env.HAULBASE_KEY}` } }
  );
  if (!res.ok) throw new Error(`Haulbase API: ${res.status}`);

  const { data, paging } = await res.json();
  all.push(...data);
  if (!paging.has_more) break;
  offset += paging.limit;
}

What the API does not do