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

# JATS XML and Accessible PDF

> Run the published JATS XML and Accessible PDF processors and review their artifacts and verification results.

> [!NOTE]
>
> This feature is currently in beta. The API may change.

Use the published `jats` and `wcag` processors through `POST /api/v1/agent`.
These are fixed document tasks; you do not need to design a processor.

**Before you begin**, make sure you have:

1. A [Datalab account](https://www.datalab.to/auth/sign_up) with an [API key](https://www.datalab.to/app/keys)
2. Python 3.11+ installed
3. The Datalab SDK: `pip install datalab-python-sdk`
4. Your `DATALAB_API_KEY` environment variable set

## Choose a Processor

| Processor | Task | Output |
| - | - | - |
| `jats` | Convert a scholarly article into JATS 1.2 Journal-Publishing XML | `jats_xml` contains the article XML; `images` contains its figure images when `return_images=true` |
| `wcag` | Produce a tagged PDF targeting PDF/UA-1 and WCAG 2.2 | `accessible_pdf` contains the PDF as base64 |

Use page-based documents such as PDFs and scans. Spreadsheets cannot run these
two processors. Both return a `verdict` and `verifier_metrics` in addition to any
available artifact.

## Run and Review

```python Python SDK theme={null}
from pathlib import Path
from datalab_sdk import AgentOptions, DatalabClient

output = Path("output")
output.mkdir(parents=True, exist_ok=True)
client = DatalabClient()

for processor, source in (("jats", "article.pdf"), ("wcag", "document.pdf")):
    result = client.run_agent(
        source,
        options=AgentOptions(
            processor=processor,
            verification_effort="balanced",
            return_images=(processor == "jats"),
        ),
        max_polls=1800,
        poll_interval=2,
    )
    print(processor, result.finished, result.verdict, result.verifier_metrics)
    artifact = result.jats_xml if processor == "jats" else result.accessible_pdf
    blocking_issues = [
        check for check in (result.verifier_metrics or [])
        if check.get("severity", "blocking") != "advisory"
        and (check.get("status") != "pass" or check.get("errored"))
    ]
    if (
        not result.success or result.finished is not True
        or result.verdict != "PASS" or blocking_issues or not artifact
    ):
        print("Needs review:", result.error or "Inspect the verification results")
        continue
    # Writes jats.xml and its figures, or wcag.pdf.
    result.save_output(output / processor)
```

```bash cURL theme={null}
# JATS XML, including figure images in the result.
curl --fail-with-body https://www.datalab.to/api/v1/agent \
  -H "X-API-Key: $DATALAB_API_KEY" \
  -F "file=@article.pdf" \
  -F "processor=jats" \
  -F "verification_effort=balanced" \
  -F "return_images=true"

# Accessible PDF.
curl --fail-with-body https://www.datalab.to/api/v1/agent \
  -H "X-API-Key: $DATALAB_API_KEY" \
  -F "file=@document.pdf" \
  -F "processor=wcag" \
  -F "verification_effort=balanced"

# Poll the request_check_url from each submission response.
curl --fail-with-body "$REQUEST_CHECK_URL" \
  -H "X-API-Key: $DATALAB_API_KEY"
```

```python Python (requests) theme={null}
import os
import time
import requests

headers = {"X-API-Key": os.environ["DATALAB_API_KEY"]}
with requests.Session() as session:
    with open("article.pdf", "rb") as file:
        response = session.post(
            "https://www.datalab.to/api/v1/agent",
            headers=headers,
            files={"file": ("article.pdf", file, "application/pdf")},
            data={
                "processor": "jats",
                "verification_effort": "balanced",
                "return_images": "true",
            },
            timeout=60,
        )
    response.raise_for_status()
    check_url = response.json()["request_check_url"]
    for _ in range(1800):
        response = session.get(check_url, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
        if result.get("status") == "complete":
            if result.get("result_url"):
                # The signed download does not need the API key header.
                download = session.get(result["result_url"], timeout=60)
                download.raise_for_status()
                result = {
                    **download.json(),
                    **{key: value for key, value in result.items() if value is not None},
                }
            print(result.get("finished"), result.get("verdict"))
            print(result.get("verifier_metrics"))
            break
        if result.get("success") is False or result.get("status") == "failed":
            raise RuntimeError(result.get("error") or "Agent run failed")
        time.sleep(2)
    else:
        raise TimeoutError("Run still processing; keep the request ID to poll later")
```

These examples allow a longer polling window for agent work. Choose
`max_polls` and `poll_interval` for your workflow; they limit client polling,
not the server's processing time. If polling times out, keep the request ID and
continue polling the existing request instead of submitting it again.

The SDK helpers require SDK 0.6.0 or later. `AsyncDatalabClient.run_agent()`
accepts the same options with `await`. For `wcag`, change `processor` to `wcag`
and read `accessible_pdf` instead of `jats_xml`.

> [!WARNING]
>
> An available artifact or `success=true` does not establish conformance.
> Inspect `finished`, `verdict` and the verification results before using the
> output. A missing verdict, failed or errored blocking check, or unfinished run
> needs review. A passing verdict reports the checks that ran; it does not replace
> your journal's submission review or an accessibility review of the resulting PDF.

## Versions and Run Options

Use `processor="jats@1.0"` or `processor="wcag@1.0"` to pin the seeded version.
Omitting the version selects the newest published version.

The processor chooses XML or PDF output. `mode="fast"` or `mode="balanced"`
controls the run budget; it is separate from `verification_effort`, which can
be `fast`, `balanced` or `accurate`. The default effort is `fast`, so set it
explicitly when comparing results.

For a regional request, use `file_url` or an uploaded file reference with
`processing_location`. Regional completion can return a signed `result_url`
for the result JSON; current SDKs download it automatically.

## Next Steps

> [card] [Published Processors](https://documentation.datalab.to/docs/recipes/pipelines/custom-processors.md)
>
> Learn the shared run options and version addressing.

> [card] [Document Conversion](https://documentation.datalab.to/docs/recipes/conversion/conversion-api-overview.md)
>
> Use Convert when you need Markdown, HTML, JSON or chunks.

> [card] [Redaction](https://documentation.datalab.to/docs/recipes/redaction/redaction-api-overview.md)
>
> Remove selected information and review the redaction verdict.

> [card] [Python SDK](https://documentation.datalab.to/docs/welcome/sdk.md)
>
> Configure polling, errors and result handling.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.