Developer documentation · API 1.9.0 · SDK 1.1.0

Skavio Processing API

5-minute quickstart: your first processing job

API base: https://getskavio.com/v1. Create a Skavio account and company profile in the company workspace, then create a read/write API key. The secret is shown once. A company receives 1,000 trial credits.

Upload the source, estimate its price, submit the job with a stable idempotency key, poll until terminal status, then download the results. Do not put API keys in frontend code.

Requirements: Bash, curl and jq. The example uses a synthetic invoice and estimates its price before processing. Queue time varies; five minutes is a setup guide, not a processing SLA.

# Bash + curl + jq. Run in a new directory. Keep the key server-side.
read -rsp 'Skavio API key: ' SKAVIO_API_KEY; echo
export SKAVIO_API_KEY
BASE=https://getskavio.com
printf 'SYNTHETIC INVOICE\nSupplier: Example Supplies\nInvoice number: DEMO-001\nCurrency: USD\nTotal: 42.00\n' > invoice.txt
curl -fsS "$BASE/v1/uploads" -H "Authorization: Bearer $SKAVIO_API_KEY" \
  -F 'files=@invoice.txt' > upload.json
jq -n --arg id "$(jq -er '.uploads[0].id' upload.json)" \
  '{operation:"flow-extract",upload_ids:[$id],fields:["supplier","invoice_number","currency","total"]}' > request.json
curl -fsS "$BASE/v1/estimate" -H "Authorization: Bearer $SKAVIO_API_KEY" \
  -H 'Content-Type: application/json' --data-binary @request.json > estimate.json
jq --argjson cap "$(jq -er '.estimated_credits' estimate.json)" \
  '. + {max_credits:$cap}' request.json > job-request.json
# Keep this key and the exact payload for retries of THIS submission.
printf 'ph-quickstart-%s' "$(date +%s)-$RANDOM" > idempotency.txt
curl -fsS "$BASE/v1/jobs" -H "Authorization: Bearer $SKAVIO_API_KEY" \
  -H 'Content-Type: application/json' -H "Idempotency-Key: $(cat idempotency.txt)" \
  --data-binary @job-request.json > submitted.json
JOB_ID=$(jq -er '.id' submitted.json)
for attempt in {1..60}; do
  curl -fsS "$BASE/v1/jobs/$JOB_ID" -H "Authorization: Bearer $SKAVIO_API_KEY" > status.json
  STATUS=$(jq -er '.status' status.json)
  case "$STATUS" in completed|failed) break ;; esac
  sleep 5
done
jq '{id,status,charged_credits,result}' status.json
# If still queued/processing, continue polling this JOB_ID; do not resubmit.
if [ "$STATUS" = completed ]; then
  RESULT=$(jq -er '.result.files[] | select(.name | endswith(".json")) | .download_path' status.json)
  # curl strips Authorization on cross-origin redirects; never use --location-trusted.
  curl -fsSL "$BASE$RESULT" -H "Authorization: Bearer $SKAVIO_API_KEY" -o result.json
  jq . result.json
fi
unset SKAVIO_API_KEY

Python and JavaScript: use the official SDK quickstarts (Python 3.10+ / Node.js 22+). Install from the repository; packages are not yet on PyPI/npm. Runnable Python example · Runnable Node.js example · Postman collection.

Official SDK 1.1.0

The official server-side SDK release for Processing API 1.9.0 is 1.1.0 for both Python and Node.js. The SDK registry covers all 92 operations in the current OpenAPI contract. Packages are supplied from the public GitHub repository and are not yet published to PyPI or npm.

Python 3.10+

git clone https://github.com/skavio-eu/skavio-processing-api.git
cd skavio-processing-api
python3 -m pip install ./sdk/python

SkavioClient provides uploads, estimates, idempotent job submission, batches, bulk manifests, workflows, polling, downloads and the generic call() method for every registered operation.

Node.js 22+

git clone https://github.com/skavio-eu/skavio-processing-api.git
cd skavio-processing-api
npm install ./sdk/javascript

Package name: @skavio/sdk. TypeScript declarations are included and the package has no runtime dependencies.

SDK source and examples OpenAPI 1.9.0 JSON

Storage Fabric · API 1.9.0

New sources and results use private persistent local storage, with compatible legacy local files. Company-scoped direct uploads, cloud imports, retention, archive, hold and verified local replicas and scheduled offsite Borg backup are available. Pricing V2 remains unchanged.

