A shipping aggregator integrates dozens of carriers whose APIs report failures inconsistently - some with HTTP codes, some with 200 and an error body, some with a timeout that means success. How should errors be modelled?
Show the full answer Hide the answer
The taxonomy that must exist
The consuming system needs to know one thing above all: can this be retried, and is the outcome known? That produces four categories, and every carrier response must be mapped into one:
- Definitely failed, retryable — transient error, rate limit, temporary unavailability. Retry with backoff.
- Definitely failed, not retryable — invalid address, unsupported service, validation error. Never retry; surface to the user or the operations team.
- Definitely succeeded — booking confirmed with a reference.
- Unknown — timeout, connection reset, ambiguous response. This is the category most systems omit, and it is the one that causes duplicate bookings.
Why the fourth category is the whole problem
A timeout on a booking request does not mean the booking failed. It means you do not know. Retrying may create a second shipment; treating it as failed may strand a real one.
The resolution path must exist as architecture rather than as an error handler: a pending state, a status query against the carrier using your own reference, a resolution deadline, and escalation when it expires. Which requires that you sent your own idempotency reference in the original request — carriers that do not support one force reconciliation by other means, and that limitation belongs on the carrier's capability record.
The per-carrier mapping
The mapping from carrier response to category is carrier-specific code, and it is the real integration
work. A carrier returning HTTP 200 with {"status": "FAIL"} and another returning 500 for a validation error
both need explicit handling, and generic error handling gets both wrong.
That mapping must be tested against recorded real responses, because carrier documentation is frequently incomplete and occasionally wrong about its own error codes.
What the platform exposes upward
A normalised error taxonomy with the carrier's raw response attached. Merchants integrating with the aggregator need a stable contract; support engineers need the original. Discarding the raw response to keep the model clean is the decision that makes every future investigation harder.
The backstop
Daily reconciliation against each carrier's own record of bookings, which is the only mechanism that finds a shipment that succeeded at the carrier after your timeout and exists nowhere in your system.