Vendor API keyServer to server, for a carrier's own systems. Send it as Authorization: Bearer or X-API-Key. Every request is scoped to the carrier that owns the key.
Device-bound JWTFor mobile apps and SPAs. Exchange credentials at /api/auth/token, then send the access token with X-Device-Id. Tokens are pinned to the device that requested them.
Keys are stored only as a SHA-256 hash — the secret is shown once, at issue or rotation, and cannot be recovered afterwards. Lost one? Rotate it. A key also stops working the moment its
carrier stops being active, so suspending a carrier closes every integration it holds without anyone hunting through this list.
API reference
11 endpoints, read from this deployment's own route table — so this list is what is actually running, not what someone remembered to write down.
/
Vendor API key
Server to server. One key belongs to one carrier and can never read another carrier's data.
GET/api/v1/pingConfirm a key works and see which carrier it is scoped to.
The cheapest way to verify a newly issued or rotated key before you deploy it.
PATCH/api/v1/parcels/{parcel}/statusMove a parcel to its next state.
Runs the parcel state machine, so an illegal transition is refused with 422 rather than silently written. The legal moves are the ones drawn in the lifecycle below.
POST/api/v1/telemetry/positionsPush real GPS/AVL positions. Batched, up to 1000 per call.
Rows are validated individually: a bad row comes back reported by index and the rest of the batch still lands, so one malformed vehicle never costs you the whole push.
{
"accepted": 998,
"rejected": 2,
"errors": [
{ "index": 41, "errors": ["reported_at is in the future — check the sender clock."] },
{ "index": 77, "errors": ["The lat field must be between -90 and 90."] }
]
}
Device-bound JWT
For mobile apps and SPAs. Access tokens are pinned to the device that asked for them.
GET/api/auth/meThe account behind the current access token.
Call this deployment with your own key and see the real response. Read-only endpoints only.
Response headers
The request goes straight from your browser to this deployment's API. Your key is never sent
anywhere else, is not stored, and is cleared when you leave the page — but it is still a live
credential, so use a staging key if you would rather not type a production one into a browser.
Press Ctrl
+ Enter to send.
Errors & rate limits
Failures come back as JSON with the reason in error or message.
401
Missing API key, or the key is invalid, revoked, expired — or its carrier is no longer active.
403
The key is valid but not for this carrier's data.
404
The record does not exist, or belongs to another carrier. Deliberately indistinguishable, so the API cannot be used to probe for other carriers' ids.
422
Validation failed, or a state machine refused the transition. The body names the offending fields.
429
Rate limit exceeded. Limits are per key, so one carrier can never starve another.
Rate limits are shown on each endpoint above where one applies. Telemetry is deliberately
generous — 600 / minute — because a whole fleet reports together.
Webhooks
Register an endpoint under Operator → Developer and subscribe to the events you want. Deliveries are signed.
parcel.status_changedParcel status changed
parcel.deliveredParcel delivered
booking.createdBooking created
disruption.reportedService disruption reported
pingTest ping
Delivery & retries
Attempts
5, then the delivery is parked for inspection.
Backoff
10 s, 1 min, 5 min, 15 min between attempts.
Timeout
5 seconds per attempt.
Success
Any 2xx. Anything else — including a timeout — counts as a failure and is retried.
X-Transia-Event
The event name, so you can route before you parse.
X-Transia-Signature
sha256=<hmac_sha256(raw_body, endpoint_secret)>
Retries mean the same event can arrive twice — a receiver that hangs up after processing will
still be retried. Treat id as an idempotency key and ignore one you have already seen.
HMAC the raw request body — not a re-encoded copy of the parsed JSON, whose key order and
spacing will not match — with your endpoint secret, and compare in constant time.
import crypto from "node:crypto";
// express.raw({ type: "application/json" }) — keep the bytes, not a parsed body.
const expected = "sha256=" + crypto.createHmac("sha256", endpointSecret)
.update(req.body).digest("hex");
const given = req.get("X-Transia-Signature") ?? "";
const ok = expected.length === given.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given));
if (!ok) return res.sendStatus(403);
const event = JSON.parse(req.body.toString());
import hmac, hashlib, json
raw = request.get_data() # bytes, before any parsing
expected = "sha256=" + hmac.new(
endpoint_secret.encode(), raw, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Transia-Signature", "")):
abort(403)
event = json.loads(raw)
Parcel lifecycle
The states PATCH /api/v1/parcels/{parcel}/status enforces, and the moves each one
allows. Anything not drawn here is refused with 422 — so this is the graph to code against, rather
than discovering it one rejected transition at a time in production.
Push real vehicle positions and the live map stops being a simulation. Batch up to 1000 rows a call;
each row is validated on its own, so one bad vehicle never costs you the batch.
Parcel status pushes accept 8 states —
the lifecycle above shows which
of them each state may move to. Illegal transitions are refused with 422 rather than written.