API

39 endpoints, 61 operations. JSON in, JSON out. The reference data is open; anything about your containers needs a key.

This page is generated from the route registry and checked against the source in CI, so it cannot drift from what the server actually does.

Authentication

Send your key as Authorization: Bearer ds_live_…. Issue one under Developer in the app.

A key is shown once at creation and stored only as a hash. We cannot recover a lost key, only replace it.

Public
No credentials.
Signed in
Any key or session. Scoped to your organisation.
Staff
Platform operators only.
Root
Platform owner only.
Signature
Authenticated by request signature, not by a key.

Rate limits

Fixed window, keyed by your account when signed in and by IP otherwise. Every API response carries the remaining budget, not just the 429 — a client that can only discover the limit by hitting it has no way to back off politely.

BudgetPer minute
catalogue

Reference reads.

240
rates

Pricing, which is the expensive path.

60
write

Anything that changes state.

20
auth

Deliberately tight: this is the surface a credential-stuffer hits.

10

Limits are counted per server instance. Behind more than one instance the effective ceiling is higher — the response says scope: per-instance so you are not guessing.

Read the source field

Chassis, appointment and empty-return responses say whether the figures came from a terminal feed or from fixtures. The fixtures are deterministic and realistic, which is exactly why they must not be dispatched against. Empty-return acceptance is fixtures everywhere today: no provider publishes it through an API — it lives on per-terminal notice boards.

Reference

/api/ports

GET Public Every port, with free days and the representative per-diem day rate.

Rate budget catalogue — 240/min.

/api/ports/{code}

GET Public One port with its terminals, lanes, carriers and today’s appointment and empty-return summaries.
code (path, required)
UN/LOCODE, e.g. USLAX.

Rate budget catalogue — 240/min.

/api/carriers

GET Public Drayage carriers, their fleets and the ports they serve.

Rate budget catalogue — 240/min.

/api/carriers/{scac}

GET Public One carrier in full.
scac (path, required)
Standard Carrier Alpha Code.

Rate budget catalogue — 240/min.

/api/lanes

GET Public Quotable port-to-destination lanes.

Rate budget catalogue — 240/min.

/api/chassis

GET Public Chassis availability at a port, separating what is ready now from what needs a split.
port (query, required)
UN/LOCODE.
equipment (query)
20ft, 40ft, 40ft tri-axle, Genset or 45ft.

Rate budget catalogue — 240/min.

Read `source`: `fixtures` means no provider is connected and the numbers are generated. Do not dispatch against them.

/api/chassis/search

GET Public Chassis ranked across ports: on-dock first, then no split required, then first-day cost.
equipment (query)
Narrow to one equipment type.
port (query)
Comma-separated UN/LOCODEs. Omit to search every port.
limit (query)
Page size, 1–100. Defaults to 20.

Rate budget catalogue — 240/min.

`count` is the total match; `results` is the page. An unknown port code is a 400, never a silent omission.

/api/empty-returns

GET Public Which terminal is accepting which ocean line’s empties, with an alternative when one is shut.
port (query, required)
UN/LOCODE.
line (query)
Ocean line SCAC.
date (query)
YYYY-MM-DD. Defaults to today.

Rate budget catalogue — 240/min.

Always fixtures today: no surveyed provider publishes acceptance through an API. Acceptance also changes same-day — confirm before dispatch.

/api/rates

POST Public Price a lane. Returns the customer price only — never the buy rate or the margin.

Rate budget rates — 60/min.

Per diem

/api/per-diem/{container}

GET Signed in The day-by-day demurrage and detention ledger for one container.
container (path, required)
ISO 6346 container number.
horizonDays (query)
How far to project, 0–90. Defaults to 14.

Rate budget catalogue — 240/min.

Each day carries `billed`. Days after `asOf` are a projection, not an invoice line. `billedBy.attributed` is false for leased or unrecognised boxes — do not bill off those figures.

/api/per-diem/calendar

GET Public The terminal closure calendar a charge was priced against.
year (query)
2000–2100. Defaults to the current year.
coast (query)
west, east, gulf or inland. West adds the ILWU contract days.

Rate budget catalogue — 240/min.

Published so a dispute can rest on something checkable. Holidays are computed from the rules that define them, not from a table that goes stale.

Operations

/api/moves

GET Signed in Your organisation’s moves, with per-diem exposure on each.
PATCH Staff Advance a move’s status through the transition table.

Rate budget catalogue — 240/min.

An illegal status jump is refused rather than corrupting the per-diem clocks downstream.

/api/tracking

GET Signed in One container: status, events, last free day and per-diem exposure.
POST Signed in Start tracking a container.

Rate budget catalogue — 240/min.

A container belonging to another organisation answers 404, byte-identical to one that does not exist.

/api/bookings

GET Signed in Your bookings.
POST Signed in Book a move against a quoted lane.
PATCH Staff Dispatch-side changes.

Rate budget write — 20/min.

/api/quotes

GET Signed in Your quotes.
POST Signed in Quote a lane and keep it.

Rate budget write — 20/min.

/api/exceptions

GET Signed in Open exceptions on your containers, with liability and disputable amounts.
POST Staff Resolve one.

