API reference
Integrate image-to-Word, image-to-Excel, handwriting removal and document scanning into your software. Create tasks, poll status and download results with an API key and credits.
Submit images from any public storage provider, including R2, S3 and OSS. The API imports your images, processes them asynchronously and returns downloadable files. Your original objects remain in your storage.
One active task per account. All API keys on the same account share the concurrency limit. Polling does not consume credits.
Authentication
Scripts authenticate with a user API key. An invalid key returns HTTP 401. The website sends the same JSON with its session cookie.
Create a key in API Keys. Use it from your server or local scripts. Keep it out of frontend bundles, URLs and public repositories.
| Header | Value |
|---|---|
| Authorization | Bearer YOUR_API_KEY |
| Content-Type | application/json (POST requests only) |
Scripts use a user API key. An invalid key returns 401. Admin Agent keys are not accepted. The website calls this same endpoint with its session cookie and the same JSON. A paid subscription is not required if the account has enough credits.
curl 'https://doccrisp.com/api/v1/task/b758a315-8fa1-4c6a-9d1b-4d8ece769152' \
-H 'Authorization: Bearer YOUR_API_KEY'Base URL
Quickstart & setup
Each task below includes a complete Node.js and Python script. Select a language, copy or download the file, then run it with these environment variables.
| Variable | Required | Description |
|---|---|---|
| API_BASE_URL | Yes | https://doccrisp.com. |
| API_KEY | Yes | An active user API key. |
| IMAGE_URL | Yes | A public URL of your JPEG, PNG or WebP image. |
# macOS / Linux / Git Bash
export API_BASE_URL='https://doccrisp.com'
export API_KEY='YOUR_API_KEY'
export IMAGE_URL='https://your-storage.example.com/page.png'
node word.mjs
# Other tasks: excel.mjs, handwriting.mjs, scan.mjs# macOS / Linux / Git Bash
export API_BASE_URL='https://doccrisp.com'
export API_KEY='YOUR_API_KEY'
export IMAGE_URL='https://your-storage.example.com/page.png'
python word.py
# Other tasks: excel.py, handwriting.py, scan.pyWindows PowerShell setup
$env:API_BASE_URL = 'https://doccrisp.com'
$env:API_KEY = 'YOUR_API_KEY'
$env:IMAGE_URL = 'https://your-storage.example.com/page.png'
node word.mjs
# Or: python word.pyThe scripts submit once, poll every 3 seconds, handle polling rate limits, stop on failure and save downloads in outputs/. They time out after 30 minutes without resubmitting. For Word/Excel, add URLs to the input list to submit multiple pages. Handwriting removal and scan accept exactly one URL.
For local testing, set API_BASE_URL=http://localhost:3000. Production requests use https://doccrisp.com.
Image to Word
Convert one image or an ordered set of pages into a single editable Word document. Use service: "image-to-word".
/api/v1/taskRequest body
{
"input": [
"https://your-storage.example.com/page.png"
],
"service": "image-to-word"
}Returns: HTTP 202 with a queued task. This does not mean the conversion has finished.
Successful output: One .docx document in data.result, with a signed download URL valid for 30 minutes after completion.
Successful response · HTTP 200
{
"code": 0,
"message": "ok",
"data": {
"id": "b758a315-8fa1-4c6a-9d1b-4d8ece769152",
"service": "image-to-word",
"status": "succeeded",
"lane": "paid",
"input": [
"https://your-storage.example.com/page.png"
],
"result": {
"url": "https://private-results.oss-cn-hangzhou.aliyuncs.com/doc-api-results/20261106/b758a315-8fa1-4c6a-9d1b-4d8ece769152.docx?OSSAccessKeyId=EXAMPLE&Expires=1793939400&Signature=EXAMPLE_SIGNATURE",
"filename": "document.docx",
"size": 24576,
"contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
},
"error": null,
"createdAt": "2026-11-06T04:00:00.000Z",
"expiresAt": "2026-11-06T04:30:00.000Z"
}
}Complete example · submit, poll & download
// Node.js 20+ · No dependencies · Save as word.mjs
import { mkdir, writeFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises';
const baseUrl = process.env.API_BASE_URL;
const apiKey = process.env.API_KEY;
const imageUrl = process.env.IMAGE_URL;
if (!baseUrl || !apiKey || !imageUrl) {
throw new Error('Set API_BASE_URL, API_KEY and IMAGE_URL.');
}
const origin = new URL(baseUrl).origin;
const deadline = Date.now() + 30 * 60_000;
async function api(path, body) {
const url = new URL(path, origin);
if (url.origin !== origin) throw new Error('Unexpected API origin.');
const response = await fetch(url, {
method: body ? 'POST' : 'GET',
headers: {
Authorization: 'Bearer ' + apiKey,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
...(body ? { body: JSON.stringify(body) } : {}),
signal: AbortSignal.timeout(60_000),
redirect: 'error',
});
// Only retry reads. A repeated POST could create a second billable task.
if (!body && response.status === 429 && Date.now() < deadline) {
const delay = Math.min(60, Math.max(3,
Number(response.headers.get('Retry-After')) || 3));
await response.body?.cancel();
await sleep(delay * 1000);
return api(path);
}
const result = await response.json();
if (!response.ok || result.code !== 0) {
throw new Error('HTTP ' + response.status + ': ' + (result.message ?? 'Request failed'));
}
return result.data;
}
let task = await api('/api/v1/task', {
service: 'image-to-word',
input: [imageUrl],
});
console.log('Task accepted:', task.id);
while (task.status === 'queued' || task.status === 'processing') {
if (Date.now() >= deadline) {
throw new Error('Polling timed out. Resume GET /api/v1/task/' + task.id + '; do not resubmit.');
}
await sleep(3000);
task = await api('/api/v1/task/' + task.id);
}
if (task.status !== 'succeeded') {
throw new Error(task.error ?? ('Task ended with status: ' + task.status));
}
await mkdir('outputs', { recursive: true });
if (!task.result) throw new Error('The task has no downloadable result.');
{
const file = task.result;
// Storage downloads must NOT receive your API key.
const response = await fetch(new URL(file.url, origin), {
signal: AbortSignal.timeout(60_000),
});
if (!response.ok) throw new Error('Download failed: HTTP ' + response.status);
const destination = join('outputs', basename(file.filename));
await writeFile(destination, Buffer.from(await response.arrayBuffer()));
console.log('Saved:', destination);
}
Run node word.mjs or python word.py after setting your environment variables.
Image to Excel
Convert images of tables into a single editable Excel workbook. Use service: "image-to-excel".
/api/v1/taskRequest body
{
"input": [
"https://your-storage.example.com/page.png"
],
"service": "image-to-excel"
}Returns: HTTP 202 with a queued task. This does not mean the conversion has finished.
Successful output: One .xlsx workbook in data.result, with a signed download URL valid for 30 minutes after completion.
Successful response · HTTP 200
{
"code": 0,
"message": "ok",
"data": {
"id": "b758a315-8fa1-4c6a-9d1b-4d8ece769152",
"service": "image-to-excel",
"status": "succeeded",
"lane": "paid",
"input": [
"https://your-storage.example.com/page.png"
],
"result": {
"url": "https://private-results.oss-cn-hangzhou.aliyuncs.com/doc-api-results/20261106/b758a315-8fa1-4c6a-9d1b-4d8ece769152.xlsx?OSSAccessKeyId=EXAMPLE&Expires=1793939400&Signature=EXAMPLE_SIGNATURE",
"filename": "document.xlsx",
"size": 24576,
"contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
},
"error": null,
"createdAt": "2026-11-06T04:00:00.000Z",
"expiresAt": "2026-11-06T04:30:00.000Z"
}
}Complete example · submit, poll & download
// Node.js 20+ · No dependencies · Save as excel.mjs
import { mkdir, writeFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises';
const baseUrl = process.env.API_BASE_URL;
const apiKey = process.env.API_KEY;
const imageUrl = process.env.IMAGE_URL;
if (!baseUrl || !apiKey || !imageUrl) {
throw new Error('Set API_BASE_URL, API_KEY and IMAGE_URL.');
}
const origin = new URL(baseUrl).origin;
const deadline = Date.now() + 30 * 60_000;
async function api(path, body) {
const url = new URL(path, origin);
if (url.origin !== origin) throw new Error('Unexpected API origin.');
const response = await fetch(url, {
method: body ? 'POST' : 'GET',
headers: {
Authorization: 'Bearer ' + apiKey,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
...(body ? { body: JSON.stringify(body) } : {}),
signal: AbortSignal.timeout(60_000),
redirect: 'error',
});
// Only retry reads. A repeated POST could create a second billable task.
if (!body && response.status === 429 && Date.now() < deadline) {
const delay = Math.min(60, Math.max(3,
Number(response.headers.get('Retry-After')) || 3));
await response.body?.cancel();
await sleep(delay * 1000);
return api(path);
}
const result = await response.json();
if (!response.ok || result.code !== 0) {
throw new Error('HTTP ' + response.status + ': ' + (result.message ?? 'Request failed'));
}
return result.data;
}
let task = await api('/api/v1/task', {
service: 'image-to-excel',
input: [imageUrl],
});
console.log('Task accepted:', task.id);
while (task.status === 'queued' || task.status === 'processing') {
if (Date.now() >= deadline) {
throw new Error('Polling timed out. Resume GET /api/v1/task/' + task.id + '; do not resubmit.');
}
await sleep(3000);
task = await api('/api/v1/task/' + task.id);
}
if (task.status !== 'succeeded') {
throw new Error(task.error ?? ('Task ended with status: ' + task.status));
}
await mkdir('outputs', { recursive: true });
if (!task.result) throw new Error('The task has no downloadable result.');
{
const file = task.result;
// Storage downloads must NOT receive your API key.
const response = await fetch(new URL(file.url, origin), {
signal: AbortSignal.timeout(60_000),
});
if (!response.ok) throw new Error('Download failed: HTTP ' + response.status);
const destination = join('outputs', basename(file.filename));
await writeFile(destination, Buffer.from(await response.arrayBuffer()));
console.log('Saved:', destination);
}
Run node excel.mjs or python excel.py after setting your environment variables.
Remove handwriting
Remove handwritten annotations from document images while preserving printed content. Use service: "remove-handwriting".
/api/v1/taskRequest body
{
"input": [
"https://your-storage.example.com/page.png"
],
"service": "remove-handwriting"
}Returns: HTTP 202 with a queued task. This does not mean the conversion has finished.
Successful output: One cleaned image in data.result. Exactly one input URL is required.
Successful response · HTTP 200
{
"code": 0,
"message": "ok",
"data": {
"id": "b758a315-8fa1-4c6a-9d1b-4d8ece769152",
"service": "remove-handwriting",
"status": "succeeded",
"lane": "paid",
"input": [
"https://your-storage.example.com/page.png"
],
"result": {
"url": "https://private-results.oss-cn-hangzhou.aliyuncs.com/doc-api-results/20261106/b758a315-8fa1-4c6a-9d1b-4d8ece769152.png?OSSAccessKeyId=EXAMPLE&Expires=1793939400&Signature=EXAMPLE_SIGNATURE",
"filename": "document.png",
"size": 24576,
"contentType": "image/png"
},
"error": null,
"createdAt": "2026-11-06T04:00:00.000Z",
"expiresAt": "2026-11-06T04:30:00.000Z"
}
}Complete example · submit, poll & download
// Node.js 20+ · No dependencies · Save as handwriting.mjs
import { mkdir, writeFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises';
const baseUrl = process.env.API_BASE_URL;
const apiKey = process.env.API_KEY;
const imageUrl = process.env.IMAGE_URL;
if (!baseUrl || !apiKey || !imageUrl) {
throw new Error('Set API_BASE_URL, API_KEY and IMAGE_URL.');
}
const origin = new URL(baseUrl).origin;
const deadline = Date.now() + 30 * 60_000;
async function api(path, body) {
const url = new URL(path, origin);
if (url.origin !== origin) throw new Error('Unexpected API origin.');
const response = await fetch(url, {
method: body ? 'POST' : 'GET',
headers: {
Authorization: 'Bearer ' + apiKey,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
...(body ? { body: JSON.stringify(body) } : {}),
signal: AbortSignal.timeout(60_000),
redirect: 'error',
});
// Only retry reads. A repeated POST could create a second billable task.
if (!body && response.status === 429 && Date.now() < deadline) {
const delay = Math.min(60, Math.max(3,
Number(response.headers.get('Retry-After')) || 3));
await response.body?.cancel();
await sleep(delay * 1000);
return api(path);
}
const result = await response.json();
if (!response.ok || result.code !== 0) {
throw new Error('HTTP ' + response.status + ': ' + (result.message ?? 'Request failed'));
}
return result.data;
}
let task = await api('/api/v1/task', {
service: 'remove-handwriting',
input: [imageUrl],
});
console.log('Task accepted:', task.id);
while (task.status === 'queued' || task.status === 'processing') {
if (Date.now() >= deadline) {
throw new Error('Polling timed out. Resume GET /api/v1/task/' + task.id + '; do not resubmit.');
}
await sleep(3000);
task = await api('/api/v1/task/' + task.id);
}
if (task.status !== 'succeeded') {
throw new Error(task.error ?? ('Task ended with status: ' + task.status));
}
await mkdir('outputs', { recursive: true });
if (!task.result) throw new Error('The task has no downloadable result.');
{
const file = task.result;
// Storage downloads must NOT receive your API key.
const response = await fetch(new URL(file.url, origin), {
signal: AbortSignal.timeout(60_000),
});
if (!response.ok) throw new Error('Download failed: HTTP ' + response.status);
const destination = join('outputs', basename(file.filename));
await writeFile(destination, Buffer.from(await response.arrayBuffer()));
console.log('Saved:', destination);
}
Run node handwriting.mjs or python handwriting.py after setting your environment variables.
Enhance a scan
Enhance document images for a cleaner, more readable scan. Use service: "document-scan".
/api/v1/taskRequest body
{
"input": [
"https://your-storage.example.com/page.png"
],
"service": "document-scan"
}Returns: HTTP 202 with a queued task. This does not mean the conversion has finished.
Successful output: One enhanced image in data.result. Exactly one input URL is required.
Successful response · HTTP 200
{
"code": 0,
"message": "ok",
"data": {
"id": "b758a315-8fa1-4c6a-9d1b-4d8ece769152",
"service": "document-scan",
"status": "succeeded",
"lane": "paid",
"input": [
"https://your-storage.example.com/page.png"
],
"result": {
"url": "https://private-results.oss-cn-hangzhou.aliyuncs.com/doc-api-results/20261106/b758a315-8fa1-4c6a-9d1b-4d8ece769152.png?OSSAccessKeyId=EXAMPLE&Expires=1793939400&Signature=EXAMPLE_SIGNATURE",
"filename": "document.png",
"size": 24576,
"contentType": "image/png"
},
"error": null,
"createdAt": "2026-11-06T04:00:00.000Z",
"expiresAt": "2026-11-06T04:30:00.000Z"
}
}Complete example · submit, poll & download
// Node.js 20+ · No dependencies · Save as scan.mjs
import { mkdir, writeFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises';
const baseUrl = process.env.API_BASE_URL;
const apiKey = process.env.API_KEY;
const imageUrl = process.env.IMAGE_URL;
if (!baseUrl || !apiKey || !imageUrl) {
throw new Error('Set API_BASE_URL, API_KEY and IMAGE_URL.');
}
const origin = new URL(baseUrl).origin;
const deadline = Date.now() + 30 * 60_000;
async function api(path, body) {
const url = new URL(path, origin);
if (url.origin !== origin) throw new Error('Unexpected API origin.');
const response = await fetch(url, {
method: body ? 'POST' : 'GET',
headers: {
Authorization: 'Bearer ' + apiKey,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
...(body ? { body: JSON.stringify(body) } : {}),
signal: AbortSignal.timeout(60_000),
redirect: 'error',
});
// Only retry reads. A repeated POST could create a second billable task.
if (!body && response.status === 429 && Date.now() < deadline) {
const delay = Math.min(60, Math.max(3,
Number(response.headers.get('Retry-After')) || 3));
await response.body?.cancel();
await sleep(delay * 1000);
return api(path);
}
const result = await response.json();
if (!response.ok || result.code !== 0) {
throw new Error('HTTP ' + response.status + ': ' + (result.message ?? 'Request failed'));
}
return result.data;
}
let task = await api('/api/v1/task', {
service: 'document-scan',
input: [imageUrl],
});
console.log('Task accepted:', task.id);
while (task.status === 'queued' || task.status === 'processing') {
if (Date.now() >= deadline) {
throw new Error('Polling timed out. Resume GET /api/v1/task/' + task.id + '; do not resubmit.');
}
await sleep(3000);
task = await api('/api/v1/task/' + task.id);
}
if (task.status !== 'succeeded') {
throw new Error(task.error ?? ('Task ended with status: ' + task.status));
}
await mkdir('outputs', { recursive: true });
if (!task.result) throw new Error('The task has no downloadable result.');
{
const file = task.result;
// Storage downloads must NOT receive your API key.
const response = await fetch(new URL(file.url, origin), {
signal: AbortSignal.timeout(60_000),
});
if (!response.ok) throw new Error('Download failed: HTTP ' + response.status);
const destination = join('outputs', basename(file.filename));
await writeFile(destination, Buffer.from(await response.arrayBuffer()));
console.log('Saved:', destination);
}
Run node scan.mjs or python scan.py after setting your environment variables.
Create a task
Accept an ordered list of publicly accessible image URLs and create an asynchronous conversion task. This endpoint is shared by all four task types.
/api/v1/taskRequest body
| Parameter | Type | Required | Description |
|---|---|---|---|
| input | array<string> | Required | Public image URLs. Word/Excel: 1–20 URLs, in page order. Handwriting/scan: exactly 1 URL. |
| input[] | string · URI | Required | Public HTTP(S) image URL; up to 2,048 characters. Signed URLs are supported if no additional headers are needed. |
| service | enum | Required | image-to-word, image-to-excel, remove-handwriting, or document-scan. |
service chooses the task. Unknown fields are rejected. Images are imported in the background after the request is accepted. Keep source URLs accessible until import finishes.
Response headers
| Header | Description |
|---|---|
| Location | Relative task retrieval URL: /api/v1/task/{task_id}. |
| Retry-After | Recommended polling delay in seconds (3). |
| Cache-Control | no-store |
The response uses the standard { code, message, data } envelope. code: 0 confirms API success. See the task object for field definitions.
{
"code": 0,
"message": "ok",
"data": {
"id": "b758a315-8fa1-4c6a-9d1b-4d8ece769152",
"service": "image-to-word",
"status": "queued",
"lane": "paid",
"input": [
"https://your-storage.example.com/page.png"
],
"result": null,
"error": null,
"createdAt": "2026-11-06T04:00:00.000Z",
"expiresAt": null
}
}Retrieve a task
Retrieve the current state of a task owned by the authenticated account. Poll every 3 seconds until succeeded or failed.
/api/v1/task/{task_id}| Parameter | Type | Required | Description |
|---|---|---|---|
| task_id | string | Required | The task ID returned when the task was created. |
curl 'https://doccrisp.com/api/v1/task/b758a315-8fa1-4c6a-9d1b-4d8ece769152' \
-H 'Authorization: Bearer YOUR_API_KEY'Task lifecycle
| Status | Terminal | Client action |
|---|---|---|
| queued | No | Accepted; continue polling. |
| processing | No | Importing or converting; continue polling. |
| succeeded | Yes | Stop polling and download results before expiresAt. |
| failed | Yes | Stop polling and inspect error. This includes expired results. |
HTTP 200 confirms that the query succeeded; task completion is determined by data.status. Requests for another account’s task return 404. Responses include Cache-Control: no-store and Retry-After: 3.
The task object
The data property of successful submission and polling responses contains the task object. Result fields are null until the task succeeds.
| Field | Type | Description |
|---|---|---|
| id | string | The public task identifier. Use it to retrieve this task. |
| status | enum | queued, processing, succeeded, failed, or canceled. Stop polling at a terminal status. |
| service | enum | The service submitted with the task. |
| lane | enum | free or paid. Website sessions use this for the waiting label. API-key tasks are paid. |
| input | string[] | The input URL list submitted when creating the task. |
| result | File | null | One result file for every task type. Contains url, filename, size and contentType. Null until success or after expiry. ZIP/PDF output is not supported. |
| error | string | null | Failure or expiration message. Null when there is no error. |
| createdAt | string · date-time | Task creation time in ISO 8601 UTC. |
| expiresAt | string · date-time | null | Fixed download deadline: 30 minutes after successful task completion. Null before completion. Polling never extends it. |
The File object
| Field | Type | Description |
|---|---|---|
| url | string | Signed private download URL. Valid for 30 minutes after completion; polling does not renew it. |
| filename | string | Suggested filename for saving the result. |
| size | number | File size in bytes. |
| contentType | string | MIME type of the result. |
{
"url": "https://private-results.oss-cn-hangzhou.aliyuncs.com/doc-api-results/20261106/b758a315-8fa1-4c6a-9d1b-4d8ece769152.docx?OSSAccessKeyId=EXAMPLE&Expires=1793939400&Signature=EXAMPLE_SIGNATURE",
"filename": "document.docx",
"size": 24576,
"contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
}The result field contains one file for every task type. Download it before expiresAt, exactly 30 minutes after successful completion. Polling never extends this deadline.
Result files are stored separately from the API. Do not send your API key with storage downloads.
After expiry, the signed URL is denied by storage. Polling returnsfailed, an expiration error and result: null. ZIP and PDF generation are not offered by this API.
Limits & billing
Limits apply to third-party API requests. All API keys on an account share the same active-task slot.
| Limit | Value | Behavior |
|---|---|---|
| Active tasks | 1 per account | Includes image import and conversion. Additional submissions return 429; they are not queued or charged. |
| Input URLs per task | Word/Excel: 1–20; handwriting/scan: 1 | input is always a list of URL strings. Word/Excel preserve page order. |
| Image size | 20 MiB each | Validated while downloading. |
| Image formats | JPEG, PNG, WebP | File content is checked; SVG is not supported. |
| JSON request body | 64 KiB | Oversized requests return 413. |
| Source URL access | Public IPv4; HTTP(S), ports 80/443 | No additional authentication headers. Private networks, URL credentials and IPv6-only hosts are unsupported. |
| Source redirects | Up to 3 | Every redirect destination is checked. |
| Download timeout | 30 seconds / image | Images are imported sequentially. |
| Submission interval | At least 2 seconds | Excess requests return 429. |
| Polling interval | 3 seconds recommended | Honor Retry-After when returned. |
| Signed result access | 30 minutes after completion | Use expiresAt as the fixed deadline. Polling cannot renew access; unsigned access is denied. |
Credit usage
| Task | Credits per image |
|---|---|
| Image to Word | 2 |
| Image to Excel | 2 |
| Remove handwriting | 1 |
| Enhance a scan | 1 |
| Retrieve a task | 0 |
Credits are reserved when a task is accepted. Failed imports and failed conversions restore reserved credits. Insufficient balance returns 402 without creating a task. Download expiration does not refund a successfully completed task.
Errors & retries
Distinguish request errors from asynchronous task failures. An accepted task can fail later even though its submission returned HTTP 202.
Request errors
Non-successful HTTP responses use code: -1 and a human-readable message. Inspect the HTTP status and honor Retry-After on rate-limit responses.
{
"code": -1,
"message": "Not enough credits."
}| HTTP | Meaning | Recommended action |
|---|---|---|
| 400 | Invalid request | Fix the JSON body, image count, URL, or service. |
| 401 | Authentication failed | Supply an active user API key from an enabled account. |
| 402 | Insufficient credits | Add credits, then submit again. No task was created. |
| 404 | Task not found | Check the task ID and authenticate as the account that created it. |
| 413 | Request too large | Keep the JSON request body at or below 64 KiB. |
| 429 | Concurrency or rate limit | Honor Retry-After. If a task URL is provided, poll that task. |
| 500 | Server error | A POST may already have been accepted. Confirm its outcome before resubmitting. |
| 503 | Service unavailable | The conversion service is not configured. Contact support. |
Asynchronous failures
An inaccessible source URL, invalid image content or provider error appears in the polling response as data.status: "failed" with an error message. Stop polling, correct the cause, then submit a new task.
{
"code": 0,
"message": "ok",
"data": {
"id": "b758a315-8fa1-4c6a-9d1b-4d8ece769152",
"status": "failed",
"error": "Image import was interrupted. Submit a new request.",
"result": null
}
}Retry policy
- Retry GET requests after the delay in Retry-After when throttled.
- Do not automatically retry POST requests. A timeout or 500 response may occur after acceptance. Follow a known task at /api/v1/task/{task_id}; if a 429 message provides a task URL, retrieve it.
- No idempotency-key contract is provided. A repeated submission after completion creates a new billable task.
- Interrupted imports are marked failed and refunded after approximately 25 minutes when queried again.