Skip to content

CPU Tools ​

FOTOhub runs a large family of file tools that need no AI model and no GPU: PDF merge, split, compress and conversion, image and video conversion, audio cutting and joining, QR codes and barcodes, print preparation, photo development, subtitles and translation, publishing, e-signature and more. They are cheap, billed per unit of work, and several have a daily free allowance.

There are two ways in:

SurfacePathsInputsBest for
Simple file tools/v1/documents/*, /v1/convert/*, /v1/codes/*, /v1/files/*Opaque file ids from POST /v1/files/upload-urlThe everyday conversions: start here
Full tool family/v1/cpu/*Storage references from POST /v1/cpu/inputs/upload-urlEverything else (about 200 routes)

Base URL: https://apis.fotohub.appAuthentication: Authorization: Bearer fh_live_your_api_key (see Authentication)

Prices

Prices are per unit of work (pages, minutes, files, codes) and are not fixed in this page. Every submit returns the quote it was charged at, the simple tools have free quote routes, and failed jobs are refunded automatically. API keys pay from the prepaid USD wallet.

Simple file tools ​

Endpoints ​

MethodPathChargedDescription
POST/v1/files/upload-urlnoA one-time upload link and a file id
PUT/v1/files/upload/{token}noSend the file's bytes (the link is the credential)
GET, HEAD/v1/files/download/{token}noDownload a result (the link is the credential)
POST/v1/documents/mergeyesMerge 2-20 PDFs
POST/v1/documents/splityesSplit one PDF
POST/v1/documents/compressyesCompress one PDF
POST/v1/documents/convertyesOffice to PDF, or PDF to Word / PowerPoint / Excel
POST/v1/documents/quotenoPrice of a documents operation
GET/v1/documents/jobs/{job_id}noA documents job
POST/v1/convert/imageyesConvert or compress 1-64 images
POST/v1/convert/videoyesConvert, compress, shrink to a target size, or make a GIF
POST/v1/convert/audioyesConvert, cut or join audio
POST/v1/convert/quotenoPrice of a convert operation
GET/v1/convert/jobs/{job_id}noA convert job
POST/v1/codes/qryesQR codes in bulk, QR label sheets, barcodes
POST/v1/codes/quotenoPrice of a codes operation
GET/v1/codes/jobs/{job_id}noA codes job

Keys. These routes take API keys only: a browser session or a storage bucket key gets 401 {"detail": {"error": "api_key_required", "message": "Use an API key (fh_live_...)."}}. Submits, quotes and upload-url need a key with write access; a read-only key gets 403 with {"detail": "This API key is read-only. Use a key created with write access, or the dashboard, to perform this action."}. Job reads work with any key.

Rate limits per API key, per minute: 30 submits and upload links (one shared bucket), 120 quotes, 240 job reads. Over the limit: 429 rate_limited, Retry-After: 60, Too many requests for this key. You were not charged.

1. Upload a file ​

bash
curl -X POST https://apis.fotohub.app/v1/files/upload-url \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "contract.pdf", "size": 1843221}'
FieldTypeDescription
namestring 1-200The file name; its extension decides the file kind
sizeinteger > 0Bytes. At most 50 MiB (52,428,800) through the API
json
{
  "file": {"id": "file_...", "name": "contract.pdf", "size": 1843221},
  "upload": {"url": "https://apis.fotohub.app/v1/files/upload/...", "method": "PUT", "expires_at": 1760002800, "max_bytes": 52428800}
}

Then PUT the raw bytes to upload.url before expires_at (Unix seconds, one to two hours ahead). Content-Length is required; the body may not be larger than the declared size. Answer: {"uploaded": true, "bytes": 1843221}.

Upload errorHTTP
file_too_large (max_bytes): A <kind> file can be at most 50 MB (52428800 bytes) through the API.413
too_many_pending_uploads (cap_urls, pending_urls, window_s, retry_after_s), upload_quota_exceeded (cap_bytes, outstanding_bytes, retry_after_s), upload_busy429
upload_unavailable, temporarily_unavailable503
PUT: file_not_found 404, link_expired 410, length_required 411, file_too_large 413, length_mismatch 400, upload_failed 502

Pass the file to a tool as {"id", "name", "size", "content_type"} in files (size is required; name and content_type are optional, up to 255 characters). A file id is bound to your account: another account's id is 404 file_not_found. Uploaded files that no job uses are deleted after 24 hours, and a job's inputs one hour after it finishes. Besides the 50 MiB API cap, each tool has its own input cap (for example 25 MiB for an Office file, 2 MiB for a CSV of codes).

2. Submit ​

Every submit is asynchronous and answers 200 with the job and its quote:

json
{
  "job": {"id": "a3e1c7d2-...", "type": "document.job", "operation": "merge", "status": "queued", "expires_at": "2026-10-10T09:00:00Z"},
  "created": true,
  "quote": {"credits": 0, "units": 1, "free": true, "free_remaining": 4, "free_daily": 5, "eta_s": 3}
}

quote holds units, the price (credits, and credits_full when part of the job was covered by the free allowance), free, free_units, free_remaining, free_daily and eta_s. The price is computed from the server's own reading of your files (pages, duration, rows), never from numbers you send.

Idempotency. Send Idempotency-Key (or X-Idempotency-Key). A retry with the same key returns the first job with "created": false and the full job view, and is not charged again. The key is not compared with the body: within one tool, reusing a key returns the first job whatever you send.

3. Poll and download ​

GET /v1/{documents|convert|codes}/jobs/{job_id}:

json
{
  "id": "a3e1c7d2-...",
  "type": "document.job",
  "operation": "merge",
  "status": "succeeded",
  "progress": 100,
  "eta_s": null,
  "charged_credits": 0,
  "created_at": "...", "started_at": "...", "finished_at": "...", "expires_at": "...",
  "refunded": false,
  "error": null,
  "result": {"assets": [{"name": "contract-merged.pdf", "content_type": "application/pdf", "bytes": 2210311, "url": "https://apis.fotohub.app/v1/files/download/..."}]}
}
  • status: queued, running, succeeded, failed, cancelled. progress is 0-100.
  • result.assets[].url is valid for one hour; read the job again for fresh links. The download streams the file as an attachment (Content-Disposition), with Cache-Control: private, no-store. Errors: 404 file_not_found, 410 link_expired, 502 download_unavailable.
  • A job that is not yours, of another tool family, or not a UUID is 404 job_not_found. The job record lives 24 hours (expires_at).
  • A failed job carries error: {code, message} and is refunded automatically (refunded: true, the message ends with Your credits were returned.). Codes include password_required, wrong_password, needs_ocr, page_limit, file_too_large, unsupported_format, corrupt_file, too_long, input_changed, exceeds_priced_limits, frame_rate_too_high, unsupported_or_unsafe, input_expired, input_rejected, queue_timeout, timeout, resource_limit; anything else reads The job could not be completed.

Quotes ​

POST /v1/documents/quote, /v1/convert/quote and /v1/codes/quote take the submit body and answer the price without creating a job:

json
{"operation": "merge", "available": true, "quote": {"credits": 0.5, "units": 1, "free": false, "free_remaining": 0, "free_daily": 5, "eta_s": 3}, "charged": false}
  • Documents: add "operation": "merge" | "split" | "compress" | "convert" (and to for convert; password is not accepted here).
  • Convert: add "type": "image" | "video" | "audio".
  • Codes: the same body as POST /v1/codes/qr.

available: false (with retry_after_s) means the queue cannot take the work right now. The quote reserves nothing: a submit is priced again from the files.

Documents ​

Merge, split, compress take {"files": [...], "options": {...}}. Merge takes 2-20 PDFs (Choose between 2 and 20 PDFs to merge.), split and compress exactly one (Choose one PDF.). Each PDF may have up to 2,000 pages (422 too_many_pages, max_pages); an unreadable file is 422 pdf_unreadable; a non-PDF content_type is 422 unsupported_type (This tool works on PDF files.).

OperationOptionsUnits
mergebookmarks (boolean, default false)1 per started 50 pages (all files together)
splitmode: ranges (default, with ranges such as "1-3,5"), every (with every: pages per file), size (with max_mb 0.1-50) or bookmarks1 per started 50 pages
compresslevel: low, recommended (default) or extreme1 per started 100 pages

options is at most 16 KiB of JSON; password fields are refused. An option value the tool cannot use fails the job (refunded).

Convert takes {"files": [one file], "to": "pdf" | "docx" | "pptx" | "xlsx", "password": "..."}:

toInputUnitsNotes
pdfdocx, doc, odt, rtf, xlsx, xls, ods, pptx, ppt, odp, txt (up to 25 MiB)1 per filepassword is refused
docxOne PDF, up to 50 pages1 per started 25 pagespassword: the PDF's open password (up to 128 characters; never stored with the job)
pptx, xlsxOne PDF, up to 50 pages1as above

Job errors specific to conversion: password_required, wrong_password, needs_ocr (a scanned PDF without a text layer).

Convert ​

Images (POST /v1/convert/image): {"files": [1-64 images], "operation": "convert" | "compress", "format": "same" | "jpg" | "jpeg" | "png" | "webp" | "avif" | "heic" | "gif" | "tiff" | "ico", "options": {...}}. Options include quality (1-100), colour_profile (srgb | keep), animated (WebP / GIF output), background (hex colour), resize ({width, height, fit}, each side 1-8192), and for compress a compress object (mode: quality, target_kb with target_kb 1-102400, or ssim with ssim 0.8-0.999). Metadata is always stripped. One input gives one file; several give images.zip. Units: 1 per started 10 images.

Video (POST /v1/convert/video): {"files": [one video], "operation": "convert" | "compress" | "target_size" | "gif", "options": {...}}. Options are checked before any charge (422 invalid_options):

operationOptions
convertformat (mp4 | webm), quality (low | medium | high), audio (keep | drop)
compressquality, max_height (0, 240, 360, 480, 720, 1080 or 1440), audio_kbps (48-192), audio
target_sizetarget: discord_8, discord_25, whatsapp_16, email_20, email_25 or custom (default) with target_mb 0.5-500; max_height; audio
gifstart_s (0-300), fps (5-30), width (120-1080), speed (0.25-4), ping_pong, reverse (not with ping_pong), duration_s (0.1-120; the GIF may last at most 30 s), loop (-1..100), dither (bayer | sierra | none), crop ({x, y, w, h}), caption_pos, optimise

Limits: source up to 30 minutes (422 too_long, max_seconds) and 3840x2160 (422 too_large_picture); GIF source up to 300 s. Units: per started output minute, x2 above 1080p and x4 above 1440p; target_size counts twice (two passes); a GIF is 1 unit (2 when it is longer than 15 s).

Audio (POST /v1/convert/audio): {"files": [...], "operation": "convert" | "cut" | "join", "format": "wav" | "mp3" | "flac" | "m4a" | "aac" | "ogg" | "opus", "bitrate_k": 32-320, "sample_rate": 8000 | 11025 | 12000 | 16000 | 22050 | 24000 | 32000 | 44100 | 48000, "channels": 1 | 2, "options": {...}}.

operationFilesOptionsUnits
convert1none (default format mp3)1 per file
cut1start_s, end_s (0-86400, at least one), fade_in_s, fade_out_s (0-30)1 per file
join2-20crossfade_s or gap_s (0-10, not both), fade_in_s, fade_out_s1 per started 30 minutes in total

Each file may last 30 minutes, a join 2 hours in total.

Codes ​

POST /v1/codes/qr:

FieldTypeDefaultDescription
operationbulk | sheet | barcodebulkbulk: many QR codes; sheet: a printable label sheet (always PDF); barcode: barcodes
symbologyqr, ean13, ean8, upca, code128, code39, itf, datamatrix, pdf417, aztecqr (code128 for barcode)bulk and sheet are QR only; barcode cannot be qr
formatsvg | png | pdfsvg
valuesstring[] (max 500, each 1-2048 bytes)Send values or one CSV in files, not both
files0-1 CSVUp to 2 MiB and 500 rows
templatestring (max 4096)The code content, with {column} placeholders over the CSV columns
filename_column, caption_columnstring (max 128)For inline values, only "data"
optionsobject{}Allowed keys: ec, size_mm, width_mm, height_mm, quiet, fg, bg, dpi, text, layout (for example {"ec": "Q", "size_mm": 30} or {"layout": {"preset": "avery_l7160"}})

Codes are static: nothing is shortened or tracked, and values / template are not stored with the job. One inline value gives one file; more, or any CSV job, gives qr-codes.zip / barcodes.zip; a sheet gives label-sheet.pdf. Units: 1 per started 100 codes.

Errors (simple tools) ​

All errors are {"detail": {...}} and state charged: false when nothing was taken:

HTTPerrorNotes
401api_key_requiredA browser session or bucket key
402insufficient_fundsrequired_usd, balance_usd, shortfall_usd, currency, topup_url
403not_availableThe tool is not enabled for your account; reason (tier_required adds min_tier)
404file_not_found, job_not_found
409units_mismatchA file changed after it was priced: ask for the price again
413file_too_largemax_bytes
422invalid_requestfields: [{field, problem}] for schema errors, or a message
422unsupported_type, pdf_unreadable, too_many_pages, invalid_options, too_long, too_large_picture
429rate_limited, ceiling_exceededretry_after_s, Retry-After
503not_availableThe tool is switched off right now; Retry-After: 300
503temporarily_unavailableNo capacity now; retry_after_s. You were not charged

Full tool family (/v1/cpu/*) ​

How every /v1/cpu/* tool works ​

Keys. Browser sessions and API keys both work. Every route that starts work or changes something needs a key with write access (403, This API key is read-only. Use a key created with write access, or the dashboard, to perform this action.); storage bucket keys are refused the same way. Reads (GET) work with read-only keys unless noted.

Availability. Each tool is enabled per account and some are rolled out in stages. A tool that is not enabled for you answers 403 with {"error": "feature_disabled", "reason": "..."} (reason: "tier_required" adds min_tier, the plan that unlocks it). A tool that is switched off or not yet priced answers 503 (not_priced or feature_off, This tool is not available yet. You were not charged., Retry-After: 300). Nothing is charged in either case.

Uploading inputs for CPU tools ​

/v1/cpu/* tools never fetch URLs. They take files from your FOTOhub storage as input_refs:

bash
curl -X POST https://apis.fotohub.app/v1/cpu/inputs/upload-url \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "talk.mp4", "size": 734003200}'
FieldTypeDescription
namestring 1-200File name
sizeinteger 1 to 4 GiBBytes
json
{"path": "cpu-inputs/<your user id>/cputier/9f2c...-talk.mp4", "bucket": "cpu-inputs", "object": "<your user id>/cputier/9f2c...-talk.mp4", "upload_url": "https://s1.fotohub.app/storage/v1/object/upload/sign/...", "expires_at": 1760006400}

Upload the bytes to upload_url before expires_at; the link creates one new object and never overwrites. POST /v1/cpu/inputs/confirm with {"object": "<object>"} (optional) answers {object, confirmed} and releases the pending-upload reservation. Limits: 60 pending upload links per hour and 20 GiB of unconsumed uploads (429 cpu_input_pending_urls_exceeded / cpu_input_quota_exceeded, with retry_after_s); 429 cpu_input_upload_busy (Retry-After: 2); 503 input_store_unavailable.

Then reference the file:

json
{"input_refs": [{"bucket": "cpu-inputs", "path": "cpu-inputs/<your user id>/cputier/9f2c...-talk.mp4", "name": "talk.mp4", "size": 734003200, "content_type": "video/mp4"}]}

path is <bucket>/<your user id>/... and its first segment must equal bucket. Files already in your photos, videos or audio storage can be referenced the same way. A path in someone else's folder is 403 input_not_owned (One of the files is not yours.); a ref larger than the tool's cap is 413 input_too_large (max_bytes). At most 64 refs per job unless a tool says otherwise. size is a hint: the server measures the files itself (pages, duration, rows) and prices from its own measurement.

Submit and the job ​

Bodies are JSON and strict: an unknown field is 422. A submit answers 200:

json
{"job_id": "5b0e...", "quote": {"units": 2, "credits": 0.2, "usd": 0.002, "free": false, "free_remaining": 0, "free_daily": 5, "eta_s": 6, "available": true}, "status": "queued", "expires_at": "2026-10-10T09:00:00Z"}

The numbers are an example. Work is charged at submit and refunded once, automatically, if the job fails or is cancelled before it starts. Many tools have a daily free allowance, shown in the quote as free_remaining / free_daily; a fully free job has free: true and costs nothing. The quote may carry more fields (ETA detail, internal identifiers): treat any field not listed here as opaque.

When the work cannot run right now the answer is still 200, with {"available": false, "reason": "...", "eta_s": ..., "quote": {...}, "charged": false}. Nothing is charged; retry later.

There is no separate quote route on most /v1/cpu/* tools: the price is the quote returned with the job, and a few tools expose a free unit or quota lookup (listed in the tables below). Idempotency-Key makes a retry return the original job with "duplicate": true and no second charge.

Follow the job with:

MethodPathDescription
GET/v1/cpu/jobs/{job_id}One job
GET/v1/cpu/jobs?status=&limit=Your jobs, newest first (limit 1-100, default 50)
GET/v1/cpu/jobs/{job_id}/eventsServer-Sent Events: the whole job first, then changed fields; keep-alive every 15 s; ends at a terminal status or after 600 s
POST/v1/cpu/jobs/{job_id}/cancelCancel. Refunded only if the job had not started (cancelled, refunded in the answer)

The job: {id, status, progress, outputs: [{name, url, content_type, bytes}], report, error_code, credits_charged, units, created_at, started_at, finished_at, expires_at, refunded} (plus an identifier of the tool, which you should treat as opaque). status is queued, running, succeeded, failed, cancelled or refunded. Output links are signed and valid for one hour; read the job again for fresh ones. report holds tool-specific results (for example per-file savings, a diff, a list of findings). A failed job has an error_code, for example password_required, wrong_password, needs_ocr, page_limit, file_too_large, unsupported_format, corrupt_file, too_long, input_changed (a file changed after it was priced), exceeds_priced_limits, no_face_found, timeout, input_rejected or processing_failed.

Errors ​

All errors are {"detail": {...}}; refusals before any charge say You were not charged. and carry charged: false.

HTTPerrorWhen
402insufficient_fundsWallet too low: required_usd, balance_usd, shortfall_usd, currency, topup_url
403feature_disabledNot enabled for your account (reason, min_tier)
403input_not_ownedA file is not in your storage
409units_mismatchA file changed after pricing (units): ask for the price again
413input_too_large, file_too_large, request_too_largemax_bytes
422invalid_request, invalid_options, unsupported_type, unsupported_format, unsupported_conversion, pdf_unreadable, password_protected, too_many_pages, too_long, too_many_rows, too_large_picture, op_disabled, ...The request or file cannot be processed; extra fields such as max_pages, max_seconds
429ceiling_exceededA concurrency, rate or daily spending ceiling (retry_after_s, Retry-After); fair_use_exceeded and rate_limited on some free tools
503not_priced, feature_offThe tool is not available now (Retry-After: 300)
503cpu_tier_unavailable, feature_check_unavailable, feature_degradedTemporarily unavailable; retry after Retry-After

Schema errors from the request model itself are FastAPI's standard 422 list.

Video, GIF and media — /v1/cpu/media/* ​

Body: {"input_refs": [...], "op": "...", "options": {...}, "caption": "..."}. op is required; unknown option keys are 422 invalid_options (Unknown option for <op>: ...). caption is one line of up to 80 characters, for video_to_gif, overlay and social_resize only. The server measures each video; source videos may last 30 minutes and be up to 3840x2160 (422 too_long, too_large_picture).

Routeop valuesUnits
POST /v1/cpu/media/gifvideo_to_gif, video_to_anim (animated WebP or silent WebM), gif_maker (2-64 images), gif_optimise, gif_resize, gif_crop, gif_reverse, gif_speed, gif_frames1 per GIF (2 when longer than 15 s; at most 30 s)
POST /v1/cpu/media/toolstrim, merge (2-20 videos), mute, extract_audio, replace_audio, frames, contact_sheet, thumbnail (no re-encode)1 per 500 MB of video
POST /v1/cpu/media/edittrim_exact, merge_reencode, rotate, flip, crop (re-encode)per output minute x resolution factor
POST /v1/cpu/media/compresscompress, target_size, convert, speed, slowmo, loop, reverse, social_resize, overlay, burn_subsper output minute x resolution factor (x2 for target_size)

The resolution factor is 1 up to 1080p (short side), 2 up to 1440p and 4 above. Main options:

opOptions
video_to_gifstart_s 0-300, fps 5-30, width 120-1080, speed 0.25-4, ping_pong, reverse (not both), duration_s 0.1-120 (GIF at most 30 s), loop -1..100, dither (bayer | sierra | none), crop {x, y, w, h}, caption_pos (top | middle | bottom), optimise
video_to_animformat (webp | webm), start_s, fps 1-30, width 64-1280, duration_s 0.1-30, loop, quality 1-100
gif_makerwidth, fit (contain | cover), background, delays_ms (one per image, 20-10000) or delay_ms, loop, dither
gif_optimise / gif_resize / gif_speed / gif_frameslossy 0-200, colors 2-256, level 1-3 / width, height 16-1080 / speed 0.25-4 / every 1-100
trim, trim_exactstart_s, and end_s or duration_s
extract_audioformat (m4a | mp3 | wav | flac)
replace_audioFiles: video then audio. mode (replace | add), volume, orig_volume 0-2
framesevery_s 0.1-3600 or count 1-200, format (jpg | png), width 64-1920 (ZIP output)
contact_sheet / thumbnailcount 4-48, cols 2-8, thumb_w 120-480, timestamps / at_s, format, width
merge_reencodewidth 64-3840 and height 64-2160 together, fps 10-60
rotate / flip / cropangle 90|180|270 / direction (horizontal | vertical | both) / crop {x, y, w, h}
compressquality (low | medium | high), max_height (0, 240, 360, 480, 720, 1080, 1440), audio_kbps 48-192, audio (keep | drop)
target_sizetarget (discord_8, discord_25, whatsapp_16, email_20, email_25, custom with target_mb 0.5-500), max_height, audio
convertformat (mp4 | webm), quality, audio
speed / slowmo / loopfactor 0.25-4 / factor 1.25-8 / count 2-20
social_resizeaspect (9:16, 1:1, 4:5, 16:9), mode (blur | crop | fit), res (720 | 1080), focus_x, focus_y 0-1
overlaycaption and/or logo: true (the last input is the logo, up to 25 MB): caption_pos, caption_color, caption_size_pct 2-12, logo_pos, logo_scale_pct 5-50, logo_margin_pct, logo_opacity 0.1-1, start_s, end_s
burn_subsFiles: video then an SRT / WebVTT / ASS file (up to 2 MB). size (small | medium | large), position (bottom | top)

PDF — /v1/cpu/pdf/* ​

The server counts the pages itself. Units are per started block of pages: edit 50 (merge: all files together; images_pdf: one per image), compress 100, render 20 (selected pages), finish 50, compare 50 (the longer file), ocr 10, forms 50, repair 50. Page ceilings: 2,000 per file for edit, compress, finish and repair; 300 for render and compare; 100 for ocr; 500 for forms.

POST /v1/cpu/pdf/edit — {"input_refs", "op", "options", "password", "user_password", "owner_password"} (password fields up to 128 characters, never stored):

opInputsOptions
merge2-20 PDFsbookmarks
split1 PDFmode: ranges (with ranges, e.g. "1-3,5"), every, size (max_mb 0.1-50), bookmarks
extract, delete1 PDFpages (required)
rotate1 PDFangle (90, 180, 270, -90), pages
reorder / organise1 PDForder (page numbers) / map ([{page, rotate}])
protect1 PDFtop-level user_password (required), owner_password; permissions {print, copy, modify, annotate}
unlock1 PDFtop-level password
watermark1 PDF (+ 1 image for kind: "image")kind, layout (single | tile), position, opacity 0.05-1, rotation, pages, text (up to 120 chars), size, color, scale
page_numbers1 PDFposition, format (n, n_of_total, page_n, page_n_of_total, dash_n), start, size, margin_mm, pages, skip_first, color
crop1 PDFmode: "margins" with margins (mm) or mode: "box" with box (fractions); pages
resize / nup1 PDFsize (a4 | letter), orientation / per_sheet (2, 4, 6, 9), margin_mm, gap_mm
images_pdf1-100 imagespage_size (a4 | letter | fit), orientation, margin_mm
metadata1 PDFclear, title, author, subject, keywords
RouteBody / options
POST /v1/cpu/pdf/compressoptions.level: low, recommended (default), extreme. Never larger than the input
POST /v1/cpu/pdf/renderop: render (default) or thumbs (128 px); options: format (png | jpg | webp), dpi 72-300 (150), pages
POST /v1/cpu/pdf/finishop: sign (placements: up to 50 stamps, an image input or text, positioned in page fractions), redact (rects and/or terms, case_sensitive; the result is checked and the job fails if text survives), flatten (annotations, forms), pdfa (PDF/A-2b)
POST /v1/cpu/pdf/compareExactly 2 PDFs; options: dpi 50-150, sensitivity 0.01-0.5. Outputs a text diff (JSON) and a visual diff PDF
POST /v1/cpu/pdf/ocr{"input_refs": [1 PDF], "languages": ["pl", "en"]} (1-3 of pl, en, de). Outputs a searchable PDF
POST /v1/cpu/pdf/forms1 PDF; op: detect (include_existing, min_confidence), create (fields: up to 5,000 of {kind: text|checkbox|radio|dropdown|date|signature, name, page, rect, ...}, remove), fill (values: name -> value, flatten), export, flatten. Field values are not stored with the job
POST /v1/cpu/pdf/repair{"input_refs": [1 PDF]}. A damaged file is priced from its size (at most 10 units) and the unused part is refunded; outputs the repaired PDF and a report. Nothing recoverable: unrecoverable, refunded

Images — /v1/cpu/images/* ​

POST /v1/cpu/images/convert — {"input_refs": [1-64 images], "op", "format", "options"}. Units: 1 per started 10 images. Up to 120 megapixels per image; metadata is always stripped. One input gives one file, several give images.zip.

opOptions
convertformat (same, jpg, jpeg, png, webp, avif, heic, gif, tiff, ico), quality 1-100, colour_profile (srgb | keep), animated, background, resize {width, height, fit}, ico_sizes
compresscompress {mode: quality | target_kb | ssim, target_kb, ssim}; the report gives saved_percent per file
renamepattern (1-120 chars) with {name}, {n}, {n:03}, {date}, {w}, {h}, {ext}
socialpreset: instagram-square, instagram-portrait, instagram-story, facebook-post, x-post, linkedin-post, youtube-thumbnail, pinterest-pin, whatsapp-status, whatsapp-profile, favicon-32, favicon-180
rotate, flip, cropangle / flip (h | v | both) / crop {x, y, width, height} or {aspect, gravity}
circle, rounded, border, shadowradius_px or radius_pct / border {width, color} / shadow {dx, dy, blur, color, opacity} (PNG or WebP output)
icc, stripConvert to sRGB / remove metadata
svg_png, svg_optimisesvg_width 16-8192, svg_dpi 36-600 / none

POST /v1/cpu/images/sets — op: favicon and app_icons (1 source image; app_name, theme_color, background_color), sprite (1-64 images; layout, padding, max_size, columns, prefix), og_card (0-1 logo; title required, subtitle, colours). 1 unit.

Documents — /v1/cpu/docs/* ​

RouteBodyUnits
POST /v1/cpu/docs/office-pdf{"input_refs": [1 file]}: docx, doc, odt, rtf, xlsx, xls, ods, pptx, ppt, odp, txt, up to 25 MB1
POST /v1/cpu/docs/pdf-office{"input_refs": [1 PDF], "op": "docx" | "pptx" | "xlsx", "password"}; up to 50 pagesWord: 1 per started 25 pages; PowerPoint / Excel: per 50
POST /v1/cpu/docs/convert{"input_refs": [1 file], "to": "pdf" | "epub" | "md" | "txt" | "docx" | "rtf", "title", "toc", "page_size", "csv_header", "csv_delimiter"}1

/v1/cpu/docs/convert detects the source from the file's bytes: Markdown -> pdf, epub, docx, rtf, txt; text -> epub, md, docx, rtf; RTF -> epub, md, txt, docx; CSV -> pdf, md; EPUB -> pdf, md, txt, docx; Apple Pages -> pdf, docx, epub, md, txt; PDF -> epub, md, txt. Any other pair is 422 unsupported_conversion with supported and, where another route fits, use. Limits: Markdown and text 5 MB, CSV and RTF 20 MB (CSV at most 10,000 rows and 100 columns), EPUB, Pages and PDF 50 MB (PDF at most 500 pages).

Bulk variants — /v1/cpu/bulk ​

Render one design scene once per row of a data file (CSV, XLSX or JSON, up to 10,000 rows) into one ZIP. For templates managed by the API, POST /v1/render/bulk is the simpler entry point; this route takes the files directly.

POST /v1/cpu/bulk:

FieldTypeDefaultDescription
input_refsarrayIn order: the scene .json (up to 4 MB), the data file (up to 50 MB), then each asset in the order of assets
rowsinteger 1-10,000Required. The number of rows you are paying for
feed_namestringRequired. The data file's name, ending in .csv, .xlsx or .json
mappingobjectRequired. Which scene object and property each column fills (at most 32 KB)
bundle_hashstring (64 hex)Required. From GET /v1/cpu/bulk/bundle-hash
formatpng | jpeg | webp | pdfpng
scalenumber 0.1-81.0
transparentbooleanfalsepng or webp only
qualityinteger 1-10090
assetsstring[] (max 64)[]Unique plain file names
image_hostsstring[] (max 20)[]Host names image cells may be fetched from
pdf_mergebooleanfalsepdf only
name_columnstring 1-128The column that names each file
webhook_urlstring (max 512)Signed webhook when the job settles

Billing is per started block of 100 rows, charged at submit; blocks that did not render are refunded when the job finishes (at least one block is kept), and a job where no row renders is refunded in full. A file with more rows than rows fails the job (input_rejected, refunded). Limits: 20 bulk jobs per hour (429 rate_limited, Retry-After: 600) and 3 running at once.

MethodPathDescription
GET/v1/cpu/bulk/{job_id}The job, plus blocks: {charged, billed, refunded} and webhook delivery state
GET/v1/cpu/bulk/{job_id}/download{url, expires_in: 604800}: a 7-day link to the ZIP (409 not_ready before success)
GET/v1/cpu/bulk/bundle-hash{bundle_hash}
GET/v1/cpu/bulk/webhook-secretThe signing secret (write key)
POST/v1/cpu/bulk/webhook-secret/rotateA new secret generation (write key)
GET/v1/cpu/bulk/webhooks/deadUp to 50 deliveries that gave up
POST/v1/cpu/bulk/{job_id}/webhook/retryRe-arm a dead delivery (409 nothing_to_retry otherwise)

Webhooks are the bulk.completed / bulk.failed events, signed with a timestamp, retried for 24 hours; the format and verification are described under Render API webhooks. Payload: event, job_id, status, error_code, refunded, and on success rows, ok, failed, blocks_charged, blocks_billed, download.

Other tools ​

Compact list; every charged route follows the contract above (write key, input_refs, 200 with job_id and quote, poll GET /v1/cpu/jobs/{id}). Free routes answer directly and charge nothing. Where a tool has more options than shown, the request model refuses unknown fields with 422, so the error tells you what is accepted.

Audio ​

MethodPathWhat it doesMain fields
POST/v1/cpu/audiotools/toolscut, convert, join, fade, speed, pitch, silence, gain (incl. loudness presets), eq, channels, metadataop, format, bitrate_k 32-320, sample_rate, channels, options; per 30 min of output, files up to 30 min
POST/v1/cpu/audiotools/waveformAudiogram video, waveform PNG or peaks JSONop (audiogram | waveform_png | peaks), srt (audiogram), options; audio up to 10 min
POST/v1/cpu/asr/transcribeTranscription to txt / srt / vtt / jsonduration_s (max 7200), language, formats; per started 10 min
POST/v1/cpu/audio/enhanceVoice clean-up and loudness presetduration_s (max 3600), preset (spotify, youtube, podcast, broadcast, none), denoise, strength, format
POST/v1/cpu/audio/repairHum, clicks, clipping, noise, reverb, breaths, loudnessmodules, format; up to 60 min
POST/v1/cpu/audio/repair/previewFree. Before/after excerpt (10-30 s)modules, start_s, length_s; 20 a day
POST/v1/cpu/audio/bleepBleep or mute chosen wordstranscript ([{t, s, e}]), languages, custom_words, style; up to 60 min
POST/v1/cpu/audio/bleep/detectFree. Which words would be replacedas above
POST/v1/cpu/audio/midiAudio to MIDI plus tempo, key and chordsops, mode, quantize, sensitivity; up to 10 min
POST/v1/cpu/audio/switchMulti-camera podcast cut list (EDL) from 2-8 audio anglesangle_ids, layout, min_shot_s, diarization, ...
POST/v1/cpu/master/runReference masteringInputs: track, reference; duration_s (max 600), format
POST/v1/cpu/midi/renderRender a .mid to audioduration_s (max 1800), soundfont, format
POST/v1/cpu/podcast/mixMix up to 8 tracks with ducking and loudnesstracks ({ref, role, gain_db, offset_s, duration_s}), preset, format, duck_amount_db; per started 30 min of the longest track

Video ​

MethodPathWhat it doesMain fields
POST/v1/cpu/video/transcodeDelivery encode (AV1, HEVC, H.264, ProRes)codec, quality, preset (youtube, instagram, tiktok, broadcast, web-av1, archive-prores), duration_s, width, height, captions_srt; per output minute
POST/v1/cpu/video/hlsAdaptive streaming ladder (3-5 renditions)duration_s, width, height; per minute
GET/v1/cpu/video/hls/playlist?path=Free. The playlist with signed segment links
GET/v1/cpu/video/hls/sign?path=Free. One fresh signed link
DELETE/v1/cpu/video/hls/ladder?path=Delete a ladder you made
POST/v1/cpu/video/repairDeinterlace, lens, stabilise, deflicker, denoise, deband, frame ratelevel (standard | strong), modules; up to 30 min, 4 GB
POST/v1/cpu/video/repair/previewFree. 5-second before/afterlevel, modules, start_s; 20 a day
POST/v1/cpu/video/thumbsPick the best thumbnail frames, with crop packstop_n 1-12; per started 10 min
GET/v1/cpu/video/thumbs/units?duration_s=Free. {units}
POST/v1/cpu/video/parallax2.5D camera move from one photomotion, duration_s 1-30, fps, resolution, formats, layers
GET/v1/cpu/video/parallax/unitsFree. {units, frames, allowed_formats, max_layers}duration_s, fps, resolution
POST/v1/cpu/video/proxyFree. Editing proxy for a library videofile_id
POST/v1/cpu/color/tonemapHDR to SDRduration_s, width, height, fps, tonemap
POST/v1/cpu/vedit/analyzeSilences, filler words, scene cuts, chaptersduration_s (max 7200), language, words, ...
POST/v1/cpu/vedit/chaptersFree. Chapters from a word listwords, language, max_chapters, min_chapter_s
POST/v1/cpu/anim/renderAnimated design export (MP4, WebM, GIF, APNG)bundle_hash, format, fps, duration_s, scale, ...
POST/v1/cpu/lottie/renderLottie to video or animated imageduration_s (max 30), format, fps, width, height

Photo ​

MethodPathWhat it doesMain fields
POST/v1/cpu/photo/hdrMerge 2-7 exposuresalign, deghost, format, quality
POST/v1/cpu/photo/panoStitch 2-12 photosprojection, crop, fov_deg
POST/v1/cpu/photo/focusFocus-stack 2-20 photosalign
POST/v1/cpu/raw/develop, /v1/cpu/raw/photoDevelop RAW files (1-64, or exactly 1)preset, look, format
POST/v1/cpu/photo/recipeApply a saved edit to 1-64 photosrecipe, bundle_hash, format, zip
POST/v1/cpu/photo/lensLens correction and uprightdistortion, vignetting, tca, upright, guides
POST/v1/cpu/photo/portraitPortrait retouchsliders smooth, blemish, eyes, teeth, ...
POST/v1/cpu/photo/skySky replacementsky, sensitivity, horizon_blend, ...
POST/v1/cpu/photo/cullRate 1-500 photos for sharpness, exposure and blinksblur_min, burst_gap_s, refresh
POST/v1/cpu/photo/contact-sheetContact sheet or proof book PDFmode, preset, page, captions, ...
POST/v1/cpu/creative/collageCollage of 2-20 imageslayout, width, height, gap, background, format
POST/v1/cpu/creative/effectsDuotone, halftone, pixel, ASCIIop, options
POST/v1/cpu/creative/mockupDevice and print mockupsframe, tone, pose, aspect
POST/v1/cpu/creative/photo-idID / passport photo and print sheetop, preset (country), background, sheet
GET/v1/cpu/creative/opsFree. Which creative tools are on
POST/v1/cpu/privacy/detect, /v1/cpu/privacy/blurDetect and blur faces and number plates in images or a video (no identification)mode (image | video), faces, plates, blur, strength
POST/v1/cpu/color/lutApply a .cube LUT to an imageformat
POST/v1/cpu/color/ocioColour-space conversionsrc, dst or display + view
GET/v1/cpu/color/ocio/spacesFree. The available colour spaces
POST/v1/cpu/luts/upload-url, /finalize, /read-url, /deleteFree. Your LUT library
POST/v1/cpu/meta/iptcWrite IPTC/XMP into copies of up to 100 photos, or a stock-agency CSVdefaults, files, csv, stock
POST/v1/cpu/meta/iptc/prefillFree. Captions and keywords from your libraryasset_ids
POST/v1/cpu/watermark/embed, /embed-audio, /embed-videoInvisible watermark in an image, audio or videoformat, duration_s
POST/v1/cpu/watermark/verifyFree. Check a file for the watermark (raw file body)?media=audio|video

Design, print and 3D ​

MethodPathWhat it doesMain fields
POST/v1/cpu/codes/renderQR codes, label sheets and barcodes from /v1/cpu/* inputs (same options as /v1/codes/qr)op, symbology, values or a CSV
POST/v1/cpu/vector/cutfileCut files for plotters and lasers (DXF, SVG, PLT, EPS, PDF) from SVG / DXF / DWGformats, size_mm, kerf_mm, bridges, nest, ...
POST/v1/cpu/vectorizeImage to SVG (and PDF)width, height, mode, detail, colors
POST/v1/cpu/sticker/renderSticker, sticker sheet or messenger packop, border_px, paper, mode
POST/v1/cpu/print/preflightPrint preflight reportpages, profile, bleed_mm
POST/v1/cpu/print/pdfxPrint-ready PDF/Xpages, profile, intent, spots
POST/v1/cpu/print/softproofFree. Soft proof and gamut warningprofile, paper
POST/v1/cpu/print/imposeImposition (n-up, booklet, cut-and-stack)pages, plan
POST/v1/cpu/print/vdpVariable-data print from a PDF template and a CSVrecords, fields, output
POST/v1/cpu/scan/pdfDocument scan from 1-64 photoscolour, page_size, pages
POST/v1/cpu/scan/detectFree. Page corners in 1-8 photos
POST/v1/cpu/pptx/export, /v1/cpu/pptx/importDesigns to PowerPoint, and backtitle, notes / thumbnails
POST/v1/cpu/importDesign file (PSD, PDF, AI, EPS, SVG, IDML, Sketch) to editable layersname, page
PUT, GET, DELETE/v1/cpu/import/figma/tokenYour Figma personal access tokentoken
POST/v1/cpu/import/figma/framesFree. Frames of a Figma fileurl
POST/v1/cpu/import/figmaImport Figma framesurl, frame_ids
POST/v1/cpu/export/layeredDesign to layered PSD / SVG / PDFformats, artboard
POST/v1/cpu/html5-banner, /v1/cpu/html5-banner/validateHTML5 ad banners, and a validatorartboards, networks / size
POST/v1/cpu/magic-resizeSmart crop boxes per target formatformats, layer_ids
POST/v1/cpu/attentionPredicted-attention heatmap and CTA scorecta or scene
POST/v1/cpu/font-idClosest fonts for text in an imagebox
POST/v1/cpu/fonts/inspect, /v1/cpu/fonts/subsetFree. Font report; WOFF2 subsetcharsets, text, axes
DELETE/v1/cpu/fonts/brand/{brand_id}/{font_id}Free. Remove a brand font
POST/v1/cpu/brand-urlPalette, fonts, logo and tone from a websiteurl, palette_size
POST/v1/cpu/brand-check/scanFree. Check a design against brand and accessibility rulesscene, brand
POST/v1/cpu/brand-check/filePDF accessibility or video flash/caption check, with fixfix, lang, title
POST/v1/cpu/mesh/optimize, /v1/cpu/mesh/convertOptimise or convert a 3D model (GLB, glTF, OBJ, FBX, STL, PLY, STEP)target_ratio, format
POST/v1/cpu/turntable/renderTurntable video of a 3D modelframes, fps, width, height, hdri
POST/v1/cpu/splatGaussian-splat PLY to compressed formats and a viewersplat_count, formats, sh_bands

Text and translation ​

MethodPathWhat it doesMain fields
GET/v1/cpu/localize/languagesFree. The 30 supported languages
POST/v1/cpu/localize/translateTranslate text segmentssource, target, segments (max 500; at most 1,000 words and 12,000 characters in total), glossary, do_not_translate
POST/v1/cpu/localize/subtitlesTranslate SRT / VTTsource, target, content
POST/v1/cpu/localize/posteditAI tone edit of a translation (synchronous)tone, segments (max 100)
POST/v1/cpu/localize/fitFree. Re-fit translated text into its boxesboxes (max 500)
POST/v1/cpu/images/translate/detectFree. Text boxes in an image, with a draft translationtarget, source
POST/v1/cpu/images/translateReplace the text in an imagetarget, boxes
POST/v1/cpu/ocrText from images or PDFs (up to 10 pages)pages

Commerce ​

MethodPathWhat it doesMain fields
POST/v1/cpu/ecom-packStore image pack per marketplacemarketplaces, formats, products
POST/v1/cpu/ecom/complyMarketplace image compliance check or fixmarketplace, mode
POST/v1/cpu/ecom-pushFree. Push a pack to a connected shop (dry_run is the default)job_id, connection_id, confirm_shop
GET/v1/cpu/ecom-push/statusFree. State of a pushjob_id, connection_id, push_id

Library, publishing, e-signature and projects ​

AreaRoutesNotes
Library search and curationPOST /v1/cpu/dam/search, /search/semantic; GET, POST /collections, PATCH, DELETE /collections/{id}, GET /collections/{id}/assets; POST /dedupe, GET /dedupe/status, GET /dedupe/clusters, POST /dedupe/clusters/{id}/dismiss and /reopen; GET /library; GET /index/status, POST /index/backfillAll under /v1/cpu/dam. Free, except indexing beyond the monthly free allowance (402 backfill_exceeds_free_allowance with a quote)
Captions and rightsPOST /v1/cpu/dam/caption (charged), POST /caption/apply; GET, PUT /assets/{id}/caption; GET, PUT /assets/{id}/rights, GET /assets/{id}/rights/export; POST /rights/lookupUnder /v1/cpu/dam. Your own edits are never overwritten
PeopleGET /v1/cpu/people, /search, /photos, /{id}/photos, /export; GET, PUT /settings; PATCH /{id}; POST /{id}/merge, /{id}/split; DELETE /dataFace grouping of your library. It is on by default; PUT /settings with {"enabled": false} switches it off and DELETE /v1/cpu/people/data deletes all face data
Published pagesGET /v1/cpu/publish/quote, /quota, /slug; POST /page; GET /pages, /pages/{id}; POST /pages/{id}/update, /unpublish; DELETE /pages/{id}; POST /export, GET /export/{job_id}Under /v1/cpu/publish. Free, limited by a per-plan page quota (402 page_quota_reached)
PodcastsGET /v1/cpu/podcast/quote, /quota, /categories, /slug; GET, POST /shows, GET, PATCH, DELETE /shows/{id}, POST /shows/{id}/artwork, POST /shows/{id}/episodes; GET, PATCH, DELETE /episodes/{id}, POST /episodes/{id}/publish, /unpublish; GET /jobs/{job_id}; POST /validateUnder /v1/cpu/podcast. Hosted RSS feeds; free, limited by a per-plan quota
E-signatureGET /v1/cpu/esign/limits; GET, POST /envelopes; GET, PUT, DELETE /envelopes/{id}; POST /send (charged per envelope); POST /envelopes/{id}/cancel, /remind; GET /envelopes/{id}/downloadUnder /v1/cpu/esign. Up to 10 signers and 60 fields; a cancelled or expired envelope that nobody opened is refunded. Signers use the emailed link, not the API
BundlesPOST /v1/cpu/bundle, GET /v1/cpu/bundle/{bundle_id}1-5 small jobs billed together (at least the bundle minimum, never more than separately)
Version historyGET, POST /v1/cpu/versions/{editor}/{project_id}; GET /compare; GET, DELETE /{version_id}; POST /{version_id}/name, /snapshot, /branch, /restoreeditor is video, canvas or audio. Free
PluginsGET /v1/cpu/plugins; POST /drafts; DELETE /versions/{id}, POST /versions/{id}/submit; GET, POST /installs, DELETE /installs/{id}, DELETE /installs/{id}/grants/{capability}; POST /run (charged)Under /v1/cpu/plugins. Sandboxed plugins; installing needs consent: true and per-capability grants

Not part of the public API ​

These routes exist under the same prefixes but are not for API keys, and are not documented here:

  • Administration and plugin review (/v1/aiwave/admin/cpu/*): staff sessions only.
  • Service-to-service routes: /v1/cpu/audio/relay, /v1/cpu/ocr/relay, /v1/cpu/video/encode-handoff*, /v1/cpu/jobs/{id}/io.
  • Link-token routes used by public pages: the signer pages (/v1/esign/*), the shop plugin acknowledgement (/v1/cpu/ecom-push/ack), share links (/v1/cpu/video/hls/share/*, /v1/cpu/splat/viewer/*).
  • The free web tools behind fotohub.app/tools (/v1/tools/guest/*): browser-only, no API key.
  • /v1/cpu/photogrammetry/scan and /align exist but have no price yet, so they are refused (503 not_priced, or 403 feature_disabled) and nothing is charged.