# Error Handling

## Unsupported file type (HTTP 400)

```json
{
  "success": false,
  "statusCode": 400,
  "path": "/api/master?module=pathology",
  "timestamp": "2026-09-07T04:11:45.928Z",
  "message": "Unsupported file type \"report.xyz\" for pathology module. Supported types: PDF, HL7, XML, JSON, CSV, Parquet, and Images (PNG, JPEG, HEIC, HEIF, AVIF, WEBP, BMP, TIFF).",
  "error": "UNSUPPORTED_FILE_TYPE",
  "code": "UNSUPPORTED_FILE_TYPE",
  "type": "validation"
}
```

When `success` is `false`, the `data` field is absent. Rejected uploads include a
machine-readable `code` and a `type` (`"validation"` for file-type errors,
`"billing"` for subscription/usage errors) so you can branch programmatically
without parsing the `message` string.

If you send multiple files in one request and any file is an unsupported type,
the entire request is rejected and no file is processed.

## Subscription and usage rejections (HTTP 402)

When the upload is rejected for a billing or usage reason, the response is
`HTTP 402 Payment Required` with `type: "billing"`. The `code` field identifies
the specific cause:

| `code` | Meaning |
|  --- | --- |
| `SUBSCRIPTION_EXPIRED` | Your subscription for this module has expired (or you have no active subscription). Renew to resume uploads. |
| `MODULE_SUBSCRIPTION_MISSING` | You have no subscription for the module you're uploading to. |
| `SUBSCRIPTION_INACTIVE` | A subscription exists for this module but is not currently active. |
| `PAGE_LIMIT_EXCEEDED` | You have used all pages in your plan and no overage is available. |
| `SPEND_CAP_REACHED` | Processing another page would exceed your configured spend cap. |
| `PILOT_QUOTA_EXCEEDED` | Your pilot transformation quota is exhausted. |


Example (`SUBSCRIPTION_EXPIRED`):

```json
{
  "success": false,
  "statusCode": 402,
  "path": "/api/master?module=pathology",
  "timestamp": "2026-09-07T04:11:45.928Z",
  "message": "Your pathology subscription has expired",
  "error": "SUBSCRIPTION_EXPIRED",
  "code": "SUBSCRIPTION_EXPIRED",
  "type": "billing"
}
```

Example (`SPEND_CAP_REACHED`):

```json
{
  "success": false,
  "statusCode": 402,
  "path": "/api/master?module=pathology",
  "timestamp": "2026-09-07T04:11:45.928Z",
  "message": "Spend cap reached. Cannot process more pages.",
  "error": "SPEND_CAP_REACHED",
  "code": "SPEND_CAP_REACHED",
  "type": "billing"
}
```

For every `402`, `code` and `message` always describe the same cause. Subscriptions
are per-module: an active subscription for another module does not permit uploads
to a module whose subscription has expired.

## Unauthorized (HTTP 401)

```json
{
  "success": false,
  "statusCode": 401,
  "path": "/api/master?module=pathology",
  "timestamp": "2026-04-02T08:26:13.716Z",
  "message": "Invalid API token format",
  "error": "Unauthorized"
}
```

Three distinct `message` values are possible:

| Message | Cause |
|  --- | --- |
| `"API token required"` | `Authorization` header is missing entirely |
| `"Invalid API token format"` | Header present but token doesn't match the expected format |
| `"Invalid API token"` | Token is correctly formatted but not found or account is inactive |


## Missing or invalid parameters (HTTP 400)

Returned when a required parameter is missing or invalid — for example, the
`module` query parameter is absent.

```json
{
  "success": false,
  "statusCode": 400,
  "path": "/api/master",
  "timestamp": "2026-09-07T04:11:45.928Z",
  "message": "Query parameter 'module' is required. Use: 'pathology', 'wearable', or 'medical'",
  "error": "Bad Request"
}
```

## Server error (HTTP 500)

Returned when an unexpected error occurs while processing the request. This
indicates a problem on our side, not with your request — retry after a short
delay, and if it persists, contact support with the `timestamp` and `path`.

```json
{
  "success": false,
  "statusCode": 500,
  "path": "/api/master?module=pathology",
  "timestamp": "2026-09-07T04:11:45.928Z",
  "message": "Internal server error",
  "error": "Internal Server Error"
}
```

## Processing error callbacks