/api/alerts

GET Signed in Per-diem and exception alerts raised for you.
POST Signed in Acknowledge an alert.

Account

/api/account

GET Signed in Your profile and organisation.
PATCH Signed in Update them.

/api/preferences

GET Signed in Notification and display preferences.
PATCH Signed in Update them.

/api/team

GET Signed in Teammates, seats and pending invites on your workspace.
POST Signed in Invite one, with a role no higher than your own (owner or staff only).
DELETE Signed in Remove a teammate, or revoke a pending invite (kind: "invite").

An invite link works once and expires after an hour. It is emailed to the invitee; only when no email could be sent does the POST response carry it, for the inviter to pass on. It is never stored — only its hash.

/api/developer

GET Signed in Your API keys and webhook endpoints.
POST Signed in Issue a key, or register a webhook endpoint.
DELETE Signed in Revoke either.

A key is shown once at creation and stored only as a hash. It cannot be recovered, only replaced.

/api/feedback

GET Signed in Your support threads.
POST Signed in Open a new support thread.

/api/feedback/{id}

GET Signed in One thread.
POST Signed in Reply to it.
id (path, required)
Thread id.

Billing

/api/billing

GET Signed in Your plan, subscription and what the gateway can currently do.
POST Signed in Start a checkout.
DELETE Signed in Cancel the subscription at the end of the period.

A checkout can only become `active` on a gateway confirmation. With no gateway configured it stays `pending` and never counts towards MRR.

/api/billing/webhook

POST Signature The payment gateway’s callback. The only path by which a subscription becomes active.
GET Public Answers 405. It exists so a human checking the URL gets an explanation.

Rate budget write — 20/min.

HMAC-SHA256 over `<timestamp>.<raw body>`, five-minute tolerance, idempotent by event id. With no secret configured it answers 503 and refuses everything rather than trusting it.

Authentication

/api/auth/signup

POST Public Create a Basefyio identity and this app’s account profile.

Rate budget auth — 10/min.

Answers 503 when Basefyio auth is not configured. Without tokens from Basefyio (email verification pending) it points to /verify instead of signing in.

/api/auth/invite

POST Public Accept a team invite: sign in (or create a sign-in) through Basefyio with the invited address.

Rate budget auth — 10/min.

The verified token’s email must match the invite. An address that already has an account in any organisation is refused; accounts are never moved between organisations.

/api/auth/login

POST Public Sign in through Basefyio. Sets HttpOnly access and refresh token cookies.

Rate budget auth — 10/min.

Every access token is verified (RS256, pinned realm issuer, JWKS) on every request. Answers 503, never "wrong password", when auth is not configured.

/api/auth/logout

POST Public Sign out and clear the session cookies.
GET Public Sign out via a plain link.

/api/auth/forgot-password

POST Public Ask Basefyio to email a reset code.

Rate budget auth — 10/min.

Answers identically whether or not the account exists, so it cannot be used to discover which addresses are registered.

/api/auth/reset-password

POST Public Set a new password with the emailed code, then sign in.

Rate budget auth — 10/min.

Basefyio checks the code; it is single-use and expires.

/api/auth/verify

POST Public Confirm an email address with the code Basefyio sent.

Rate budget auth — 10/min.

/api/auth/resend-verification

POST Public Not available: answers 501 until a Basefyio resend endpoint is confirmed.

Administration

/api/admin/audit

GET Staff The audit trail.

/api/admin/users

GET Staff All accounts.
PATCH Staff Change an account. Role changes require root.

/api/admin/preflight

GET Staff Whether this deployment is fit to take real customers, and what is missing.

Rate budget catalogue — 240/min.

Answers 200 whether or not it is ready — a report, not a probe. Staff-only because the checks name which secrets are unset.

/api/admin/pricing

GET Staff Margin rules.
PATCH Staff Change one.
DELETE Root Remove one.

System

/api/health

GET Public Subsystem health, provider routing and cache statistics.

/api/cron/per-diem-scan

GET Staff Scan every open move and raise per-diem alerts.

Safe to run hourly: the alert ledger de-duplicates per container, per severity, per day. Also accepts a CRON_SECRET bearer token instead of a staff session.

Webhooks

Register an endpoint under Developer to receive move events. Deliveries are signed so you can tell ours from anyone else's POST:

X-DrayageScanner-Event: move.gated_out
X-DrayageScanner-Timestamp: 1789000000000
X-DrayageScanner-Signature: sha256=<hmac>

The HMAC is SHA-256 over <timestamp>.<raw body> with your endpoint's secret. Verify it over the bytes you received — parsing the JSON and re-serialising it changes them and the signature will not match.

Endpoints must be https. One that fails five times in a row is disabled automatically; a single success clears the streak.

Errors

Errors are JSON with an error string.

400 The request is wrong in a way retrying will not fix.
401 No credentials, or they did not verify.
403 Signed in, but not allowed.
404 No such record — including one that belongs to another organisation. We do not distinguish the two, because doing so would confirm the id exists.
429 Rate limited. Honour Retry-After.
503 A dependency we need is not configured or not answering.