Errors
What a failure looks like, and which calls are safe to retry.
Most failures return the same three-field JSON envelope.
{
"message": "Order not found",
"error": "Not Found",
"statusCode": 404
}| Field | Meaning |
|---|---|
message | What went wrong, written for a human. A string, or an array of strings when several fields failed validation at once. |
error | The name of the status code. |
statusCode | The HTTP status code, repeated in the response body. |
Branch on the status code, not the message
Message text is not part of the API contract and can change without a new version. Use
statusCode for programmatic error handling.
Rate-limit responses are the exception: they include statusCode and message but omit error.
Validation failures
When a request body or a query string does not validate, message is an array with one entry per
problem, naming the field path.
{
"message": [
"lineItems.0.quantity must not be less than 1",
"shippingAddress.lastName should not be empty",
"shippingAddress.address should not be empty"
],
"error": "Bad Request",
"statusCode": 400
}Query parameters are validated the same way:
{
"message": ["limit must not be greater than 100"],
"error": "Bad Request",
"statusCode": 400
}Unrecognized fields are ignored rather than rejected. The API does not store them.
Status codes
| Code | Means | What to do |
|---|---|---|
400 | The request is malformed, fails validation, or asks for something that cannot be ordered. | Fix the request. Retrying it unchanged will fail again. |
401 | The key is missing or wrong for this store, or the store is inactive. | Check the key and store ID. Do not retry automatically. |
404 | No such route, or no such resource in this store. | Check the path and resource ID. Resources from another store also return 404. |
409 | An order already exists with this externalOrderId. | Fetch the existing order instead of creating another. |
429 | You have gone past the rate limit. | Wait for the Retry-After header, then retry. See Rate limits. |
500 | Something broke on our side. | Retry with backoff. If it persists, contact support with the time and the path. |
The API does not return 403. Resources outside the authenticated store return 404.
Requests that never matched a route
A path that is not served, including a version that does not exist, is a plain 404:
{
"message": "Cannot GET /store-api/2025-01/stores/ovy3u54frpsl4sd963aja0h1/products",
"error": "Not Found",
"statusCode": 404
}If a whole integration starts returning this, check the version segment in your base URL first.
What is safe to retry
GET requests are safe to retry after a timeout, 429, or 500. Apply exponential backoff and honor
the Retry-After header for 429 responses.
An order request that times out may still have succeeded. Send externalOrderId with every order to
make retries safe:
- The retry succeeds with
201. The first attempt never landed. - The retry fails with
409. The first attempt did land, and the order exists.
After a 409, find the order you already have rather than creating another:
curl "https://api.pengine.io/store-api/2026-08/stores/$STORE_ID/orders?filter[externalOrderId]=web-10482" \
-H "X-API-Key: $PENGINE_API_KEY"Without an external order ID there is no protection
Without externalOrderId, each request creates a distinct order. A retry after a timeout can create
and bill the same order twice. Always use your own order number as the external ID.