# Errors & status codes

> How failures are reported, and a checklist for handling them.

Page: https://developers.zactonz.com/apis/errors/

The APIs report outcomes in the JSON body, not only in the HTTP status line. Most endpoints answer **HTTP 200 for every request** and put the real outcome in the `status` field. Do not treat an HTTP 200 as success on its own.

## The status field

`status` follows HTTP conventions: `200` means the operation succeeded, `4xx` means the request could not be processed as sent, `5xx` means an upstream or server problem. Two things to be aware of:

- **Type varies by endpoint.** Some endpoints return a number (`200`), some a string (`"200"`). Compare loosely (`== 200`) or cast first. Each reference page says which type its endpoint uses.
- **The message field varies too.** Depending on the endpoint the explanation is in `data` (as a string), in `message`, or in an `errors` array. The reference pages show the exact shape.

## Codes you will see

| status | Meaning | Typical cause |
|---|---|---|
| `200` | Success | The operation completed. Check for an empty result before using it. |
| `301` | Not recognised | YouTube Video Info: the URL did not contain a video id. |
| `400` | Bad request | A required parameter is missing or malformed. |
| `401` | Unauthorised | The `Authorization: Bearer` header is missing, or the key is invalid or expired. |
| `401` | Input unusable | QR Decoder: the image URL was invalid, private, too large, or not decodable base64. |
| `403` | Forbidden | The API key is not recognised, or (Drive) the file is not accessible. |
| `406` | Key expired | Mail Verifier: renew the key with support. |
| `413` | Too large | The upload, fetched page or image exceeds the plan's or endpoint's size cap. |
| `415` | Wrong type | The URL did not return a type the endpoint accepts, for example HTML where an image was expected. |
| `422` | Nothing found | QR Decoder: the image loaded but held no readable QR code. |
| `422` | Upstream failed | The target page, host or registry could not be fetched or resolved; `upstream_status` carries its HTTP code when there is one. |
| `429` | Throttled | A plan limit was hit; see [Rate limits](https://developers.zactonz.com/apis/rate-limits/) and honour `Retry-After`. |
| `500` | Server error | The operation failed unexpectedly. Safe to retry once. |
| `502` | Upstream error | A third-party service refused the request. Retry later. |
| `503` | Upstream unavailable | Video Downloader: the extraction route is blocked. Retry later. |
| `null` | Rejected | Video endpoints: a required parameter or a valid key was missing. |

## Handling errors in code

```php
<?php
$json   = file_get_contents('https://api.zactonz.com/qr/dec/?image=' . rawurlencode($imageUrl));
$result = json_decode($json, true);

if (!is_array($result) || (int) ($result['status'] ?? 0) !== 200) {
    $reason = $result['data'] ?? $result['message'] ?? implode(', ', $result['errors'] ?? ['unknown']);
    throw new RuntimeException('QR decode failed: ' . $reason);
}
$text = $result['data'];
```

A short checklist:

1. Parse the body as JSON. If parsing fails, the API is unreachable or returned an HTML error page; treat as a server error.
2. Cast `status` to an integer and compare with `200`.
3. On failure, read the explanation from `data`, `message` or `errors`, whichever the endpoint uses.
4. Retry only `5xx` outcomes, with a delay, and `429` after `Retry-After`. Other `4xx` outcomes will fail again unchanged.
