To use PDFCraft from an AI agent, see connect your agent with 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 provides a machine-readable specification.
For a working example, start with your first conversion. Field combinations are explained under conversion and translation.
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.
{ "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.
{ "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.
{ "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:
{ "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:
{ "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.
{ "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.
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:
PATCH /transforms/{uuid}/notificationContent-Type: application/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.
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:
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.
{ "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.
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 for byte ranges, cursor advancement and content formats.