The API.

Convert files from your code. Simple REST, auth with one header, response in JSON.

Start free — the API is self-serve on every plan. Create an account, verify your email, generate a key, and you're converting in minutes. One credit pool covers the web app and the API (weighted by file size + type: 1 / 3 / 10, rising to 100 for multi-GB files).
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.

  1. 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.
  2. 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 }
  1. 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.

Building an AI agent or client library? Import the machine-readable OpenAPI 3.1 spec to auto-generate a typed client or an agent tool, and point your LLM at the /llms.txt summary.

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

POST/api/v1/convert

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:

FieldTypeDescription
filefileThe 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.
tostringOutput 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.
opstringOptional. 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.
qualityintegerOptional. 10–95. Applies to compress (images) and compress-video. Lower = smaller file, higher = better quality. Defaults to a balanced value if omitted.
redactJSON stringRequired when op=redact. A JSON object describing what to black out — see the redaction example below.
angleintegerRequired when op=rotate-pdf. Clockwise rotation applied to every page: 90, 180 or 270.
splitJSON stringRequired 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.
pagesstringRequired 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.
passwordstringRequired 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.
trimJSON stringRequired when op=trim-media. {"start":12,"end":45} — start and end in seconds (end after start). Lossless stream-copy.
resizeJSON stringRequired 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).
langstringOptional, 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).

opInputWhat it does
clean-metadataImages, audio, video, PDF, OfficeStrip EXIF, GPS, author and other hidden metadata.
compressJPG, PNG, WEBPRe-encode at a target quality to reduce file size.
resizeJPG, PNG, WEBP, GIF, TIFF, AVIFResize and/or crop to a target size (set resize). Returns the same format.
compress-videoMP4, MOV, AVI, WEBMRe-encode (H.264, or VP9 for WEBM) at a target quality.
blur-facesImagesDetect faces and replace them with a pixel mosaic. Returns PNG.
remove-bgJPG, PNG, WEBP, BMP, HEICRemove the background (segmentation runs on our servers — the image never goes to a third party). Returns a transparent PNG.
ocrPDFAdd an invisible searchable text layer to a scanned PDF.
remove-watermarkPDFRemove annotation/layer-based watermarks from PDFs you authored.
redactPDFPermanently delete matching text from the file (not just cover it). Rule-based — no AI. Requires a redact spec.
repair-pdfPDFRecover a damaged/corrupted PDF by rebuilding its structure; also strips auto-run scripts. Best-effort, rule-based — no AI.
repair-csvCSVNormalise a malformed CSV: fix encoding (→ UTF-8), delimiter (→ comma), line endings and quoting. Content preserved. Rule-based — no AI.
repair-officeDOCX, XLSX, PPTXRecover a damaged Office file by re-saving it with LibreOffice's repair-tolerant import. Best-effort, rule-based — no AI.
rotate-pdfPDFRotate every page by 90/180/270° (set angle). No quality loss — the page is rotated in place. Returns a PDF.
split-pdfPDFSplit into one PDF per page, or per custom range (set split). Returns a ZIP of the resulting PDFs.
grayscaleJPG, PNG, WEBP, GIF, TIFF, AVIFConvert the image to greyscale. Returns the same format.
sepiaJPG, PNG, WEBP, GIF, TIFF, AVIFApply a warm sepia tone. Returns the same format.
invertJPG, PNG, WEBP, GIF, TIFF, AVIFInvert the image colours (photo negative). Returns the same format.
compress-pdfPDFShrink a PDF by recompressing its streams/images. Returns a PDF.
pdf-to-textPDFExtract the embedded text layer. Returns a .txt file.
extract-imagesPDFPull out every embedded raster image. Returns a ZIP.
extract-pagesPDFKeep only the pages listed in pages. Returns a PDF.
delete-pagesPDFRemove the pages listed in pages. Returns a PDF.
protect-pdfPDFEncrypt with a password (AES-256). Returns a PDF.
unlock-pdfPDFRemove the password (supply the current one in password). Returns a PDF.
mute-videoMP4, MOV, AVI, WEBMRemove the audio track by stream-copy (lossless). Returns the same format.
trim-mediaAudio & videoCut to trim {start,end} in seconds, lossless. Returns the same format.
normalize-audioAudioNormalise 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.

POST/api/pdf/merge

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
POST/api/batch/convert

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.

POST/api/parse-cv

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.

POST/api/transcribe

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.

POST/api/file-search

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.

POST/api/pdf-to-markdown

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"
}
GET/api/v1/jobs/:jobId

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.

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:

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.

TierConditionCredits
SmallSimple image formats, < 10 MB1
MediumAudio, SVG, HEIC/HEIF, CSV — or 10–100 MB3
HeavyVideo, PDF, Office docs — or > 100 MB10–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"
}
GET/api/v1/credits

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).

GET/api/v1/history?limit=50&offset=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.

POST/api/v1/signed-url
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:

Error codes

CodeMeaning
400Bad request — missing or invalid parameters
401Invalid or missing API key
403Forbidden — 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
404Job not found
413File too large (over plan limit)
429Rate limit exceeded
500Server error — try again
Need help? Email support@formatly.pro. All paid plans (Pro, Business and Enterprise) get priority email support.