The API.
Convert files from your code. Simple REST, auth with one header, response in JSON.
Free: 250 credits/month, 30 requests/minute — enough to build and evaluate.
Pro (€9.99): 3,000 credits/month, 120 requests/minute, files up to 500 MB.
Business (€49.99): 25,000 credits/month, 750 requests/minute, files up to 5 GB, plus signed-URL outputs (7-day retention).
Pay-as-you-go: add a card and keep converting past your included credits, billed monthly at €5 per 1,000 credits (available on every plan except Enterprise). Enterprise (contact sales) adds custom volume and a mutual NDA. See pricing.
Quickstart
The whole loop in under a minute — sign up free, grab a key, convert.
- Get a key. Sign up (free), verify your email, then open My Account → Generate API key. Free includes 250 credits/month — no card needed to start.
- Convert a file — one request, returns a job id:
curl -X POST https://formatly.pro/api/v1/convert \ -H "Authorization: Bearer ck_live_..." \ -F "file=@photo.heic" \ -F "to=jpg" # → { "jobId": "123", "creditCost": 3, "creditsRemaining": 247 }
- Poll the job, then download the result (links are single-mint, valid 15 min):
curl https://formatly.pro/api/v1/jobs/123 \ -H "Authorization: Bearer ck_live_..." # → { "status": "done", "downloadUrl": "https://…", "expiresIn": 900 } curl -L -o out.jpg "<downloadUrl>"
That's it. Every response includes your remaining credits. Full reference is below, with Node and Python snippets further down.
Authentication
All API requests require an API key passed via the Authorization header:
Authorization: Bearer ck_live_xxxxxxxxxxxxxxxxxxxxxxxx
Generate your key from the My Account panel (any verified account — Free included). Treat it like a password — anyone with your key can convert files on your behalf. The full key is shown only once at generation; store it securely.
Base URL
https://formatly.pro/api/v1
Endpoints
Upload a file and start a conversion in one request. Returns a job ID you can poll.
Request
Send a multipart/form-data request with two fields:
| Field | Type | Description |
|---|---|---|
| file | file | The file to convert. Max size scales by plan — 25 MB (Free) up to 5 GB (Business/Enterprise). The API is self-serve on every plan. |
| to | string | Output format (e.g. jpg, mp3, pdf). For transform tools (see op below) the output keeps the input format, so set to equal to the input extension. |
| op | string | Optional. Run a transform tool instead of a format conversion. One of clean-metadata, compress, resize, compress-video, blur-faces, remove-bg, ocr, remove-watermark, redact, repair-pdf, repair-csv, repair-office, rotate-pdf, split-pdf, grayscale, invert, sepia, compress-pdf, pdf-to-text, extract-images, extract-pages, delete-pages, protect-pdf, unlock-pdf, mute-video, trim-media, normalize-audio. When set, the input and output formats must match. |
| quality | integer | Optional. 10–95. Applies to compress (images) and compress-video. Lower = smaller file, higher = better quality. Defaults to a balanced value if omitted. |
| redact | JSON string | Required when op=redact. A JSON object describing what to black out — see the redaction example below. |
| angle | integer | Required when op=rotate-pdf. Clockwise rotation applied to every page: 90, 180 or 270. |
| split | JSON string | Required when op=split-pdf. {"mode":"pages"} for one PDF per page, or {"mode":"ranges","ranges":"1-3,4,5-8"} for custom page ranges. The result is a ZIP of the resulting PDFs. |
| pages | string | Required when op=extract-pages or delete-pages. 1-based page numbers/ranges, e.g. 1-3, 5, 8-10. extract-pages keeps them; delete-pages removes them. |
| password | string | Required when op=protect-pdf or unlock-pdf. 1–128 characters, used transiently and never stored. For unlock-pdf it must be the PDF's current password. |
| trim | JSON string | Required when op=trim-media. {"start":12,"end":45} — start and end in seconds (end after start). Lossless stream-copy. |
| resize | JSON string | Required when op=resize. {"width":1080,"height":1080,"fit":"cover"} — width/height in pixels (1–10000, at least one required); fit is one of cover (crop to fill), contain (fit with padding), inside (shrink to fit) or fill (stretch). |
| lang | string | Optional, for op=ocr. Document language — one of eng, fra, deu, jpn, or up to three joined with + (e.g. eng+fra). Defaults to eng. Picking the document's real language dramatically improves recognition accuracy. |
Transform tools PRO & UP
The same tools available in the web app can be driven through the API by adding an op field. Each is a transform-in-place op, so the file's format is preserved (ocr, remove-watermark, redact and repair-pdf are PDF-only and return a PDF; blur-faces and remove-bg return a PNG).
| op | Input | What it does |
|---|---|---|
clean-metadata | Images, audio, video, PDF, Office | Strip EXIF, GPS, author and other hidden metadata. |
compress | JPG, PNG, WEBP | Re-encode at a target quality to reduce file size. |
resize | JPG, PNG, WEBP, GIF, TIFF, AVIF | Resize and/or crop to a target size (set resize). Returns the same format. |
compress-video | MP4, MOV, AVI, WEBM | Re-encode (H.264, or VP9 for WEBM) at a target quality. |
blur-faces | Images | Detect faces and replace them with a pixel mosaic. Returns PNG. |
remove-bg | JPG, PNG, WEBP, BMP, HEIC | Remove the background (segmentation runs on our servers — the image never goes to a third party). Returns a transparent PNG. |
ocr | Add an invisible searchable text layer to a scanned PDF. | |
remove-watermark | Remove annotation/layer-based watermarks from PDFs you authored. | |
redact | Permanently delete matching text from the file (not just cover it). Rule-based — no AI. Requires a redact spec. | |
repair-pdf | Recover a damaged/corrupted PDF by rebuilding its structure; also strips auto-run scripts. Best-effort, rule-based — no AI. | |
repair-csv | CSV | Normalise a malformed CSV: fix encoding (→ UTF-8), delimiter (→ comma), line endings and quoting. Content preserved. Rule-based — no AI. |
repair-office | DOCX, XLSX, PPTX | Recover a damaged Office file by re-saving it with LibreOffice's repair-tolerant import. Best-effort, rule-based — no AI. |
rotate-pdf | Rotate every page by 90/180/270° (set angle). No quality loss — the page is rotated in place. Returns a PDF. | |
split-pdf | Split into one PDF per page, or per custom range (set split). Returns a ZIP of the resulting PDFs. | |
grayscale | JPG, PNG, WEBP, GIF, TIFF, AVIF | Convert the image to greyscale. Returns the same format. |
sepia | JPG, PNG, WEBP, GIF, TIFF, AVIF | Apply a warm sepia tone. Returns the same format. |
invert | JPG, PNG, WEBP, GIF, TIFF, AVIF | Invert the image colours (photo negative). Returns the same format. |
compress-pdf | Shrink a PDF by recompressing its streams/images. Returns a PDF. | |
pdf-to-text | Extract the embedded text layer. Returns a .txt file. | |
extract-images | Pull out every embedded raster image. Returns a ZIP. | |
extract-pages | Keep only the pages listed in pages. Returns a PDF. | |
delete-pages | Remove the pages listed in pages. Returns a PDF. | |
protect-pdf | Encrypt with a password (AES-256). Returns a PDF. | |
unlock-pdf | Remove the password (supply the current one in password). Returns a PDF. | |
mute-video | MP4, MOV, AVI, WEBM | Remove the audio track by stream-copy (lossless). Returns the same format. |
trim-media | Audio & video | Cut to trim {start,end} in seconds, lossless. Returns the same format. |
normalize-audio | Audio | Normalise loudness to −16 LUFS (EBU R128). Returns the same format. |
Multi-file operations — merge & batch PRO & UP
Operations that take more than one input file have their own endpoints, since the single-file /api/v1/convert can't carry them. They use the same Authorization: Bearer ck_live_… key. Send files as multipart/form-data with a repeated files field.
Merge two or more PDFs into one, in the order sent. Returns a job you poll via /api/v1/jobs/:jobId (same as /api/v1/convert).
curl -X POST https://formatly.pro/api/pdf/merge \ -H "Authorization: Bearer ck_live_..." \ -F "files=@/path/a.pdf" \ -F "files=@/path/b.pdf" # { "jobId": "124", "status": "queued" } → poll GET /api/v1/jobs/124 for the downloadUrl
Convert many files in one request with a single global to format (or a global op). The result is delivered as a ZIP.
curl -X POST https://formatly.pro/api/batch/convert \ -H "Authorization: Bearer ck_live_..." \ -F "to=jpg" \ -F "files=@/path/a.png" \ -F "files=@/path/b.png" # { "batchId": "...", "statusUrl": "/api/batch/..." } # → poll GET /api/batch/:batchId, then GET /api/batch/:batchId/download (ZIP)
Every tool is reachable from the API: single-file conversions and all transform ops above via /api/v1/convert, plus merge and batch via the two endpoints here, and the two extraction endpoints below. Like every API call, these spend API credits (see below): a merge costs credits by total input size and returns creditsRemaining; batch charges per file — track the balance via /api/v1/credits.
Extraction — data in, data out PRO & UP
These return structured JSON rather than a converted file, so they have their own poll endpoints instead of /api/v1/jobs/:jobId. All run entirely on our own EU hardware with no external AI, and both the upload and the extracted result are deleted within 1 hour — so the result is only available from the poll URL during that window. Same Authorization: Bearer ck_live_… key, same credit pool.
Plan gate: these are Pro tools. A non-paid key gets one free call per tool (a single file — these are per-file batch endpoints, so the free try isn't a free batch), shared with the web app: using your free try in the browser uses it for the API too. After that, a non-paid key gets 403 with {"code":"trial_used"}; a multi-file trial call gets {"code":"trial_single_file"}. If a job fails, the free try is given back — a broken test file shouldn't cost you your evaluation.
Parse up to 50 CVs (pdf, docx, doc) into candidate rows — name, email, phone, title, employer, location, linkedin, skills, years_exp — each with a confidence score. Scanned CVs fall back to OCR automatically. Poll GET /api/parse-cv/:batchId.
curl -X POST https://formatly.pro/api/parse-cv \ -H "Authorization: Bearer ck_live_..." \ -F "files=@/path/candidate.pdf" # { "batchId": "...", "statusUrl": "/api/parse-cv/..." } # → poll GET /api/parse-cv/:batchId # { "done": true, "rows": [ { "filename": "candidate.pdf", "status": "completed", # "name": "Ada Lovelace", "email": "ada@example.com", "years_exp": 7, # "skills": ["python","sql"], "confidence": { "name": 0.85, "email": 1.0 } } ] }
Anything below 0.8 confidence is worth a human glance — the web tool highlights exactly those cells. Treat the output as a first pass, not as verified truth.
Transcribe up to 10 audio or video files (mp3, wav, m4a, mp4, mov, and more) into text plus per-segment timestamps you can render as SRT/VTT. Each file is limited to 2 hours and 2 GB (transcription cost scales with duration, not bytes — the size cap is generous so full-length video works). Business tier and up. Optional lang field (en, fr, de, es, it, nl, pt, ja); omit it to auto-detect. Poll GET /api/transcribe/:batchId.
curl -X POST https://formatly.pro/api/transcribe \ -H "Authorization: Bearer ck_live_..." \ -F "files=@/path/interview.mp3" \ -F "lang=en" # { "batchId": "...", "statusUrl": "/api/transcribe/..." } # → poll GET /api/transcribe/:batchId # { "done": true, "rows": [ { "filename": "interview.mp3", "status": "completed", # "language": "en", "duration": 612.4, "text": "So the way we...", # "segments": [ { "start": 0.0, "end": 4.2, "text": "So the way we..." } ] } ] }
Transcription is charged by file size and type like every other operation (at a higher weight than a plain conversion — it is far more compute-intensive), not per minute of audio. A long recording is a genuinely long job: expect a few minutes for an hour of audio, and keep polling.
Find a word or phrase across up to 50 mixed files — text/CSV, PDF, DOCX, spreadsheets, and photos/scans via OCR. Required query field (matched literally, never as a regex); optional wholeWord and caseSensitive ("true"). Poll GET /api/file-search/:batchId.
curl -X POST https://formatly.pro/api/file-search \ -H "Authorization: Bearer ck_live_..." \ -F "query=invoice" \ -F "files=@/path/report.pdf" \ -F "files=@/path/staff.xlsx" # { "batchId": "...", "statusUrl": "/api/file-search/..." } # → poll GET /api/file-search/:batchId # { "done": true, "totalHits": 2, "rows": [ # { "filename": "report.pdf", "status": "completed", "kind": "pdf", "count": 1, # "truncated": false, "error": null, # "matches": [ { "location": "page 3, line 12", "snippet": "…total invoice due…", # "offset": 7, "length": 7 } ] }, # { "filename": "locked.pdf", "status": "completed", "count": 0, # "error": "password protected" } ] }
Check error before trusting count: 0. A file we couldn't read reports an explicit error and a count of 0 — that means "we couldn't look", not "the word isn't there". Treating the two the same is the one mistake that makes a search result dangerous. snippet is plain text and offset/length locate the hit within it, so you can highlight without trusting file content as markup.
PDF → Markdown FOR RAG
Turn a PDF into structure-preserving Markdown for a retrieval pipeline or an LLM context window. A PDF dumped as raw text loses exactly what a retriever needs — headings, tables, reading order — so this reconstructs them: ATX headings by size rank, real Markdown tables, normalised lists, and <!-- page N --> markers you can chunk on and cite.
Running headers and footers are stripped ("Acme Corp — page 3 of 40"), because boilerplate repeated on every page pollutes every chunk and can outrank real content at query time. Send keepRunning=true to keep them, or pageBreaks=false to drop the page markers.
Up to 25 PDFs per request. Poll GET /api/pdf-to-markdown/:batchId.
curl -X POST https://formatly.pro/api/pdf-to-markdown \ -H "Authorization: Bearer ck_live_..." \ -F "files=@/path/handbook.pdf" # { "batchId": "...", "statusUrl": "/api/pdf-to-markdown/..." } # → poll GET /api/pdf-to-markdown/:batchId # { "done": true, "rows": [ { "filename": "handbook.pdf", "status": "completed", # "pages": 40, "markdown": "# Handbook\n\n## 1. Scope\n\n...", # "stats": { "headings": 32, "tables": 5, "words": 11840, # "running_lines_stripped": 2 } } ] }
Deterministic — there is no model in this path. The same PDF always yields the same Markdown, so you can diff it, cache it, and reason about it. And, as with every tool here, your document is never sent to a third-party AI in order to be "understood" — which is the whole point of using this rather than a cloud document-AI service. Charged by file size from the same credit pool.
Example — redact a PDF
The redact field is a JSON object. patterns selects built-in deterministic detectors (any of email, phone, ssn, credit-card, iban); terms is a list of exact words or phrases to remove. Matching is case-insensitive (the safer default for redaction). Provide at least one pattern or term. The matched text is removed from the PDF content stream, so it cannot be copied or recovered.
curl -X POST https://formatly.pro/api/v1/convert \ -H "Authorization: Bearer ck_live_..." \ -F "file=@/path/to/contract.pdf" \ -F "to=pdf" \ -F "op=redact" \ -F 'redact={"patterns":["email","phone"],"terms":["John Doe","Acme Corp"]}'
Example — cURL
curl -X POST https://formatly.pro/api/v1/convert \ -H "Authorization: Bearer ck_live_..." \ -F "file=@/path/to/video.mp4" \ -F "to=gif"
Example — Node.js
import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("video.mp4")]), "video.mp4"); form.append("to", "gif"); const res = await fetch("https://formatly.pro/api/v1/convert", { method: "POST", headers: { "Authorization": "Bearer ck_live_..." }, body: form, }); const data = await res.json(); // { jobId: "123", status: "queued", statusUrl: "/api/v1/jobs/123" }
Example — Python
import requests with open("video.mp4", "rb") as f: res = requests.post( "https://formatly.pro/api/v1/convert", headers={"Authorization": "Bearer ck_live_..."}, files={"file": f}, data={"to": "gif"}, ) job = res.json() # { "jobId": "123", "status": "queued", "statusUrl": "/api/v1/jobs/123" }
Response
{
"jobId": "123",
"status": "queued",
"creditCost": 3,
"creditsRemaining": 9751,
"statusUrl": "/api/v1/jobs/123"
}
Check the status of a conversion job. Poll every 1-2 seconds until status is done or failed.
Example
curl https://formatly.pro/api/v1/jobs/123 \ -H "Authorization: Bearer ck_live_..."
Responses
While queued:
{ "status": "waiting", "progress": 0 }
While processing:
{ "status": "active", "progress": 45 }
When complete:
{
"status": "done",
"downloadUrl": "https://formatly.pro/api/download/abc123...",
"expiresIn": 900
}
The downloadUrl is valid for 15 minutes from the moment the conversion completes. It can be fetched more than once within that window — useful if your client retries on a transient network error — but after 15 minutes the token is rejected and the file is purged from disk shortly after. For longer-lived or embeddable URLs, use signed-URL outputs below.
For the detection-based tools (op=blur-faces, redact, remove-watermark) the done response also carries a toolReport — e.g. {"op":"redact","detected":17} — with the number of faces / regions / watermark elements actually found. Check detected for 0: it means nothing matched (for example a scanned PDF with no text layer) and the output is unchanged — don't treat it as a successful redaction.
Supported formats
Supported input formats (50+ types). Output formats vary by category — see the converter for exact combinations.
- Images: PNG, JPG, JPEG, WEBP, AVIF, GIF, BMP, HEIC, HEIF, SVG, TIFF
- Video: MP4, MOV, AVI, WEBM, MKV, WMV, FLV, M4V, 3GP, MPEG, TS, MTS, M2TS, VOB, OGV (legacy/broadcast/DVD containers are input-only; convert them to MP4/MOV/AVI/WEBM)
- Audio: MP3, WAV, OGG, FLAC, AAC, M4A, OPUS, AIFF, WMA, AMR, M4B, MP2, AC3 (legacy audio is input-only; convert them to MP3/WAV/OGG/FLAC/AAC/M4A/OPUS/AIFF)
- Documents: PDF, DOCX, PPTX, XLSX, CSV, DOC, XLS, PPT, ODT, ODS, ODP, RTF, TXT (plus EPUB as an output; legacy DOC/XLS/PPT modernise to DOCX/XLSX/PPTX)
- Data & subtitles: CSV, XLSX, JSON, SRT, VTT — CSV ⇄ XLSX ⇄ JSON conversions run in-process (SheetJS); SRT ⇄ VTT is pure JS with formula-injection defended both directions
New output targets: AVIF (next-gen AV1 image format) and M4A (AAC audio in an MP4 container). Any image input can also be converted to PDF (e.g. to=pdf on a JPG or PNG). Data conversions: to=xlsx on a CSV/JSON, to=json on a CSV/XLSX/XLS, or to=csv on a JSON/XLSX/XLS. Subtitles: to=vtt on an SRT, or to=srt on a VTT.
Rate limits
API usage is metered in credits and rate-limited per minute, scaled by plan:
- Free: 30 requests per minute, files up to 25 MB. 250 credits/month — a developer quota to build and evaluate.
- Pro (€9.99): 120 requests per minute, files up to 500 MB. 3,000 credits/month.
- Business (€49.99): 750 requests per minute, files up to 5 GB. 25,000 credits/month, plus signed-URL outputs (7-day retention).
- Enterprise (contact sales): custom credit volume, a mutual NDA, and EU data residency. Contact sales.COMING SOON
One credit pool is shared by the web converter and the API, weighted by file size and type (1 image / 3 audio·doc / 10 video·large, up to 100 for multi-GB files). Add a card to enable pay-as-you-go and keep converting past your included credits, billed monthly at €5 per 1,000 credits (every plan except Enterprise).
Requests beyond your per-minute rate limit receive 429 Too Many Requests — retry with backoff. Throughput is otherwise bounded by your credit allowance and our shared processing pool, not a fixed per-account slot count.
Need more than a plan includes? Turn on pay-as-you-go, step up a tier, or for custom volume / a mutual NDA email sales@formatly.pro about Enterprise.
API credits ALL PLANS
API calls consume credits based on file size and format, from the same pool as the web app. Included monthly: Free 250 · Pro 3,000 · Business 25,000 (Enterprise is custom). Every response includes your credit usage so you always know where you stand.
| Tier | Condition | Credits |
|---|---|---|
| Small | Simple image formats, < 10 MB | 1 |
| Medium | Audio, SVG, HEIC/HEIF, CSV — or 10–100 MB | 3 |
| Heavy | Video, PDF, Office docs — or > 100 MB | 10–100 |
Every conversion costs at least 1 credit, regardless of file size. Your monthly credits reset on the 1st of each month.
Beyond your included credits, calls return 402 Insufficient API credits until the next reset — unless you enable overage. With overage on (opt-in, so there's never a surprise bill), calls keep working past your allowance and the excess is metered and billed pay-as-you-go on your next invoice. Track it with the overageEnabled and overageCredits fields in /api/v1/credits. Turn it on yourself in My Account → Pay-as-you-go (a card on file is required so the metered usage can be billed).
Credit fields in responses
{
"jobId": "123",
"status": "queued",
"creditCost": 3,
"creditsRemaining": 9751,
"statusUrl": "/api/v1/jobs/123"
}
Returns your current credit balance and tier breakdown.
curl https://formatly.pro/api/v1/credits \ -H "Authorization: Bearer ck_live_..."
{
"plan": "business",
"creditsIncluded": 25000,
"creditsUsed": 249,
"creditsRemaining": 24751,
"resetDate": "2026-10-01T00:00:00.000Z"
}
Conversion history ALL PLANS
Pull your account's conversion history — the same records shown in the web "My Account" panel. Newest first, paginated with limit (1–100, default 50) and offset (default 0).
curl https://formatly.pro/api/v1/history \ -H "Authorization: Bearer ck_live_..."
{
"total": 128,
"limit": 50,
"offset": 0,
"hasMore": true,
"history": [
{ "time": "2026-05-20T09:14:00.000Z", "conv": "PNG → JPG", "jobId": "123", "viaApi": true }
]
}
Signed-URL outputs BUSINESS & UP
The standard download token from /api/v1/jobs/:jobId is capped at a 15-minute lifetime — ideal for "fetch the bytes and re-host" workflows, less ideal for embedding the converted file directly in your product.
Signed URLs solve that: mint a reusable URL with a configurable lifetime, point your <img> or <video> tag at it, and stop worrying about renewing tokens. The URL is opaque (256-bit random) and does not contain user identity.
Maximum lifetime depends on plan: Business → 7 days, Enterprise → 30 days. Default lifetime if you omit expirySeconds: 24 hours on Business, 7 days on Enterprise.
When you mint a signed URL, the converted file is kept on our servers until that URL expires — instead of the default 1-hour deletion — so the link works for its full lifetime. This applies only to outputs you explicitly mint a URL for; every other file is still deleted within 1 hour. The file is mintable only while it still exists, so call /api/v1/signed-url within an hour of the conversion completing.
curl -X POST https://formatly.pro/api/v1/signed-url \ -H "Authorization: Bearer ck_live_..." \ -H "Content-Type: application/json" \ -d '{"jobId": "123", "expirySeconds": 604800}'
Request body:
{
"jobId": "123", // required — must belong to the authenticated user
"expirySeconds": 604800, // optional — min 60s; max 604,800 (7d) on Business, 2,592,000 (30d) on Enterprise
"reusable": true // optional — defaults to true (the whole point of this endpoint)
}
Response:
{
"url": "https://formatly.pro/api/download/8f4e...",
"expiresIn": 604800,
"expiresAt": "2026-05-22T11:00:00.000Z",
"reusable": true
}
Notes:
- A signed URL keeps its output on our servers until the URL expires (up to 30 days); every other output is still deleted within 1 hour. Once an output is gone, any outstanding URL for it returns
410 Gone. You can only mint a URL while the output still exists, so call/api/v1/signed-urlwithin an hour of the conversion completing. - Cross-tenant minting is blocked:
jobIdmust belong to the API key holder. Attempts return 404 (not 403, to avoid disclosing the existence of other tenants' jobs). - Maximum expiry: 7 days on Business, 30 days on Enterprise. For longer-lived embeds, re-convert the file when needed.
Error codes
| Code | Meaning |
|---|---|
| 400 | Bad request — missing or invalid parameters |
| 401 | Invalid or missing API key |
| 403 | Forbidden — e.g. email not verified (key generation), a paid-only tool op on a Free key, or a Business-and-up feature such as signed URLs |
| 404 | Job not found |
| 413 | File too large (over plan limit) |
| 429 | Rate limit exceeded |
| 500 | Server error — try again |