Create a signed form with POST /v1/storage/direct-uploads, send all returned fields plus the file, then call POST /v1/storage/direct-uploads/{session_id}/complete. Only successful completion after size/hash verification makes the upload usable. Direct uploads allow up to 500 MiB; quota and tool limits also apply.

Browser FormData uploads use the returned same-origin signed URL, without an API key or custom headers and without a manually set Content-Type. Check the transfer response, then call authenticated completion to verify size and checksum. Keep API keys server-side.

POST /v1/storage/imports accepts bounded imports from approved public HTTPS cloud URLs. Poll the import until completed and use its upload_id. Production imports are bounded by the 2 GiB company quota and the current free-space admission limit; processing limits still apply.

GET/PATCH /v1/storage/policy controls source/result retention (1–90 days, default 7), archive retention (1–3650 days, default 365) and verified local replica backup. Offsite recovery uses scheduled Borg archives; a verified local replica is not a claim that a new offsite archive has already completed. Object endpoints provide archive/restore, hold, manual backup and checksum-verified backup recovery. New policy settings govern new objects; inspect retain_until on existing objects. Quota changes require an operator.

Result downloads may return HTTP 307 to a short-lived signed URL. Follow the redirect without forwarding your API key to storage. Full Storage Fabric integration guide.

Endpoint reference

Download complete OpenAPI JSON

Import this contract into Postman, Insomnia or your OpenAPI client generator. Request bodies, field constraints and multipart upload schemas are generated from the implementation. Routes use the authenticated company scope; IDs from another company return 404.

Loading endpoint reference…

Operations and supported files

flow-extract accepts PDF, images and UTF-8 text. Every input document produces one document row; line items use a separate worksheet. Output: XLSX, CSV and JSON. Fields use unique names starting with a letter and containing letters, numbers or underscores, maximum 64 characters and 30 fields. Metadata names items, warnings, evidence, review_required and human_review are reserved. Missing values are null. Evidence is checked against the source text; this is an aid to review, not a guarantee of correctness.

transcribe and meeting accept one audio or video source. Normal file operations accept one source; pdf-merge accepts 2–20 PDFs. Single jobs keep their existing source rules. For server-managed file batches, use POST /v1/batches; each batch item becomes a normal child job with independent billing/result status. Saved workflows currently support document extraction.

Batch processing

API v1.2 adds first-class batches without changing the existing /v1/jobs contract. A batch is a parent object containing ordinary child jobs, so one bad file does not cancel successful files or require you to build your own queue.

Use POST /v1/batches/estimate to quote the whole batch, then POST /v1/batches with one stable Idempotency-Key. Creation is atomic: either the full batch is reserved/enqueued or no child job is created and no credits are reserved.

One operation over many files

curl -sS https://getskavio.com/v1/batches/estimate   -H "Authorization: Bearer $SKAVIO_API_KEY"   -H "Content-Type: application/json"   -d '{
    "operation": "webp",
    "upload_ids": ["UPLOAD_1", "UPLOAD_2", "UPLOAD_3"],
    "options": {},
    "max_credits": 90
  }'

curl -sS https://getskavio.com/v1/batches   -H "Authorization: Bearer $SKAVIO_API_KEY"   -H "Idempotency-Key: images-webp-2026-10-02-001"   -H "Content-Type: application/json"   -d '{
    "operation": "webp",
    "upload_ids": ["UPLOAD_1", "UPLOAD_2", "UPLOAD_3"],
    "webhook_id": "WEBHOOK_ID",
    "webhook_mode": "batch",
    "max_credits": 90
  }'

With the shorthand upload_ids, each upload becomes one child job. This works for File Toolbox single-file operations, OCR, Transcribe, Meeting and Flow extraction. For Flow this means independent per-document results; use explicit groups when one child job should contain multiple documents.

Grouped sources

Use items when an operation needs multiple sources per child job. For example, two independent PDF merges:

{
  "operation": "pdf-merge",
  "items": [
    {"upload_ids": ["PDF_A", "PDF_B"]},
    {"upload_ids": ["PDF_C", "PDF_D", "PDF_E"]}
  ]
}

The same grouping model can be used with Flow when several uploaded documents should produce one combined extraction export. A request must use either upload_ids or items, never both.

Lifecycle and partial failures

