> 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.

# Embed OCR in PDF

> Use parseRuns.createAndPoll and files.retrieve to add searchable, selectable OCR text to PDFs with advancedOptions.embedOcrInPdf, through the SDKs or Extend Studio.

Parse can embed recognized words as an invisible text layer in a PDF, making scanned pages searchable and allowing text selection and copying while preserving their appearance. Enable `advancedOptions.embedOcrInPdf` to use this feature; it defaults to `false`.

## Via the API

### Enable embedding and retrieve the PDF

Create a parse run with this configuration:

```json
{
  "file": { "id": "file_Xj8mK2pL9nR4vT7qY5wZ" },
  "config": {
    "advancedOptions": {
      "embedOcrInPdf": true
    }
  }
}
```

Wait for `status: "PROCESSED"`, then retrieve the file using the completed run's `file.id`. The file's `presignedUrl` downloads the processed PDF and expires after 15 minutes. Retrieve the file again to get a fresh URL.

> **Note**
>
> Set `EXTEND_API_KEY` before running these examples. Python, TypeScript, and Java send the `embedOcrInPdf` wire field through SDK support for additional properties. The Go example creates the run over HTTP, then uses the SDK to poll and retrieve the file, so it also works before the Go SDK exposes a typed field for this option.

#### Python

```python
from extend_ai import Extend, ParseConfig, ParseConfigAdvancedOptions

client = Extend()
advanced_options = ParseConfigAdvancedOptions(**{"embedOcrInPdf": True})
run = client.parse_runs.create_and_poll(
    file={"id": "file_Xj8mK2pL9nR4vT7qY5wZ"},
    config=ParseConfig(advanced_options=advanced_options),
)
if run.status != "PROCESSED":
    raise RuntimeError(f"Parse ended with status {run.status}")

processed_file = client.files.retrieve(run.file.id)
if not processed_file.presigned_url:
    raise RuntimeError("The file has no download URL")
print(processed_file.presigned_url)
```

#### TypeScript

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

const client = new ExtendClient();
const advancedOptions: Extend.ParseConfigAdvancedOptions & {
  embedOcrInPdf: boolean;
} = { embedOcrInPdf: true };

const run = await client.parseRuns.createAndPoll({
  file: { id: "file_Xj8mK2pL9nR4vT7qY5wZ" },
  config: { advancedOptions },
});
if (run.status !== "PROCESSED") {
  throw new Error(`Parse ended with status ${run.status}`);
}

const processedFile = await client.files.retrieve(run.file.id);
if (!processedFile.presignedUrl) throw new Error("The file has no download URL");
console.log(processedFile.presignedUrl);
```

#### Java

```java
import ai.extend.ExtendClient;
import ai.extend.resources.parseruns.requests.ParseRunsCreateRequest;
import ai.extend.resources.parseruns.types.ParseRunsCreateRequestFile;
import ai.extend.types.FileFromId;
import ai.extend.types.ParseConfig;
import ai.extend.types.ParseConfigAdvancedOptions;
import ai.extend.types.ParseRun;
import ai.extend.types.ParseRunStatusEnum;

ExtendClient client = ExtendClient.builder().build();
ParseConfigAdvancedOptions advancedOptions = ParseConfigAdvancedOptions.builder().build();
advancedOptions.getAdditionalProperties().put("embedOcrInPdf", true);

ParseRun run = client.parseRuns().createAndPoll(
    ParseRunsCreateRequest.builder()
        .file(ParseRunsCreateRequestFile.of(
            FileFromId.builder().id("file_Xj8mK2pL9nR4vT7qY5wZ").build()))
        .config(ParseConfig.builder().advancedOptions(advancedOptions).build())
        .build()
);
if (!run.getStatus().equals(ParseRunStatusEnum.PROCESSED)) {
    throw new IllegalStateException("Parse ended with status " + run.getStatus());
}

