On this page

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.

REST API v1HTTPS · JSON · Bearer authentication

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.

1Submit public image URLs
2Poll the task status
3Download the results

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.

Required authentication headers
HeaderValue
AuthorizationBearer YOUR_API_KEY
Content-Typeapplication/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.

Authentication header
curl 'https://doccrisp.com/api/v1/task/b758a315-8fa1-4c6a-9d1b-4d8ece769152' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Base URL

https://doccrisp.com

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.

Example environment variables
VariableRequiredDescription
API_BASE_URLYeshttps://doccrisp.com.
API_KEYYesAn active user API key.
IMAGE_URLYesA public URL of your JPEG, PNG or WebP image.
Node.js 20+ · no packages required
# 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
Python 3.10+ · standard library only
# 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.py
Windows PowerShell setup
PowerShell
$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.py

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

POST/api/v1/task
2 credits / image

Request body

application/json
{
  "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.

Request parameters/Polling contract

Successful response · HTTP 200
Polling response · succeeded
{
  "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

word.mjs
// 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".

POST/api/v1/task
2 credits / image

Request body

application/json
{
  "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.

Request parameters/Polling contract

Successful response · HTTP 200
Polling response · succeeded
{
  "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

excel.mjs
// 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".

POST/api/v1/task
1 credit / image

Request body

application/json
{
  "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.

Request parameters/Polling contract

Successful response · HTTP 200
Polling response · succeeded
{
  "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

handwriting.mjs
// 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".

POST/api/v1/task
1 credit / image

Request body

application/json
{
  "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.

Request parameters/Polling contract

Successful response · HTTP 200
Polling response · succeeded
{
  "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

scan.mjs
// 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.

POST/api/v1/task
Returns HTTP 202

Request body

Create task request parameters
ParameterTypeRequiredDescription
inputarray<string>RequiredPublic image URLs. Word/Excel: 1–20 URLs, in page order. Handwriting/scan: exactly 1 URL.
input[]string · URIRequiredPublic HTTP(S) image URL; up to 2,048 characters. Signed URLs are supported if no additional headers are needed.
serviceenumRequiredimage-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

Create task response headers
HeaderDescription
LocationRelative task retrieval URL: /api/v1/task/{task_id}.
Retry-AfterRecommended polling delay in seconds (3).
Cache-Controlno-store

The response uses the standard { code, message, data } envelope. code: 0 confirms API success. See the task object for field definitions.

HTTP 202 · accepted
{
  "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.

GET/api/v1/task/{task_id}
Returns HTTP 200
Retrieve task path parameters
ParameterTypeRequiredDescription
task_idstringRequiredThe task ID returned when the task was created.
Retrieve a task
curl 'https://doccrisp.com/api/v1/task/b758a315-8fa1-4c6a-9d1b-4d8ece769152' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Task lifecycle

queuedprocessingsucceededorfailed
Task status values
StatusTerminalClient action
queuedNoAccepted; continue polling.
processingNoImporting or converting; continue polling.
succeededYesStop polling and download results before expiresAt.
failedYesStop 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.

Task response fields
FieldTypeDescription
idstringThe public task identifier. Use it to retrieve this task.
statusenumqueued, processing, succeeded, failed, or canceled. Stop polling at a terminal status.
serviceenumThe service submitted with the task.
laneenumfree or paid. Website sessions use this for the waiting label. API-key tasks are paid.
inputstring[]The input URL list submitted when creating the task.
resultFile | nullOne result file for every task type. Contains url, filename, size and contentType. Null until success or after expiry. ZIP/PDF output is not supported.
errorstring | nullFailure or expiration message. Null when there is no error.
createdAtstring · date-timeTask creation time in ISO 8601 UTC.
expiresAtstring · date-time | nullFixed download deadline: 30 minutes after successful task completion. Null before completion. Polling never extends it.

The File object

File response fields
FieldTypeDescription
urlstringSigned private download URL. Valid for 30 minutes after completion; polling does not renew it.
filenamestringSuggested filename for saving the result.
sizenumberFile size in bytes.
contentTypestringMIME type of the result.
File object · Word document
{
  "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.

API limits
LimitValueBehavior
Active tasks1 per accountIncludes image import and conversion. Additional submissions return 429; they are not queued or charged.
Input URLs per taskWord/Excel: 1–20; handwriting/scan: 1input is always a list of URL strings. Word/Excel preserve page order.
Image size20 MiB eachValidated while downloading.
Image formatsJPEG, PNG, WebPFile content is checked; SVG is not supported.
JSON request body64 KiBOversized requests return 413.
Source URL accessPublic IPv4; HTTP(S), ports 80/443No additional authentication headers. Private networks, URL credentials and IPv6-only hosts are unsupported.
Source redirectsUp to 3Every redirect destination is checked.
Download timeout30 seconds / imageImages are imported sequentially.
Submission intervalAt least 2 secondsExcess requests return 429.
Polling interval3 seconds recommendedHonor Retry-After when returned.
Signed result access30 minutes after completionUse expiresAt as the fixed deadline. Polling cannot renew access; unsigned access is denied.

Credit usage

Task credit prices
TaskCredits per image
Image to Word2
Image to Excel2
Remove handwriting1
Enhance a scan1
Retrieve a task0

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.

HTTP 402 · insufficient credits
{
  "code": -1,
  "message": "Not enough credits."
}
HTTP error reference
HTTPMeaningRecommended action
400Invalid requestFix the JSON body, image count, URL, or service.
401Authentication failedSupply an active user API key from an enabled account.
402Insufficient creditsAdd credits, then submit again. No task was created.
404Task not foundCheck the task ID and authenticate as the account that created it.
413Request too largeKeep the JSON request body at or below 64 KiB.
429Concurrency or rate limitHonor Retry-After. If a task URL is provided, poll that task.
500Server errorA POST may already have been accepted. Confirm its outcome before resubmitting.
503Service unavailableThe 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.

HTTP 200 · task failed (excerpt)
{
  "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.