Batch status is queued → processing → completed | partial | failed. partial means at least one child completed and at least one failed. GET /v1/batches/{batch_id} returns aggregate progress plus every child job with its normal job result metadata. Failed child jobs release their own reserved credits; successful items remain charged and downloadable.

POST /v1/batches/{batch_id}/retry-failed creates a new batch containing only failed items. Supply a new stable Idempotency-Key. Source retention still applies, so retry before the original uploads expire.

Webhooks and downloads

webhook_mode can be batch (default), items, or both. Batch callbacks use the existing signing secret and signature rules with event types batch.completed, batch.partial or batch.failed. Item mode produces the normal signed job.completed/job.failed callbacks for each child.

Download child results through their authenticated job result paths, or after the batch is terminal use GET /v1/batches/{batch_id}/results.zip. The ZIP contains successful results grouped by item and a manifest.json. Combined ZIP generation is limited to 1 GB; larger batches should download item results individually.

Limits

A batch contains up to 100 items. Child jobs count toward the existing company queue limit of 100. The current fair-share scheduler still processes one active job per company, so large batches are naturally serialized for that company while the platform can process jobs from other companies concurrently. max_credits applies to the complete batch quote before anything is reserved.

Large bulk manifests · 1.5.0

For more than 100 logical items, create POST /v1/bulks with a stable Idempotency-Key, operation, total_items (up to 100,000), max_credits and total declared_input_bytes, declared_output_bytes and declared_scratch_bytes. This reserves disk headroom only. It does not create all jobs or reserve all credits.

Upload supported sources through the existing upload endpoint. Append immutable pages through PUT /v1/bulks/{id}/manifest: an offset plus up to 100 items, each with upload_ids, output_bytes and scratch_bytes. Replaying the same offset/body returns the existing manifest; a changed body returns 409. Source pages remain pinned while the manifest is open. Reusing a source in multiple logical items is allowed and incurs a quote for each item.

Seal using POST /v1/bulks/{id}/seal. The full measured quote must fit max_credits and available credits at seal time; all declared byte totals must fit the reserved disk and company quota. Scratch covers the sum of the largest simultaneous item working sets, not just one file. Each item needs at least 4096 output/scratch bytes; scratch also covers twice its input. Declare realistic output/working overhead. Admission is not a guarantee of converter expansion, hardware capacity, turnaround or SLA.

The scheduler creates bounded ordinary child batches, 16 items by default, and waits for that window before creating the next. Each child keeps existing source limits, company authorization, workflow validation, quote, atomic reservation, idempotency, concurrency, webhooks, retries and seven-day result retention. Regular BatchSpec stays capped at 100. Individual PDFs still need splitting above 100 pages. Other spending, missing sources, changed workflows, revoked keys or disk pressure can pause dispatch. Storage/credit/capacity pauses resume when recovered; authorization/source changes require review or authenticated POST /v1/bulks/{id}/resume.

GET /v1/storage reports your company quota and reservations, customer-data filesystem capacity and metadata-disk pressure. Both disks need free headroom for admission and processing; storage pressure pauses work without discarding credit reservations.

Poll GET /v1/bulks/{id}. Read bounded /items or /outputs pages with after=-1&limit=100, then the returned next_after. Output paths use the existing authenticated job downloads; expired files are marked expired and return 410. POST /retry-failed with a new stable Idempotency-Key retries failed items only. Successful items are never reserved again. DELETE cancels future dispatch after admitted work finishes and retains accounting.

webhook_mode is bulk, items or both. Bulk completion uses the existing signed callback format and an event ID for consumer deduplication; item events remain ordinary job callbacks. Provider IDs and local checkpoints survive restart. Unknown provider acceptance pauses for owner reconciliation, rather than silently submitting another paid request. A completed output checkpoint reconciles settlement once after a crash. External execution and callbacks are not guaranteed exactly once.

Operation options

Put settings in the options object. File Toolbox validates source dimensions, duration, page ranges and passwords before processing. Passwords are forwarded privately to the PDF worker; do not log request bodies.