String downloadUrl = client.files().retrieve(run.getFile().getId())
    .getPresignedUrl().orElseThrow();
System.out.println(downloadUrl);
```

#### Go

```go
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
    "time"

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

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
    defer cancel()
    c := client.NewClient()

    request, err := http.NewRequestWithContext(ctx, http.MethodPost,
        "https://api.extend.ai/parse_runs", strings.NewReader(`{
            "file": {"id": "file_Xj8mK2pL9nR4vT7qY5wZ"},
            "config": {"advancedOptions": {"embedOcrInPdf": true}}
        }`))
    if err != nil { panic(err) }
    request.Header.Set("Authorization", "Bearer " + os.Getenv("EXTEND_API_KEY"))
    request.Header.Set("Content-Type", "application/json")
    request.Header.Set("x-extend-api-version", "2026-02-09")

    response, err := http.DefaultClient.Do(request)
    if err != nil { panic(err) }
    if response.StatusCode < 200 || response.StatusCode >= 300 {
        response.Body.Close()
        panic(fmt.Sprintf("Create parse run returned HTTP %d", response.StatusCode))
    }
    run := &extend.ParseRun{}
    err = json.NewDecoder(response.Body).Decode(run)
    response.Body.Close()
    if err != nil { panic(err) }

    for run.Status == extend.ParseRunStatusEnumPending ||
        run.Status == extend.ParseRunStatusEnumProcessing {
        select {
        case <-ctx.Done():
            panic(ctx.Err())
        case <-time.After(2 * time.Second):
        }
        run, err = c.ParseRuns.Retrieve(ctx, run.ID, &extend.ParseRunsRetrieveRequest{})
        if err != nil { panic(err) }
    }
    if run.Status != extend.ParseRunStatusEnumProcessed {
        panic(fmt.Sprintf("Parse ended with status %s", run.Status))
    }

    processedFile, err := c.Files.Retrieve(ctx, run.File.ID, &extend.FilesRetrieveRequest{})
    if err != nil { panic(err) }
    if processedFile.PresignedURL == nil { panic("The file has no download URL") }
    fmt.Println(*processedFile.PresignedURL)
}
```

> **Note**
>
> `outputUrl` is a download URL for the parsed **JSON**, not the PDF. The PDF download URL is `presignedUrl` on the [Get File](/api-reference/endpoints/file/get-file) response; it is not included in the parse run's file summary.

### What gets embedded

* **Pages without existing text:** recognized OCR words are added as an invisible text layer. Pages with no usable OCR words receive no text layer.
* **Pages with existing text:** the entire page is skipped, including pages that mix existing text with scanned regions. Existing text layers are preserved rather than replaced or corrected.
* **Selected pages:** if you set `advancedOptions.pageRanges`, only processed pages are candidates for embedding.
* **PDF conversion:** embedding applies to PDFs, including supported inputs converted to PDF before parsing with [`advancedOptions.alwaysConvertToPdf`](/parsing/configuration#advancedoptionsalwaysconverttopdf).

Embedding is independent of `advancedOptions.returnOcr.words` and `advancedOptions.returnOcr.lines`. Leave both disabled if you only need a searchable PDF; enable them separately if you also need OCR coordinates in the JSON response.

## Via Extend Studio

1. Open the parser in [Extend Studio](https://dashboard.extend.ai/studio) and select a document.
2. In the parser configuration, expand **Output & Structure** and enable **Embed OCR in PDF**.
3. Run the parser. The processed PDF supports search, text selection, and copying on pages where OCR text was embedded.

When OCR text was embedded, the document viewer displays a message explaining the change. **Download Original** retrieves the original document.

## Reference

* [Parsing configuration](/parsing/configuration#advancedoptionsembedocrinpdf)
* [Create Parse Run](/api-reference/endpoints/parse/create-parse-run)
* [Get Parse Run](/api-reference/endpoints/parse/get-parse-run)
* [Get File](/api-reference/endpoints/file/get-file)