> This page is for version v2026-02-09 (default).
> For other versions, use one of these documentation indexes:
> - v2026-02-09 (default): https://docs.extend.ai/2026-02-09/llms.txt
> - v2025-04-21: https://docs.extend.ai/2025-04-21/llms.txt
> - v2024-12-23: https://docs.extend.ai/2024-12-23/llms.txt

> ## 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.
>
> ## API version
> The current API version is `2026-02-09`, served at the site root (no version prefix in URLs).
> If this page URL contains `/2025-04-21/` or `/2024-12-23/`, you are reading an older API version.
> Prefer the current docs at https://docs.extend.ai/llms.txt unless the user explicitly needs that older version.
> Do not treat older-version pages as the source of truth for new integrations.

# Verify Error Handling

> Handle Verify API errors: structured error fields, the failure reasons a verify run can end with (NO_APPLICABLE_SIGNALS, FILE_TYPE_NOT_SUPPORTED, and more), HTTP status codes, and sync vs async patterns.

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

| Field       | Type    | Description                                                                                                      |
| ----------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
| `code`      | string  | A specific error code for programmatic handling.                                                                 |
| `message`   | string  | A human-readable description of the error. Don't rely on its exact text — it may change.                         |
| `retryable` | boolean | Whether retrying the request might succeed. When `true`, retry with exponential backoff.                         |
| `requestId` | string  | A unique identifier for the request, useful when contacting support.                                             |
| `runId`     | string  | The ID of the verify run that is still processing. Returned only when a synchronous `/verify` request times out. |

## How failures surface: sync vs. async

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