OperationExample options
pdf-extract / pdf-delete{"pages":"1-3,5"}
pdf-rotate{"pages":"1-2","angle":90} · 90, 180 or 270 degrees
pdf-protect / pdf-unlock{"password":"user-supplied-secret"} · 1–128 characters
pdf-watermark{"text":"DRAFT","pages":""} · empty pages selects all
pdf-number{"start":1,"pages":""}
image-edit{"width":1200,"height":1200,"angle":0,"flip":"none","format":"webp","crop":[0,0,800,800]} · resize contains image within the box and preserves aspect ratio; supply both dimensions
image-compress{"format":"webp","level":"balanced"} · formats jpg/png/webp; level balanced/strong
audio-trim / video-trim{"start":10,"end":60} · seconds within source duration
video-rotate{"angle":90}
video-resize{"height":720} · 240, 360, 480, 720, 1080, 1440, 2160
audio-compress / video-compress{"level":"balanced"}
transcribe{"output_format":"txt"} · TXT, DOCX, SRT, VTT, JSON with native word timestamps and anonymous speaker segments

Without a saved workflow, set operation, upload_ids, optional fields and instructions. With a saved workflow, also set workflow_id; the saved operation, options, fields and instructions take precedence. max_credits rejects a job if its quote is too high.

Job lifecycle, retries and idempotency

queued → processing → completed | failed. A job ID is returned immediately with HTTP 202. Poll every 3–10 seconds; back off on 429 or transient 5xx errors.

Use a unique Idempotency-Key for each business request, 8–128 characters: letters, numbers, underscore, dot, colon or dash. Retrying the identical request with the same key returns the same job without charging again. Reusing a key with different parameters returns 409. On a lost submission response, retry with the original key; do not create another key.

Failed jobs release reserved credits. Submit a new job with a new idempotency key when you intentionally retry a terminal failure. Temporary provider outages keep the job queued with its reservation. Durable checkpoints and retry schedules survive restarts; a replay with the same idempotency key does not create another charge. Permanent failures release reserved credits.

Authenticated downloads use the paths in result.files; no public result URL or API key in a query string. Use the dashboard to correct extracted scalar fields and regenerate exports without another processing charge. Review changes are recorded; line items must still be checked against the original. Results expire after 7 days. Preserve them in your system before expiration.

Credits, estimation and payments

POST /v1/estimate returns the price without starting work. The current contract reserves and charges that fixed quote on success. A failed job releases the full reservation. Estimates use server-measured source bytes, PDF pages and media duration.

File operations: 3 credits per started 25 MB or 10 pages (larger basis per source). OCR text: 4 per page; OCR DOCX/searchable PDF: 12 per page. Flow: 29 per page. Transcribe: 9 per started minute. Meeting: 17 per started minute. Media tools: 5 per started minute. Text extraction counts one page per started 4,000 source bytes. Image sources count as one page. Encrypted PDFs report 0 pages on upload; pdf-unlock validates your password and measures the real page count before quoting. Other operations require an unlocked source. Long document text is limited to prevent unexpectedly expensive extraction.

Monthly company plans: Starter $33 for 34,000 credits, Business $89 for 95,000 and Pro $225 for 254,000 and Enterprise $560 for 673,000. Credits are added after paid invoices and roll over. Manage or cancel through the billing portal. Each plan currently uses the same processing limits; priority service is not advertised.

Paid credit packs do not expire. Bonus packs change the effective USD price per credit. The dashboard shows credit units, not a refundable cash balance. All web tools and API now use the same prepaid wallet. Company admins can start a Stripe card checkout and reconcile a completed payment; signed Stripe webhooks reconcile it automatically. A checkout being opened does not add credits.

Keys, company scope and teams

Send Authorization: Bearer sk_live_…. Keys are hashed at rest and can have read-only or read/write scope. Administrators create and revoke keys through an interactive same-origin web session; API keys cannot create other keys or start billing checkouts. Revoke and replace keys to rotate them. Maximum 20 active keys per company.

API keys belong to a company and the member who created them. Removing membership invalidates access through that key. Team invitations are created in the dashboard. Invitations are accepted by the matching signed-in account; no email is sent automatically. Each account belongs to one company. Maximum 20 members. Company billing administrators manage settings; ordinary members can process and download files within their company. Do not share accounts between unrelated companies.

Limits, errors and retention

LimitCurrent release
Requests120 per minute per key/session
Upload batch100 files; total 500 MB
Company source storage2 GiB default shared source/result quota; configurable by agreement
Document100 pages per PDF; images count as one page
Media durationTranscribe/Meeting: 10 hours; File Toolbox media: 2 hours
Queue100 pending jobs per company
Processing4 platform jobs across this deployment by default; configured company limit up to 4, with fair scheduling
Results and source retention7 days; accounting metadata retained separately