When processing fails, JondaX POSTs an error payload to your callback endpoint.

### Error codes

| Code | Message |
|  --- | --- |
| `JX2001` | We couldn't read the text in the image you uploaded. |
| `JX2002` | The image format you uploaded isn't suitable for text recognition. |
| `JX2005` | The file isn't a blood test result. |
| `JX3002` | We do not support transforming data in the language you uploaded yet. |
| `JX3003` | We had trouble understanding the meaning of the original data. |


### Error callback fields

| Field | Type | Description |
|  --- | --- | --- |
| `customerId` | integer | Your account identifier. Always present. |
| `uploadId` | string | The upload ID of the failed file. Always present. |
| `fileName` | string | Original uploaded filename. Always present. |
| `errorCode` | string | Error code (see table above). |
| `errorMessage` | string | Human-readable error description. |
| `Results` | array | Always present but empty (`[]`) in error callbacks. |


### Example

```http
POST /api/results HTTP/1.1
Host: api.example.com
Content-Type: application/json
[api-header]: [api-key]

{
  "customerId": 16,
  "uploadId": "a24d4b93-5062-41fb-95b1-b85f5765ae91",
  "fileName": "4434.png",
  "errorCode": "JX2005",
  "errorMessage": "The file isn't a blood test result.",
  "Results": []
}
```

## Status polling errors

The status endpoint (`GET /api/v1/status/:uploadId`) can return the following errors:

### Not found (HTTP 404)

Returned when the `uploadId` does not exist or the upload has passed the 30-day retention window and been purged.

```json
{
  "message": "UploadId not found or expired",
  "error": "Not Found",
  "statusCode": 404
}
```

```json
{
  "message": "Upload has expired and data has been purged",
  "error": "Not Found",
  "statusCode": 404
}
```

### Forbidden (HTTP 403)

Returned when the `uploadId` exists but was created by a different customer's API key.

```json
{
  "message": "UploadId belongs to a different customer",
  "error": "Forbidden",
  "statusCode": 403
}
```

### Unauthorized (HTTP 401)

Returned when the API key is missing or invalid on the status endpoint.

```json
{
  "message": "Missing API key",
  "error": "Unauthorized",
  "statusCode": 401
}
```

```json
{
  "message": "Invalid API key",
  "error": "Unauthorized",
  "statusCode": 401
}
```

## Troubleshooting

| Symptom | Likely cause | Fix |
|  --- | --- | --- |
| `401` — `"API token required"` | `Authorization` header missing | Add `Authorization: Bearer <token>` to every request |
| `401` — `"Invalid API token format"` | Token format wrong | Check for extra spaces, quotes, or missing `Bearer` prefix |
| `401` — `"Invalid API token"` | Token not recognised or account inactive | Verify the token in the Integration page; regenerate if needed |
| `401` on status polling | API key missing or invalid | Use the same API key from the Integration page in `Authorization: Bearer <key>` |
| `403` on status polling | Wrong API key for this upload | Use the API key that was active when the file was uploaded |
| `404` on status polling | Upload ID doesn't exist or expired | Verify the `uploadId` value; check if 30-day retention has passed |
| `400` — `"Query parameter 'module' is required..."` | Missing/invalid `module` parameter | Add `?module=pathology` (or `wearable`/`medical`) to the request URL |
| `400` — `code: "UNSUPPORTED_FILE_TYPE"` | File format not accepted | Convert to a supported format and retry |
| `402` — `code: "SUBSCRIPTION_EXPIRED"` / `"MODULE_SUBSCRIPTION_MISSING"` / `"SUBSCRIPTION_INACTIVE"` | No active subscription for this module | Renew or activate your pathology subscription on the Integration page |
| `402` — `code: "PAGE_LIMIT_EXCEEDED"` / `"SPEND_CAP_REACHED"` / `"PILOT_QUOTA_EXCEEDED"` | Plan usage exhausted or spend cap reached | Upgrade your plan, raise your spend cap, or wait for the next billing period |
| `500` — `"Internal server error"` | Unexpected error on our side | Retry after a short delay; if it persists, contact support with the `timestamp` and `path` |
| Upload accepted but no callback received | Callback URL unreachable or misconfigured | Verify endpoint URL, availability, and auth header/key on the Integration page |
| Callback delivery `exhausted` on status | All 3 retry attempts failed | Check your endpoint's availability and response codes; contact support to retrieve the stored result |