# API reference

[HTML](https://pdfcraft.ai/docs/api/overview/)
[Site index](https://pdfcraft.ai/llms.txt)

Request and response fields for the public Transform API.


To use PDFCraft from an AI agent, see [connect your agent with MCP](https://pdfcraft.ai/docs/getting-started/mcp/).

All paths below are relative to `https://api.pdfcraft.ai/v1/pcc-api`. Send `Authorization: Bearer YOUR_API_KEY` to every API request and `Content-Type: application/json` for JSON bodies. Responses use JSON and are not cacheable. [OpenAPI](https://pdfcraft.ai/openapi.json) provides a machine-readable specification.

For a working example, start with [your first conversion](https://pdfcraft.ai/docs/getting-started/overview/). Field combinations are explained under [conversion and translation](https://pdfcraft.ai/docs/api/conversion/).

## Create a task

`POST /transforms`

| JSON field | Required | Description |
| --- | --- | --- |
| `input_format` | Yes | `pdf` or `epub`; immutable |
| `input_filename` | No | Display name, 1–255 Unicode code points, not all whitespace; no control characters or invalid surrogate characters |

Unknown fields are rejected. `input_filename` cannot be null on creation. It does not set the download filename or the storage location.

```json
{
  "input_format": "pdf",
  "input_filename": "document.pdf"
}
```

Returns `201` and a task resource. Save `uuid` before uploading. Use the expiry time in the actual response.

```json
{
  "uuid": "8ae07e41-6e94-4144-9562-5af4116cba7e",
  "input_format": "pdf",
  "input_filename": "document.pdf",
  "status": "waiting_for_upload",
  "created_at": "2026-10-29T08:00:00.000Z",
  "updated_at": "2026-10-29T08:00:00.000Z",
  "expires_at": "2026-10-29T09:00:00.000Z"
}
```

## Get a task

`GET /transforms/{uuid}` returns `200` and the task resource.

| Response field | Availability | Description |
| --- | --- | --- |
| `uuid`, `input_format`, `status` | Always | Identity, source format and lifecycle state |
| `created_at`, `updated_at` | Always | ISO 8601 timestamps |
| `parameters` | After accepted launch | Normalized, immutable launch parameters |
| `finished_at`, `expires_at` | When available | Completion and service-owned expiry timestamps |
| `input_filename` | If supplied and not cleared | Original display filename |
| `title`, `authors`, `language` | When extracted and saved | Source-document metadata; authors is an array of names; language is not guaranteed to use API target-language codes |
| `statistics` | When available | `processed_pages` and/or `processed_words`; not billing amounts or live percentages |
| `error` | On a recorded error | Object containing `code` and `message` |

Optional fields may be absent. Read status from this endpoint; use observation for detailed live progress. Inaccessible, nonexistent and expired tasks return `404`.

## Rename the displayed source

`PATCH /transforms/{uuid}`

Accepts only `input_filename`, using the creation constraints. Set null to clear it, or omit it for no change. Valid in any accessible task state. Returns `200` with the updated resource.

```json
{
  "input_filename": "research.pdf"
}
```

Renaming does not replace the uploaded bytes, change conversion parameters or set the download filename.

## Get upload URLs

`GET /transforms/{uuid}/upload-urls`

Returns `200`. Before the source exists and before launch:

```json
{
  "put_url": "https://storage.example/source.pdf?signed",
  "headers": {
    "Content-Type": "application/pdf",
    "x-oss-forbid-overwrite": "true"
  }
}
```

PUT the raw file bytes using the returned headers, without API credentials. EPUB uses `application/epub+zip`. If the source already exists, the response instead contains:

```json
{
  "get_url": "https://storage.example/source.pdf?signed-get",
  "head_url": "https://storage.example/source.pdf?signed-head"
}
```

Uploaded source files cannot be overwritten. These URLs read the original file; use the download endpoint for conversion results. The example URLs on this page cannot be used for actual requests.

## Launch processing

`POST /transforms/{uuid}/launch`

| JSON field | Required | Values / default |
| --- | --- | --- |
| `output_format` | Yes | `markdown`, `epub`, `pdf` |
| `target_language` | For translation; mandatory for EPUB input or PDF output | One of the supported case-sensitive codes |
| `rendering_mode` | No | `source` without translation; `replace` by default with translation; `bilingual` or `bilingual-inline` where supported |
| `translation_prompt` | No | Nonblank string, only with translation |
| `refinement` | No; PDF only | `disabled` (default), `refinement_with_footnotes` |

See [valid combinations and language codes](https://pdfcraft.ai/docs/api/conversion/).

```json
{
  "output_format": "epub",
  "target_language": "en",
  "rendering_mode": "bilingual"
}
```

The service returns `202` when it first accepts the launch request. Repeating the same normalized parameters returns `200`. Both responses include the complete task.

If the source is missing, the concurrency limit is reached, or you try to change accepted parameters, the service returns `409`. Check `error.code` for the cause; see [errors](https://pdfcraft.ai/docs/api/error-handling/).

## Notification email

Optionally include `notification_email` and `notification_language` in the first launch request. The address must be a single plain email address (at most 254 characters after trimming); omission or null leaves notifications disabled. Empty or invalid addresses return `400 INVALID_REQUEST`. The account email is never used automatically, and the recipient is not verified. These fields are not conversion parameters; repeating launch does not change the saved settings.

`notification_language` defaults to `en`. Accepted codes are `en`, `de-DE`, `es-ES`, `fr-FR`, `it-IT`, `zh-CN`, `zh-Hant`, `ja-JP`, `ko-KR` and `ru-RU`, with exact casing. It controls the email and linked task-page language independently of `target_language`; null and aliases are rejected.

REST `GET /transforms/{uuid}` includes `notification_email` and `notification_language`, defaulting to null and `en` for historical tasks. Lists, observation and other resource responses omit them.

While a task is running or queued, update either or both fields:

```http
PATCH /transforms/{uuid}/notification
Content-Type: application/json
```

```json
{ "notification_email": "reader@example.com", "notification_language": "en" }
```

Omitted fields stay unchanged. Set the email to null to disable notifications; language cannot be null. Empty objects are invalid. Success returns `200` with both current fields. Unstarted or terminal tasks return `409 TRANSFORM_NOT_RUNNING`; unavailable or inaccessible tasks return `404`.

When the task ends, the service attempts one email describing completion or blocked access. Sending is best effort, without delivery status or automatic retries. The link opens the normal authenticated task page at `https://pdfcraft.ai/<notification_language>/task/<uuid>?utm_source=email&utm_medium=transactional&utm_campaign=conversion_result`; it does not grant access to the result. MCP does not support notification settings.

## Apply for a credit refund

`POST /transforms/{uuid}/refund`

Use the task's authoritative `refund_status` from its resource, finished list or observation. Terminal tasks expose `not_required`, `available` or `submitted`; active tasks omit it. Only `available` offers an application. Missing status on historical tasks does not establish eligibility.

Send no request body or `{}`. The endpoint rejects client amounts, user IDs, trace IDs and query parameters. HTTP `202` with `{"refund_status":"submitted"}` confirms acceptance for asynchronous processing, not a completed refund. Do not infer a refund amount or arrival time.

`409 REFUND_NOT_AVAILABLE` means eligibility must be refreshed; `503 REFUND_UNAVAILABLE` means submission could not be confirmed. After a timeout, 503 or ambiguous result, retry the same task UUID. Repeated accepted requests are idempotent. Unknown, expired, deleted or another user's tasks return `404`.

Only eligible technical failures after a charge attempt permit an application. Successful or partially successful tasks, insufficient-balance failures and restricted downloads do not. GET requests and background processing never apply automatically. See [retention and recovery](https://pdfcraft.ai/docs/api/lifecycle/).

## Download the result

`GET /transforms/{uuid}/download-urls`

When the task status is `succeeded`, the endpoint returns `200` and `{get_url, head_url}`. Other states return `404`.

Download with `get_url`. To inspect file information with a HEAD request, use `head_url`. The URLs cannot be interchanged, and neither request requires an API key.

The optional `filename` query sets the download name, including its extension. Use URL encoding exactly once:

```bash
curl --fail-with-body --silent --show-error --get \
  "$BASE/transforms/$TASK_UUID/download-urls" \
  -H "Authorization: Bearer $PDF_CRAFT_API_KEY" \
  --data-urlencode 'filename=research.markdown.zip'
```

The name must not be blank, must fit in 255 UTF-8 bytes, and must not contain control characters, invalid surrogates, `/` or `\`, or equal `.` or `..`. Invalid or repeated `filename` queries return `400`. The filename applies only to the generated links. It is not saved to the task and does not change the file contents. Include the extension yourself: `.zip` for Markdown results, `.epub` for EPUB, or `.pdf` for PDF. Use the returned URL as provided; its filename cannot be edited directly.

## List tasks

| Endpoint | Contents | Ordering |
| --- | --- | --- |
| `GET /transforms/waiting-for-upload/` | Unlaunched tasks, including uploaded sources | Creation time descending |
| `GET /transforms/running/` | Accepted work, including queued work | Creation time ascending |
| `GET /transforms/finished/` | Succeeded, failed, cancelled | Completion time descending |

Each accepts `limit` (1–100, default 50) and `cursor`. Ties are ordered by UUID. Treat cursors as opaque and reuse them only on the same queue.

```json
{
  "transforms": [
    {
      "uuid": "8ae07e41-6e94-4144-9562-5af4116cba7e",
      "input_format": "pdf",
      "input_filename": "document.pdf",
      "status": "waiting_for_upload",
      "created_at": "2026-10-29T08:00:00.000Z",
      "updated_at": "2026-10-29T08:00:00.000Z",
      "expires_at": "2026-10-29T09:00:00.000Z"
    }
  ],
  "next_cursor": "opaque-cursor"
}
```

The example shows a waiting-list item. The last page omits `next_cursor`. Lists return basic resources without progress or download signatures. Expired tasks are hidden; see [retention](https://pdfcraft.ai/docs/api/lifecycle/).

## Observe progress and preview

`GET /transforms/{uuid}/observation` accepts optional `cursor` and positive integer `preview_limit` (default 500, values above 500 are clamped). Returns `200` with `status`, `preview`, optional running `progress` and successful `download_urls`.

`GET /transforms/{uuid}/preview-assets/{stage}/{asset}` returns `{get_url, head_url}` for a preview asset. `stage` is `extraction` or `converting`; `asset` is a 64-character lowercase hexadecimal hash followed by `.png` or `.pdf`.

These endpoints do not provide a full-document archive. See [progress and preview](https://pdfcraft.ai/docs/api/preview/) for byte ranges, cursor advancement and content formats.
