> ## Documentation Index
> Fetch the complete documentation index at: https://docs.haulstow.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Common errors

> Understand an error response and fix the most common problems.

When a request fails, first read three values:

```json theme={null}
{
  "success": false,
  "statusCode": 422,
  "message": "pickup coordinates are outside Ghana",
  "error": {
    "code": "VALIDATION_ERROR"
  }
}
```

* `statusCode` is the HTTP status repeated in JSON.
* `message` explains the problem to a human.
* `error.code` is the stable value your program should check.

Also save the `X-Request-ID` response header. It lets HaulStow support find the exact request without needing your API key or customer data.

## Quick fixes

| Status and code                  | What it means                                                                           | What to do                                                                           |
| -------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `400 INVALID_REQUEST`            | The body is not valid JSON, has an unknown field, or has trailing content.              | Check commas, quotation marks, field spelling, and `Content-Type: application/json`. |
| `400 IDEMPOTENCY_KEY_REQUIRED`   | A create request has no idempotency key.                                                | Add a unique `Idempotency-Key` header.                                               |
| `401 INVALID_API_KEY`            | The bearer token is missing, incomplete, or unknown.                                    | Send `Authorization: Bearer <complete-key>` from your backend.                       |
| `401 ENVIRONMENT_MISMATCH`       | The key and base URL are for different environments.                                    | Use a test key with sandbox or a live key with live.                                 |
| `401 API_KEY_REVOKED`            | The key was permanently revoked.                                                        | Deploy a new key from the developer portal.                                          |
| `401 API_KEY_EXPIRED`            | The key passed its expiry time.                                                         | Create and deploy a replacement key.                                                 |
| `403 INSUFFICIENT_SCOPE`         | The key lacks the endpoint's permission.                                                | Create or rotate a key with the required scope.                                      |
| `404 NOT_FOUND`                  | The ID is invalid, does not exist, or belongs to another application/business.          | Copy the full typed ID and use the key that owns the resource.                       |
| `409 IDEMPOTENCY_CONFLICT`       | One idempotency key was used with two different requests.                               | Use the original body or use a new key for the changed action.                       |
| `409 IDEMPOTENCY_IN_PROGRESS`    | The first request with this key is still running.                                       | Wait for `Retry-After`, then resend the identical request.                           |
| `409 EXTERNAL_ID_CONFLICT`       | This application already used that `external_id`.                                       | Fetch the existing delivery or use a new external ID.                                |
| `409 DELIVERY_NOT_CANCELLABLE`   | Pickup already happened.                                                                | Do not retry cancellation; follow the delivery status instead.                       |
| `409 SANDBOX_TRANSITION_INVALID` | The requested test status skipped or reversed a step.                                   | Follow the allowed sandbox status order one step at a time.                          |
| `422 VALIDATION_ERROR`           | A supplied field has an invalid value.                                                  | Read `message`, then compare the body with the endpoint schema.                      |
| `422 RECIPIENT_SHAPE_CONFLICT`   | The delivery mixed inline recipient fields with recipient IDs, or supplied only one ID. | Send either an inline `recipient` or both `recipient_id` and `recipient_address_id`. |
| `422 COD_GOODS_VALUE_REQUIRED`   | A COD delivery has no positive goods value.                                             | Add a positive `payment.goods_value`; omit it for non-COD payments.                  |
| `429 RATE_LIMIT_EXCEEDED`        | Too many requests were sent in the current window.                                      | Stop for `Retry-After` seconds, then resume more slowly.                             |
| `503 QUOTE_UNAVAILABLE`          | Live pricing is temporarily unavailable.                                                | Retry with backoff. Do not invent a delivery fee.                                    |
| `500 INTERNAL_SERVER_ERROR`      | HaulStow hit an unexpected failure.                                                     | Retry safely and include `X-Request-ID` if contacting support.                       |

## “It works in curl but not in my browser”

That is expected. The public `/v1` API is server-to-server and intentionally does not enable CORS. Do not expose an API key to the browser to work around this.

Use this flow:

```text theme={null}
Browser -> Your backend endpoint -> HaulStow Developer API
```

The developer portal itself uses separate browser-facing JWT endpoints under `/api/v1/developer/*`.

## Safe retry rules

* Retry `429` after `Retry-After`.
* Retry temporary `5xx` failures with increasing delays.
* Retry a create request with the same body and same idempotency key.
* Do not repeatedly retry validation, scope, or environment errors; fix the request or credential first.
* Do not log the `Authorization` header, complete API key, webhook secret, or full request body.

## What to send support

Send:

1. `X-Request-ID`;
2. the UTC time of the request;
3. the HTTP method and path, such as `POST /v1/deliveries`;
4. the status and `error.code`;
5. whether it happened in sandbox or live.

Never send a complete API key or webhook signing secret.
