Skip to content
Last updated

Error responses

Applies to both APIs.

The two return different error shapes, and an integration that spans both has to handle both.

A connector wraps all of this in its own message, so what you see on the Integration Orders page is a reason string, not an HTTP error. That page is the right one.


The two envelopes

Credentials, order states and webhooks are shared. Error shapes are not: the two APIs use completely different envelopes.

Last-mileFulfilment
Hostapi-ae.quiqup.complatform-api.quiqup.com
Top-level keyapi_errorstatus, code, message, details
Field errorsattribute_errors, an array of {detail}details.fields, an array of {field, message}
Names the bad fieldNoYes

If you call both, write two error handlers. Branch on the host, not on trying to parse one shape and falling back to the other.

Fulfilment errors

status and code carry the error code, message carries a sentence, and details.type says what kind of failure it was.

{
  "status": "invalid_argument",
  "code": "invalid_argument",
  "message": "Invalid Input",
  "details": {
    "type": "input",
    "fields": [
      { "field": "name", "message": "required" },
      { "field": "email", "message": "email" },
      { "field": "address.street", "message": "required" }
    ]
  }
}

details.fields names the field that failed, using dotted paths for nested objects. Read it and show it to the user. This is the part last-mile does not have.

Fulfilment error codes

HTTPCodedetails.type
400invalid_argumentinput
400failed_preconditioninternal
401unauthenticatedunauthorized
403permission_deniedforbidden
404not_foundnot_found
409already_existsinternal
429resource_exhaustedinternal
499canceledinternal
500internalinternal
500internalintegration
500data_lossinternal
500unknowninternal
503unavailableinternal
504deadline_exceededinternal

The list is not exhaustive and more codes are added over time. Branch on the HTTP status and treat an unrecognised code as its status class.

Last-mile errors

Which shape you get depends on the endpoint and on how early the request was rejected.

{ "api_error": {
    "id": 1002,
    "code": "Error Code",
    "message": "Error Message",
    "human": "Human readable error message",
    "description": "Detailed human readable error description",
    "http_status_code": 400,
    "attribute_errors": [ { "detail": "additional error code" } ]
} }
{ "error": "invalid_client" }
{ "error": "unauthorized" }
{ "result": 103, "level": "error", "message": "00001 is an invalid postcode" }

Which last-mile endpoint returns which

EndpointEnvelope
Every /orders operationThe api_error shape, sent alongside four more keys carrying the same two strings: error and errors[0].detail and error_details[0].detail repeat api_error.message, and error_detail repeats api_error.human. Read error for the machine-ish string and api_error.human for the sentence.
POST /oauth/token{ "error": "invalid_client", "error_description": "..." }
Any endpoint, missing or expired tokenHTTP 401 with a bare { "error": "unauthorized" }. The request is rejected before the payload is read, so there is no api_error.

The last shape above, { "result", "level", "message" }, does not come from any /orders endpoint. It exists elsewhere on the platform, so handle it defensively if you meet it.

Three things about the api_error shape will catch you out.

api_error.http_status_code does not always match the HTTP status. A create with an invalid items array comes back as HTTP 400 carrying http_status_code: 422. Trust the HTTP status.

api_error.message and error are sometimes empty strings. An invalid payment_mode, and an invalid kind, both return 422 with every message field blank. There is nothing in the response telling you which field was wrong.

attribute_errors comes back as an array of {detail}. Some older Quiqup material shows it as a bare object. The array is what the API sends.

The five fields are not independent. error, errors[0].detail, error_details[0].detail and api_error.message all carry the same string, and error_detail repeats api_error.human.

Validation errors on order creation

MessageFieldFix
'dropoff.address1' must not be blankdestination.address.address1Error messages say dropoff and pickup; the request fields are destination and origin.
Validation failed: Postcode can't be blanknoneThere is no postcode field in the contract. See Create an order before acting on this.
unknown servicekind: "..."kindUse a value from Service types.
[order][billing_identifier] invalidbilling_identifierHTTP 400. The identifier could not be resolved. Retry once, then ask support. An identifier that simply is not linked to your account does not produce this: it is accepted and ignored, see billing_identifier.
Same day service is not enabled for your accountkind422. Same Day is not enabled on the account. Ask your account manager.
[origin] is missing, [destination] is missing, [items] is missingthose three422. The three top-level objects the API actually requires.
[destination][address][address1] is missingdestination.address.address1422. Required on both stops.
[order][items] invaliditemsHTTP 400. An items entry with no name.
[payment_amount] is missingpayment_amount422 from PUT /orders/{order_id}. The two payment fields must be sent together.
(empty string)payment_mode or kind422 with no message at all. One of those two values is not in the allowed set.
CanSubmitJobPolicy failed policy checkanySee below.

CanSubmitJobPolicy failed policy check

One string covers several unrelated commercial restrictions. None of them are payload bugs, so debugging your request will not help.

RestrictionWho lifts itDoes retrying later work?
Customer segmentation ruleAccount managerNo
Tier closedAccount managerNo
Quiqdash closed for high demandOperationsYes
Hourly order limit reachedAccount managerYes, after the window resets
3PL (third-party logistics partner) not accepting ordersOperationsYes

Missing orders do not give you a 404

CaseWhat actually happens
GET /orders/{order_id} on an id that does not exist403, not 404. It reads like an authentication problem and is not.
GET /order_label/{order_id} on an id that does not exist200, Content-Type: application/pdf, and a zero-byte body. Nothing tells you the order is missing. Check the content length before writing the response to a file.
An update endpoint returns 404Wrong HTTP method for that path. The update operation is PUT /orders/{order_id}, not POST or PATCH.
A label request 404sCheck the path. The label endpoint is GET /order_label/{order_id}. A path built under the order resource does not exist.

5xx

Both APIs. Malformed payloads can surface as 500 rather than 422 on the last-mile POST /orders and PUT /orders/{order_id}/ready_for_collection. Validate your payload shape before assuming an outage.

If it persists, send the exact request, the exact response and the UTC timestamp to apisupport@quiqup.com.