Errors return JSON with a detail field. 401: missing/invalid key or login. 402: insufficient credits. 403: scope/role/suspension. 404: object absent or not in your company. 409: incompatible state, mismatched idempotency or max-credit rejection. 410: expired result. 413: upload/storage limit. 422: invalid parameters/source. 429: request/queue limit. 502/503/504: processing/provider temporarily unavailable or timeout.

Delete a terminal job to remove platform result data while preserving accounting. Delete an unused upload to remove its source. In-flight sources cannot be deleted. Upstream processors and backups have their own retention; deletion is not a promise of immediate purge from all backups. Text from extraction is sent to the configured OpenAI model; Meeting and transcription use their existing configured processing providers. Review the privacy page and contact us for specific retention arrangements before uploading regulated data.

Webhook status

Register a public HTTPS callback URL in the dashboard or through an administrator session at POST /v1/webhooks. Save the returned signing secret; it is shown once. Pass its id as webhook_id when submitting a job.

On completion or failure we POST a JSON event with id, type (job.completed / job.failed), created_at and a job object. Job result paths still require your API key. No private source data is included in the event.

Verify X-Skavio-Signature as v1=HMAC_SHA256(secret, timestamp + "." + raw_body), using the exact body bytes and X-Skavio-Timestamp. Use constant-time comparison and reject timestamps older than five minutes. Deduplicate by X-Skavio-Event-Id. Acknowledge with a 2xx response after durable receipt.

Delivery is at least once. Up to six attempts are made, with backoff approximately 60, 120, 240, 480 and 960 seconds after failures. A disabled webhook receives no further deliveries. URL redirects are not followed; DNS is checked on every attempt and the HTTPS connection is pinned to a public IP. View status and errors under Webhooks.

