# Error Handling Reference — Agentic Commerce Recovery

All API errors follow a consistent JSON structure designed for Agentic Commerce — agents should always check for the `error` key and use `suggestions` to auto-recover without human intervention.

---

## Error response format

```json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description of what went wrong",
    "details": {},
    "suggestions": [
      "Actionable suggestion 1",
      "Actionable suggestion 2"
    ],
    "requestId": "req_xyz789"
  }
}
```

| Field | Type | Always present | Description |
|-------|------|----------------|-------------|
| `code` | string | Yes | Machine-readable error code |
| `message` | string | Yes | Human-readable error description |
| `details` | object | No | Additional context (varies by error) |
| `suggestions` | string[] | No | Recovery actions for agents — **use these** |
| `requestId` | string | No | For support tickets |

---

## Error codes and recovery

### Product errors

| Code | HTTP | Cause | Agent recovery |
|------|------|-------|----------------|
| `PRODUCT_NOT_FOUND` | 404 | Invalid slug or ID | Use `suggestions` — often contains a corrected query or search URL |
| `PRODUCT_OUT_OF_STOCK` | 400 | Product or variant unavailable | Check `details.availableVariants` for alternatives; or search for similar products |
| `VARIANT_NOT_FOUND` | 400 | Invalid `variantId` | Re-fetch product with `GET /products/{slug}` and pick a valid variant |
| `INVALID_QUANTITY` | 400 | Quantity < 1 or exceeds stock | Reduce quantity; check `details.maxQuantity` |

**Example — out of stock with alternatives:**

```json
{
  "error": {
    "code": "PRODUCT_OUT_OF_STOCK",
    "message": "The selected variant is out of stock",
    "details": {
      "productId": "prod_black_hoodie_001",
      "variantId": "var_hoodie_xl_black",
      "availableVariants": ["var_hoodie_s_black", "var_hoodie_m_black", "var_hoodie_l_black"]
    },
    "suggestions": [
      "Try a different size",
      "Check /api/products/premium-black-hoodie for available variants"
    ]
  }
}
```

### Search errors

| Code | HTTP | Cause | Agent recovery |
|------|------|-------|----------------|
| `SEARCH_NO_RESULTS` | 200 | No products match query | Broaden the query; try `/categories` to explore; check `suggestions` for spelling corrections |
| `INVALID_CATEGORY` | 400 | Category slug doesn't exist | Call `GET /categories` and use a valid slug |

**Example — typo correction:**

```json
{
  "error": {
    "code": "SEARCH_NO_RESULTS",
    "message": "No products match 'mereno wool socks'",
    "suggestions": [
      "Did you mean 'merino wool socks'?",
      "Try: /api/products/search?q=merino+wool+socks"
    ]
  }
}
```

**Agent should:** Parse the suggested query from `suggestions` and retry the search automatically.

### Checkout / validation errors

| Code | HTTP | Cause | Agent recovery |
|------|------|-------|----------------|
| `INVALID_REQUEST` | 400 | Missing or malformed field | Check `details.field` for which field is wrong; fix and retry |
| `INVALID_EMAIL` | 400 | Bad email format | Ask user for a valid email |
| `INVALID_SHIPPING` | 400 | Shipping address issue | Check `details.field`; common issue: wrong `countryCode` format (must be 2-letter ISO) |
| `UNSUPPORTED_CHAIN` | 400 | Chain not supported | Call `GET /chains` and pick a valid chain |
| `UNSUPPORTED_TOKEN` | 400 | Token not available on chain | Call `GET /chains` and pick a valid token for the chosen chain |
| `UNSUPPORTED_COUNTRY` | 400 | Cannot ship to country | Check `details.supportedCountries`; ask user for an alternate address |

**Example — missing field:**

```json
{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "Missing required field 'email'",
    "details": { "field": "email", "required": true },
    "suggestions": [
      "Provide a valid email address",
      "Example: user@example.com"
    ]
  }
}
```

### Order errors

| Code | HTTP | Cause | Agent recovery |
|------|------|-------|----------------|
| `ORDER_NOT_FOUND` | 404 | Invalid order ID | Ask user to double-check their order ID |
| `ORDER_EXPIRED` | 400 | Payment window elapsed | Create a new order; old one cannot be revived |
| `ORDER_ALREADY_PAID` | 400 | Duplicate payment attempt | Inform user; check status with `GET /orders/{orderId}/status` |
| `ORDER_CANCELLED` | 400 | Order was cancelled | Create a new order if the user still wants the items |

### Rate limiting

| Code | HTTP | Cause | Agent recovery |
|------|------|-------|----------------|
| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests | Wait `retryAfter` seconds; use exponential backoff |

**Example:**

```json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Try again in 60 seconds.",
    "retryAfter": 60
  }
}
```

**Rate limits:**
- **100 requests/minute** per IP address
- **Burst:** Up to 20 requests/second
- On 429: wait `retryAfter` seconds, then retry with exponential backoff (2s, 4s, 8s, 16s...)

### Server errors

| Code | HTTP | Cause | Agent recovery |
|------|------|-------|----------------|
| `INTERNAL_ERROR` | 500 | Server-side failure | Retry after a few seconds; if persistent, contact support |
| `SERVICE_UNAVAILABLE` | 503 | API temporarily down | Check `GET /health`; retry with backoff |

---

## Auto-recovery pattern

Agents should implement this general pattern for all API calls:

1. **Make the request.**
2. **Check for `error` in the response.**
3. **If `error.suggestions` exists**, try the first suggestion automatically:
   - If it's a corrected search query, re-run the search.
   - If it points to another endpoint, call that endpoint.
   - If it suggests a field correction, fix the field and retry.
4. **If the error is a 429**, wait `retryAfter` seconds and retry.
5. **If the error is a 500/503**, retry up to 3 times with exponential backoff.
6. **If recovery fails**, relay `error.message` and `error.suggestions` to the user clearly.

---

## Common mistakes to avoid

| Mistake | Result | Fix |
|---------|--------|-----|
| Using example/placeholder product IDs | `PRODUCT_NOT_FOUND` | Always get IDs from search or product detail API |
| Using `line1` instead of `address1` | `INVALID_REQUEST` | Use exact field names: `address1`, `stateCode`, `zip`, `countryCode` |
| Using `state` instead of `stateCode` | `INVALID_REQUEST` | Use `stateCode` |
| Using `postalCode` instead of `zip` | `INVALID_REQUEST` | Use `zip` |
| Using `country` instead of `countryCode` | `INVALID_REQUEST` | Use `countryCode` (2-letter ISO) |
| Using 3-letter country code | `INVALID_SHIPPING` | Use 2-letter ISO 3166-1 alpha-2 |
| Putting `chain`/`token` at top level | `INVALID_REQUEST` | Nest inside `payment: { chain, token }` |
| Putting address inside `shippingAddress` | `INVALID_REQUEST` | Use `shipping` (not `shippingAddress`) |
| Sending payment after expiry | `ORDER_EXPIRED` | Create a new order |