* **Synchronous (`/verify`)** 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. The wait is capped at 5 minutes on both engines. If the run outlives it, the response is usually a `504 Gateway Timeout` from the load balancer, which has no Extend error body and no `runId`. Less often it is a `500 INTERNAL_ERROR` with `retryable: false` whose body carries `runId`. In both cases the run keeps processing, and resubmitting starts a second run. `verify_agentic` runs often exceed 5 minutes, so use the asynchronous endpoint for them.
* **Asynchronous (`/verify_runs`)** returns `200` with a verify run as soon as the job is accepted. Request-level problems (invalid request, auth, a file URL that cannot be downloaded) 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 [failure reasons](#failure-reasons) below), which you read when you poll.

> **Note**
>
> Production integrations should use the asynchronous `/verify_runs` endpoint and check `status` on the returned run. Webhook events for verify runs are not available yet, so poll `GET /verify_runs/{id}` until the status is `PROCESSED`, `FAILED`, or `CANCELLED`.

## Failure reasons

These codes describe why a verify run failed. On the sync endpoint they appear in the error body's `code` field; on the async endpoint they appear as the run's `failureReason`. The structured error's `retryable` flag is authoritative at runtime.

| Failure reason            | Description                                                                                                                                                                                                                                                                | Retryable               |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `FILE_TYPE_NOT_SUPPORTED` | The file is not a PDF, JPEG, PNG, or WebP. See [supported file types](/verify/configuration#supported-file-types).                                                                                                                                                         | ❌                       |
| `NO_APPLICABLE_SIGNALS`   | No verification check could run against this document, for example an image on which none of the configured detection services are available.                                                                                                                              | ❌                       |
| `PARSE_FAILED`            | The document could not be parsed, so the checks that read the parse could not run. Applies to `verify_agentic` and to `verify_light` with `advancedOptions.requireParsing`.                                                                                                | ✅                       |
| `REQUEST_ABORTED`         | The client disconnected before the synchronous request completed.                                                                                                                                                                                                          | ❌                       |
| `INTERNAL_ERROR`          | An internal error stopped the run, such as a detection model, content analysis, or the assessment being unavailable. Retry with exponential backoff. Also returned when the synchronous request times out; that case is not retryable because the run is still processing. | ✅ (❌ on a sync timeout) |

> **Note**
>
> Additional failure reasons may be added in the future. Handle unknown values gracefully.

Transient failures on the async endpoint are retried automatically before the run is marked `FAILED`, so a `FAILED` run with a retryable reason has already been attempted more than once. Retrying by creating a new run is still reasonable after a short delay.

## HTTP status codes

These HTTP status codes are returned by the synchronous `/verify` endpoint. On the asynchronous `/verify_runs` endpoint, only request-level errors are returned at creation time — processing failures surface on the run as `status: "FAILED"`.

### 400 Bad Request

Returned when required fields are missing (for example `file`), the file locator is invalid or points at a private network, the file at `url` could not be downloaded, `config.engineVersion` is unsupported, `config` contains a key that is not recognised (including the removed `signalAllowlist` and `policy`), `advancedOptions.requireParsing` is `false` on `verify_agentic`, `metadata` exceeds 10 KB, or a list filter or pagination token is invalid. The `code` is `INVALID_REQUEST` or the name of the failed validation.

### 401 Unauthorized

Returned when the API token is missing or invalid.

### 402 Payment Required

Returned when the organization is blocked from creating runs because of an overdue balance or exhausted credits (`USAGE_BLOCKED`).

### 403 Forbidden

Returned when the API key does not have permission to create or read document-processing runs.

### 404 Not Found

Returned when the run ID does not exist in this workspace or has been deleted, when a referenced file has been purged (`FILE_PURGED`), and when **Verify is not enabled for your organization**. During the beta, every verify endpoint returns `404 NOT_FOUND` for organizations that have not been granted access; contact [support@extend.ai](mailto:support@extend.ai) to request it.

### 422 Unprocessable Entity

Returned when the file cannot be verified: it is not a supported type (`FILE_TYPE_NOT_SUPPORTED`), or no check applies to it (`NO_APPLICABLE_SIGNALS`).

### 429 Too Many Requests

Returned when you exceed the rate limit. Retry with exponential backoff. See [Rate Limits](/general/rate-limits).

### 500 Internal Server Error

Returned as `INTERNAL_ERROR` when an internal error stops the run (for example, a detection model, content analysis, or the assessment is unavailable) or the synchronous request exceeds its 5-minute timeout, and as `PARSE_FAILED` when the document could not be parsed. Retry with exponential backoff when `retryable` is `true`. A timeout returns `retryable: false`: the run continues in the background and the error body includes its `runId`, so fetch that run instead of resubmitting the file.

### 504 Gateway Timeout

Returned by the load balancer when a synchronous `/verify` request runs past 5 minutes. The response has no Extend error body and no `runId`. The run continues in the background, so don't resubmit the file. Use the asynchronous `/verify_runs` endpoint for `verify_agentic` and for long documents.

## Handling errors

The async pattern below creates a run, polls it to a terminal state, and branches on the outcome.

#### Python

```python
import time

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

client = Extend()

try:
    run = client.verify_runs.create(file={"url": "https://example.com/statement.pdf"})
except ApiError as e:
    # Request-level failure: bad input, auth, rate limit, or Verify not enabled (404)
    print(f"Request failed ({e.status_code}): {e.body}")
    raise

while run.status == "PROCESSING":
    time.sleep(2)
    run = client.verify_runs.retrieve(run.id)

if run.status == "PROCESSED":
    print(run.output.risk_level, run.output.summary)
elif run.status == "FAILED":
    if run.failure_reason in ("PARSE_FAILED", "INTERNAL_ERROR"):
        print("Transient failure, retry later:", run.failure_message)
    else:
        print("Not retryable:", run.failure_reason, run.failure_message)
else:
    print("Run was cancelled")
```

#### TypeScript

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

const client = new ExtendClient();

let run;
try {
  run = await client.verifyRuns.create({ file: { url: "https://example.com/statement.pdf" } });
} catch (err) {
  if (err instanceof ExtendError) {
    // Request-level failure: bad input, auth, rate limit, or Verify not enabled (404)
    console.error(`Request failed (${err.statusCode}):`, err.body);
  }
  throw err;
}

while (run.status === "PROCESSING") {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  run = await client.verifyRuns.retrieve(run.id);
}

const retryable = new Set(["PARSE_FAILED", "INTERNAL_ERROR"]);

if (run.status === "PROCESSED") {
  console.log(run.output?.riskLevel, run.output?.summary);
} else if (run.status === "FAILED") {
  if (run.failureReason && retryable.has(run.failureReason)) {
    console.log("Transient failure, retry later:", run.failureMessage);
  } else {
    console.log("Not retryable:", run.failureReason, run.failureMessage);
  }
} else {
  console.log("Run was cancelled");
}
```

#### Java

```java
import ai.extend.ExtendClient;
import ai.extend.core.ExtendClientApiException;
import ai.extend.requests.VerifyRunsCreateRequest;
import ai.extend.types.FileFromUrl;
import ai.extend.types.VerifyRun;
import ai.extend.types.VerifyRunStatus;
import ai.extend.types.VerifyRunsCreateRequestFile;
import java.util.Set;

ExtendClient client = ExtendClient.builder().build();

VerifyRun run;
try {
    run = client.verifyRuns().create(VerifyRunsCreateRequest.builder()
        .file(VerifyRunsCreateRequestFile.of(FileFromUrl.builder()
            .url("https://example.com/statement.pdf")
            .build()))
        .build());
} catch (ExtendClientApiException e) {
    // Request-level failure: bad input, auth, rate limit, or Verify not enabled (404)
    System.err.println("Request failed (" + e.statusCode() + "): " + e.body());
    throw e;
}

while (run.getStatus() == VerifyRunStatus.PROCESSING) {
    Thread.sleep(2000);
    run = client.verifyRuns().retrieve(run.getId());
}

Set<String> retryable = Set.of("PARSE_FAILED", "INTERNAL_ERROR");

if (run.getStatus() == VerifyRunStatus.PROCESSED) {
    var output = run.getOutput().get();
    System.out.println(output.getRiskLevel() + " " + output.getSummary());
} else if (run.getStatus() == VerifyRunStatus.FAILED) {
    String reason = run.getFailureReason().orElse("");
    if (retryable.contains(reason)) {
        System.out.println("Transient failure, retry later: " + run.getFailureMessage().orElse(""));
    } else {
        System.out.println("Not retryable: " + reason + " " + run.getFailureMessage().orElse(""));
    }
} else {
    System.out.println("Run was cancelled");
}
```

#### Go

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

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

c := client.NewClient()
ctx := context.TODO()

run, err := c.VerifyRuns.Create(ctx, &extend.VerifyRunsCreateRequest{
	File: &extend.VerifyRunsCreateRequestFile{
		FileFromURL: &extend.FileFromURL{URL: "https://example.com/statement.pdf"},
	},
})
if err != nil {
	var apiErr *core.APIError
	if errors.As(err, &apiErr) {
		// Request-level failure: bad input, auth, rate limit, or Verify not enabled (404)
		fmt.Printf("Request failed (status %d): %s\n", apiErr.StatusCode, apiErr.Error())
	}
	log.Fatal(err)
}

for run.Status == extend.VerifyRunStatusProcessing {
	time.Sleep(2 * time.Second)
	if run, err = c.VerifyRuns.Retrieve(ctx, run.ID, &extend.VerifyRunsRetrieveRequest{}); err != nil {
		log.Fatal(err)
	}
}

retryable := map[string]bool{"PARSE_FAILED": true, "INTERNAL_ERROR": true}

switch run.Status {
case extend.VerifyRunStatusProcessed:
	fmt.Println(run.Output.RiskLevel, run.Output.Summary)
case extend.VerifyRunStatusFailed:
	reason, message := "", ""
	if run.FailureReason != nil {
		reason = *run.FailureReason
	}
	if run.FailureMessage != nil {
		message = *run.FailureMessage
	}
	if retryable[reason] {
		fmt.Println("Transient failure, retry later:", message)
	} else {
		fmt.Println("Not retryable:", reason, message)
	}
default:
	fmt.Println("Run was cancelled")
}
```

## Cancelling a run

`POST /verify_runs/{id}/cancel` moves a `PROCESSING` run to `CANCELLED` and stops in-flight work. Cancelling a run that has already finished, failed, or been cancelled is a no-op that returns the run unchanged, so it is safe to call from a timeout handler without checking status first. A cancelled run never has `output`.

## Deleting a run

`DELETE /verify_runs/{id}` removes a run and everything derived from its document: the result, the evidence artifacts, and the embedded parse. A run that is still processing is cancelled first. The input file is never deleted, even a zero-retention file another run is using. After deletion the run returns `404` from every read and no longer appears in `GET /verify_runs`. The response is `{ "id": "vr_..." }`.

For the general error format and codes shared by every endpoint, see [Error Handling](/api-reference/error-handling).