import hashlib, hmac, time
def verify(secret, timestamp, raw_body, signature):
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = "v1=" + hmac.new(secret.encode(),
        timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Python example

import os, time, uuid, requests
base = "https://getskavio.com"
headers = {"Authorization": "Bearer " + os.environ["SKAVIO_API_KEY"]}
with open("invoice.pdf", "rb") as source:
    r = requests.post(base + "/v1/uploads", headers=headers,
                      files={"files": ("invoice.pdf", source)}, timeout=180)
r.raise_for_status()
payload = {"operation": "flow-extract",
           "upload_ids": [r.json()["uploads"][0]["id"]],
           "fields": ["supplier", "invoice_number", "total"]}
q = requests.post(base + "/v1/estimate", headers=headers, json=payload, timeout=30)
q.raise_for_status()
payload["max_credits"] = q.json()["estimated_credits"]
# Persist this key with your business request before submitting.
job_headers = {**headers, "Idempotency-Key": str(uuid.uuid4())}
r = requests.post(base + "/v1/jobs", headers=job_headers, json=payload, timeout=30)
r.raise_for_status()
job_id = r.json()["id"]
for _ in range(720):
    r = requests.get(base + "/v1/jobs/" + job_id, headers=headers, timeout=30)
    r.raise_for_status()
    job = r.json()
    if job["status"] in ("completed", "failed"):
        break
    time.sleep(5)
else:
    raise TimeoutError("Job still processing; resume polling with the same job ID")
if job["status"] == "failed":
    raise RuntimeError(job["error"])
result = job["result"]["files"][0]
r = requests.get(base + result["download_path"], headers=headers, timeout=180)
r.raise_for_status()
with open(result["name"], "wb") as output:
    output.write(r.content)

JavaScript / Node.js example

// Node.js 22+; server-side only.
import { readFile, writeFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';
const base = 'https://getskavio.com';
const auth = { Authorization: `Bearer ${process.env.SKAVIO_API_KEY}` };
async function json(path, options = {}) {
  const r = await fetch(base + path, { ...options,
    headers: { ...auth, ...options.headers } });
  const data = await r.json();
  if (!r.ok) throw new Error(JSON.stringify(data));
  return data;
}
const form = new FormData();
form.append('files', new Blob([await readFile('invoice.pdf')]), 'invoice.pdf');
const uploaded = await json('/v1/uploads', { method: 'POST', body: form });
const payload = { operation: 'flow-extract',
  upload_ids: [uploaded.uploads[0].id], fields: ['supplier','total'] };
const opts = { method: 'POST', headers: { 'Content-Type':'application/json' },
  body: JSON.stringify(payload) };
const quote = await json('/v1/estimate', opts);
payload.max_credits = quote.estimated_credits;
const idem = randomUUID(); // Persist before submission.
let job = await json('/v1/jobs', { ...opts,
  headers: { ...opts.headers, 'Idempotency-Key': idem },
  body: JSON.stringify(payload) });
while (!['completed','failed'].includes(job.status)) {
  await new Promise(resolve => setTimeout(resolve, 5000));
  job = await json('/v1/jobs/' + job.id);
}
if (job.status === 'failed') throw new Error(job.error);
const file = job.result.files[0];
const r = await fetch(base + file.download_path, { headers: auth });
if (!r.ok) throw new Error('Download failed');
await writeFile(file.name, Buffer.from(await r.arrayBuffer()));

API 1.9.0 — usage, limits and recovery

Track charged and reserved credits with GET /v1/usage and cursor-paginated /v1/usage/jobs. Credits and cash payments are separate. Budgets use the UTC calendar month when a job is admitted; reservations count toward the ceiling. Administrators configure company/key budgets and key expiry/permissions in Company workspace → Usage & limits.

Cancel only unstarted standalone queued jobs. For exhausted job, batch or bulk webhook deliveries, replay the original event with a stable Idempotency-Key. Event IDs and bodies stay unchanged; no processing or billing is repeated. Error responses add error_code, request_id and retryable. Provide X-Request-Id to support.

OpenAPI 1.9.0 · Company workspace

Workflow pipelines · API 1.9.0

Chain processing, human review and archive with POST /v1/workflow-runs. Supply pipeline or pipeline_id, upload_ids and max_credits. Poll GET with run ID. Maximum 20 steps and 50 open runs per company; Storage Fabric required.

Each process uses existing scheduling and step billing. The cap includes charged/reserved credits, not an upfront estimate. Completed steps do not repeat. Select file_name for multi-file output. Archive must be last.

Pause/resume/cancel/approve require Idempotency-Key. Accepted work finishes. Approval, cap/key changes and saved recipes require administrator sessions. Permanent step failure ends the run; branching and manual failed-step replay are unavailable. Poll run status.

{
  "pipeline": {"name": "Extract, review, archive", "steps": [
    {"id": "extract", "operation": "flow-extract"},
    {"id": "review", "kind": "review"},
    {"id": "archive", "kind": "archive", "retention_days": 365}
  ]},
  "upload_ids": ["UPLOAD_ID"],
  "max_credits": 100
}
Dashboard

API 1.9.0: bulk controls, notifications and SDKs

POST /v1/bulks/{bulk_id}/pause and POST /v1/bulks/{bulk_id}/cancel require a persisted Idempotency-Key. Pause prevents new admission while accepted work finishes. Cancel permanently prevents further work, preserves completed results and settles accepted jobs once. A cancelled bulk cannot resume. Another company cannot inspect or control the workload.

GET /v1/notifications, GET /v1/notifications/settings and PUT /v1/notifications/settings provide tenant-scoped notification history and settings. Notifications are disabled by default. Updating company settings requires an administrator web session, not a bearer API key. Email is available only when the production transport is configured; webhook/email delivery uses frozen events, deduplication and bounded retries. Text supports Czech, German and English.

Python and JavaScript SDK 1.1.0 cover all 92 operations in OpenAPI 1.9.0, including bulk pause/cancel and notification settings. JavaScript includes TypeScript definitions. Use SDKs on trusted servers: never expose API keys or administrator sessions in browser or mobile bundles. GET retries honor Retry-After; mutations are not automatically retried.

Versioning and changelog

v1.9.0: tenant-safe bulk pause/cancel, opt-in notifications and complete Python/JavaScript SDK contract parity.
v1.8.0: durable workflow pipelines, human review gates and per-run caps.
v1.5.0: paginated bulk manifests, bounded incremental batches, disk admission, durable scheduler/checkpoint recovery, authenticated output pages and failed-only retry.
v1.2.0: first-class batch estimate/submit/progress, grouped sources, partial-failure accounting, failed-item retry, batch webhooks and aggregate ZIP downloads.
v1.0.0: company wallet and scoped keys, asynchronous file/audio jobs, document extraction with saved workflows and XLSX/CSV/JSON export, estimates, authenticated downloads and developer contract.

The API lives under /v1. Clients should ignore unknown response fields. Incompatible changes require a new major API version or an announced migration. The OpenAPI document and capabilities endpoint describe the currently deployed release.