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

# Error Handling

> Handle Parse API errors with detailed error codes and troubleshooting guidance.

When an error occurs, the Parse API returns a structured error with the following fields:

| Field       | Type    | Description                                                     |
| ----------- | ------- | --------------------------------------------------------------- |
| `code`      | string  | A specific error code that identifies the type of error         |
| `message`   | string  | A human-readable description of the error                       |
| `retryable` | boolean | Indicates whether retrying the request might succeed            |
| `requestId` | string  | A unique identifier for the request, useful for troubleshooting |

## How failures surface: sync vs. async

Where an error shows up depends on which endpoint you use.

* **Synchronous (`/parse`)** waits for processing to finish, so every failure — both request validation and document processing — comes back as an **HTTP error response** with the fields above. The [HTTP status codes](#http-status-codes) below apply to this endpoint.
* **Asynchronous (`/parse_runs`)** returns `200` with a parse run as soon as the job is accepted. Request-level problems (invalid request, auth) still return an HTTP error at creation time, but **document processing failures are not HTTP errors** — the run instead reaches `status: "FAILED"` with a `failureReason` and `failureMessage` (one of the [custom error codes](#custom-error-codes) below), which you read when you poll or receive the webhook.

Production integrations should use the asynchronous `/parse_runs` endpoint and check `status` on the returned run. See [Move to production with async processing](/parsing/best-practices#move-to-production-with-async-processing).

## Custom error codes

We provide custom error codes so your system can tell exactly what happened. On the sync endpoint they appear in the error body's `code` field; on the async endpoint they appear as the run's `failureReason`. Most are client errors related to the file provided for parsing and are not retryable.

| Error Code                         | Description                                                                                                                                                                        | Retryable |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| `INVALID_CONFIG_OPTIONS`           | Invalid combination of options in the incoming config.                                                                                                                             | ❌         |
| `UNABLE_TO_DOWNLOAD_FILE`          | The system could not download the file from the provided URL, likely means your presigned url is expired, or malformed somehow.                                                    | ❌         |
| `FILE_TYPE_NOT_SUPPORTED`          | The file type is not supported for parsing.                                                                                                                                        | ❌         |
| `FILE_SIZE_TOO_LARGE`              | The file exceeds the maximum allowed size.                                                                                                                                         | ❌         |
| `CORRUPT_FILE`                     | The file is corrupt and cannot be parsed.                                                                                                                                          | ❌         |
| `OCR_ERROR`                        | An error occurred in the OCR system. This is a rare error code and would indicate downtime, so requests can be retried. We'd suggest applying a retry with backoff for this error. | ✅         |
| `PASSWORD_PROTECTED_FILE`          | The file is password protected. Retry the request with the file's password using `file.settings.password`. See [Password-Protected Files](/password-protected-files).              | ❌         |
| `FAILED_TO_CONVERT_TO_PDF`         | The system could not convert the file to PDF format.                                                                                                                               | ❌         |
| `FAILED_TO_GENERATE_TARGET_FORMAT` | The system could not generate the requested target format.                                                                                                                         | ❌         |
| `INTERNAL_ERROR`                   | An unexpected internal error occurred. We'd suggest applying a retry with backoff for this error as it likely a result of some outage.                                             | ✅         |

## HTTP status codes

These HTTP status codes are returned by the synchronous `/parse` endpoint. On the asynchronous `/parse_runs` endpoint, only request-level errors (`400`, `401`, `403`) are returned at creation time — processing failures surface on the run as `status: "FAILED"`. We generally recommend relying on the custom error codes above for programmatic handling.

### 400 Bad Request

Returned when:

* Required fields are missing (e.g., `file`)
* `url` is not provided in the file object
* The provided `url` is invalid
* The `config` contains invalid values (e.g., unsupported target format or chunking strategy)
* The file type is not supported
* The file size is too large

### 401 Unauthorized

Returned when:

* The API token is missing
* The API token is invalid

### 403 Forbidden

Returned when:

* The authenticated workspace doesn't have permission to use the parse functionality
* The API token doesn't have sufficient permissions

### 422 Unprocessable Entity

Returned when:

* The file is corrupt and cannot be parsed
* The file is password protected
* The file could not be converted to PDF
* The system failed to generate the target format

### 500 Internal Server Error

Returned when:

* An OCR error occurs
* A chunking error occurs
* Any other unexpected error occurs during parsing

## Handling errors

Error handling differs between the two endpoints: with sync you catch a thrown HTTP error, while with async you also check the run's terminal `status`. Pick the tab that matches how you call Parse.

Catch the HTTP error and inspect its status code and body.

```python
from extend_ai import Extend
from extend_ai.core.api_error import ApiError

client = Extend(token="your_api_key")

try:
    response = client.parse(file={"url": "https://example.com/document.pdf"})
except ApiError as e:
    # e.body holds the structured error: { code, message, retryable, requestId }
    body = e.body or {}
    if body.get("retryable"):
        ...  # retry with exponential backoff
    print(f"parse failed ({e.status_code}): {body.get('code')} — {body.get('message')}")
    raise
```

```typescript
import { ExtendClient, ExtendError } from "extend-ai";

const client = new ExtendClient({ token: "your_api_key" });

try {
  await client.parse({ file: { url: "https://example.com/document.pdf" } });
} catch (error) {
  if (error instanceof ExtendError) {
    // error.body holds the structured error: { code, message, retryable, requestId }
    const body = error.body as { code: string; message: string; retryable: boolean; requestId: string };
    if (body.retryable) {
      // retry with exponential backoff
    }
    console.error(`parse failed (${error.statusCode}): ${body.code} — ${body.message}`);
  }
  throw error;
}
```

```java
import ai.extend.core.ExtendClientApiException;
import ai.extend.requests.ParseRequest;
import ai.extend.types.FileFromUrl;
import ai.extend.types.ParseRequestFile;

try {
    client.parse(ParseRequest.builder()
        .file(ParseRequestFile.of(FileFromUrl.builder()
            .url("https://example.com/document.pdf")
            .build()))
        .build());
} catch (ExtendClientApiException e) {
    // e.statusCode() is the HTTP status; e.body() holds { code, message, retryable, requestId }
    System.err.println("parse failed (" + e.statusCode() + "): " + e.body());
    throw e;
}
```

```go
import (
	"context"
	"errors"
	"log"

	extend "github.com/extend-hq/extend-go-sdk"
	"github.com/extend-hq/extend-go-sdk/core"
)

_, err := c.Parse(context.TODO(), &extend.ParseRequest{
	File: &extend.ParseRequestFile{
		FileFromURL: &extend.FileFromURL{URL: "https://example.com/document.pdf"},
	},
})
if err != nil {
	var apiErr *core.APIError
	if errors.As(err, &apiErr) {
		// apiErr.StatusCode is the HTTP status: 400, 401, 403, 422, 500, ...
		log.Printf("parse failed (status %d): %v", apiErr.StatusCode, apiErr)
	}
	return err
}
```

A request-level problem still throws at creation time. A document that fails to process does **not** throw — the run comes back with `status: "FAILED"`, so check the terminal status and read `failureReason` / `failureMessage`.

```python
from extend_ai import Extend
from extend_ai.core.api_error import ApiError

client = Extend(token="your_api_key")

try:
    run = client.parse_runs.create_and_poll(file={"url": "https://example.com/document.pdf"})
except ApiError as e:
    # Request-level errors (invalid request, auth) still raise at creation time
    print(f"could not start parse ({e.status_code}): {e.body}")
    raise

# Processing failures surface on the run, not as a raised error
if run.status == "FAILED":
    print(f"parse failed: {run.failure_reason} — {run.failure_message}")
```

```typescript
import { ExtendClient, ExtendError } from "extend-ai";

const client = new ExtendClient({ token: "your_api_key" });

let run;
try {
  run = await client.parseRuns.createAndPoll({ file: { url: "https://example.com/document.pdf" } });
} catch (error) {
  if (error instanceof ExtendError) {
    // Request-level errors (invalid request, auth) still throw at creation time
    console.error(`could not start parse (${error.statusCode}): ${JSON.stringify(error.body)}`);
  }
  throw error;
}

// Processing failures surface on the run, not as a thrown error
if (run.status === "FAILED") {
  console.error(`parse failed: ${run.failureReason} — ${run.failureMessage}`);
}
```

```java
import ai.extend.core.ExtendClientApiException;
import ai.extend.requests.ParseRunsCreateRequest;
import ai.extend.types.FileFromUrl;
import ai.extend.types.ParseRun;
import ai.extend.types.ParseRunStatusEnum;
import ai.extend.types.ParseRunsCreateRequestFile;

ParseRun run;
try {
    run = client.parseRuns().createAndPoll(ParseRunsCreateRequest.builder()
        .file(ParseRunsCreateRequestFile.of(FileFromUrl.builder()
            .url("https://example.com/document.pdf")
            .build()))
        .build());
} catch (ExtendClientApiException e) {
    // Request-level errors (invalid request, auth) still throw at creation time
    System.err.println("could not start parse (" + e.statusCode() + "): " + e.body());
    throw e;
}

// Processing failures surface on the run, not as a thrown error
if (run.getStatus() == ParseRunStatusEnum.FAILED) {
    System.err.println("parse failed: " + run.getFailureReason().orElse("")
        + " — " + run.getFailureMessage().orElse(""));
}
```

```go
import (
	"context"
	"errors"
	"log"
	"time"

	extend "github.com/extend-hq/extend-go-sdk"
	"github.com/extend-hq/extend-go-sdk/core"
)

run, err := c.ParseRuns.Create(context.TODO(), &extend.ParseRunsCreateRequest{
	File: &extend.ParseRunsCreateRequestFile{
		FileFromURL: &extend.FileFromURL{URL: "https://example.com/document.pdf"},
	},
})
if err != nil {
	var apiErr *core.APIError
	if errors.As(err, &apiErr) {
		// Request-level errors (4xx) still come back as HTTP errors
		log.Printf("could not start parse (status %d): %v", apiErr.StatusCode, apiErr)
	}
	return err
}

// Poll until the run reaches a terminal state
for run.Status == extend.ParseRunStatusEnumPending || run.Status == extend.ParseRunStatusEnumProcessing {
	time.Sleep(2 * time.Second)
	run, err = c.ParseRuns.Retrieve(context.TODO(), run.ID, &extend.ParseRunsRetrieveRequest{})
	if err != nil {
		return err
	}
}

// Processing failures surface on the run, not as an HTTP error
if run.Status == extend.ParseRunStatusEnumFailed {
	log.Printf("parse failed: %s — %s", *run.FailureReason, *run.FailureMessage)
}
```

For retryable errors (`OCR_ERROR`, `INTERNAL_ERROR`, or any `5xx`), retry with exponential backoff. Always log the `requestId` when contacting support.