REST API documentation
conv2pdf REST API — convert, merge, split, compress, protect and number PDFs from your application — 14 tools exposed via 5 REST endpoints. Hosting in France, GDPR-compliant, no US third party in the processing chain.
Quick start
- Create an account (free, email — no password required)
- From your dashboard, create an API key (free Dev plan, 300 conversions included to test, valid 12 months, no renewal)
- Authenticate your calls with the
Authorization: Bearer cpdf_live_…header
Base URL
https://api.conv2pdf.com/v1
SDK, OpenAPI & Postman
- Official PHP SDK:
composer require conv2pdf/php(repo and examples). - OpenAPI 3.0 specification (JSON) — to generate a client in another language, import it into a tool (Swagger, Insomnia…) or feed an agent.
- Postman collection — import it, set your key, convert a PDF in a minute.
Authentication
Every request must include the HTTP header Authorization: Bearer <your_key>. A revoked or non-existent key returns a 401 status. An exceeded quota returns 429 with reset details.
Endpoints
GET /v1/tools
Returns the list of available tools, their limits and accepted formats.
curl https://api.conv2pdf.com/v1/tools \
-H "Authorization: Bearer cpdf_live_..."
POST /v1/convert/:tool
Performs a conversion. Files are sent as multipart/form-data.
14 tools available. The up-to-date machine list (accepted formats, file bounds) is returned by GET /v1/tools.
Tool (:tool) | Input | Files | Output |
|---|---|---|---|
image-to-pdf | PNG, JPG, WEBP, GIF, TIFF | 1 | |
heic-to-jpg | HEIC, HEIF | 1 | JPG |
heic-to-pdf | HEIC, HEIF | 1 | |
office-to-pdf | DOC(X/M), ODT, RTF, TXT, XLS(X/M), ODS, CSV, PPT(X/M), ODP, ODG, SXW/SXC/SXI/SXD, FODT/FODS/FODP/FODG | 1 | |
pdf-to-word | 1 | DOCX | |
pdf-to-image | 1 | ZIP (1 image/page) | |
merge-pdf | 2 to 20 | ||
split-pdf | 1 | ||
compress-pdf | 1 | ||
rotate-pdf | 1 | ||
protect-pdf | 1 | ||
unlock-pdf | 1 | ||
watermark-pdf | 1 | ||
page-numbers-pdf | 1 |
Optional parameters (form-data fields):
split-pdf:rangesrequired (e.g.1-5,7,10-12)compress-pdf:quality(low,mediumdefault,high)protect-pdf:passwordrequired (4 to 64 characters); optionalprevent_print=on,prevent_copy=onunlock-pdf:passwordrequired (the PDF’s current password, to remove)rotate-pdf:rotationrequired (90,180or270)watermark-pdf:textrequired (watermark text)pdf-to-image:format(pngdefault orjpg)page-numbers-pdf:position(bottom-centerdefault,bottom-left,bottom-right);format=simplefor the number only (no total)
Example — Image → PDF (curl)
curl -X POST https://api.conv2pdf.com/v1/convert/image-to-pdf \
-H "Authorization: Bearer cpdf_live_..." \
-F "file=@photo.jpg"
Example — Merge PDFs (curl)
curl -X POST https://api.conv2pdf.com/v1/convert/merge-pdf \
-H "Authorization: Bearer cpdf_live_..." \
-F "file=@doc1.pdf" \
-F "file=@doc2.pdf" \
-F "file=@doc3.pdf"
Example — Compression (curl)
curl -X POST https://api.conv2pdf.com/v1/convert/compress-pdf \
-H "Authorization: Bearer cpdf_live_..." \
-F "file=@big.pdf" \
-F "quality=medium"
Example — PDF → Word (curl)
curl -X POST https://api.conv2pdf.com/v1/convert/pdf-to-word \
-H "Authorization: Bearer cpdf_live_..." \
-F "file=@report.pdf"
Returns an editable .docx file. A scanned PDF (no text layer) returns 422 pdf_scanned_needs_ocr: OCR is available to Premium accounts on the website only, and is not exposed through the API; beyond 500 pages, 422 pdf_too_many_pages.
Example — PDF → Image (curl)
curl -X POST https://api.conv2pdf.com/v1/convert/pdf-to-image \
-H "Authorization: Bearer cpdf_live_..." \
-F "file=@report.pdf" \
-F "format=png"
Renders each page as an image at 150 DPI and returns a .zip archive (one image per page). The format field is png (default) or jpg. Beyond 100 pages, 422 too_many_pages; if the result exceeds the maximum size, 422 output_too_large.
Example — Password protection (curl)
curl -X POST https://api.conv2pdf.com/v1/convert/protect-pdf \
-H "Authorization: Bearer cpdf_live_..." \
-F "file=@confidential.pdf" \
-F "password=secret123" \
-F "prevent_print=on" \
-F "prevent_copy=on"
AES-256 encryption. The resulting PDF will prompt for the password on opening and apply the selected restrictions.
Successful response
The quota object is only present for calls authenticated with an API key.
{
"job_id": "abc123…",
"status": "success",
"download_url": "/v1/download/abc123…",
"size_bytes": 124533,
"quota": {
"plan": "starter",
"quota": 1000,
"used": 42,
"soft_cap_limit": 1100,
"status": "ok",
"period_end": 1715789012345
}
}
GET /v1/download/:jobId
Downloads the converted file. The file is served with its output MIME type (application/pdf, DOCX for PDF → Word, or application/zip for PDF → Image) and Cache-Control: no-store. Available for 1 hour after conversion.
curl https://api.conv2pdf.com/v1/download/abc123… \
-H "Authorization: Bearer cpdf_live_..." \
-o output.pdf
GET /v1/job/:jobId
Returns a job’s status and metadata (status, size, dates). Handy to check that a conversion is ready before downloading it.
curl https://api.conv2pdf.com/v1/job/abc123… \
-H "Authorization: Bearer cpdf_live_..."
DELETE /v1/job/:jobId
Immediately deletes a job and its file, without waiting for the automatic expiry (1 hour).
curl -X DELETE https://api.conv2pdf.com/v1/job/abc123… \
-H "Authorization: Bearer cpdf_live_..."
Examples — Node.js
import { readFile } from 'node:fs/promises';
const file = await readFile('./photo.jpg');
const formData = new FormData();
formData.append('file', new Blob([file]), 'photo.jpg');
const res = await fetch('https://api.conv2pdf.com/v1/convert/image-to-pdf', {
method: 'POST',
headers: { 'Authorization': 'Bearer cpdf_live_...' },
body: formData
});
const data = await res.json();
console.log(data.download_url);
Examples — Python
import requests
with open('photo.jpg', 'rb') as f:
r = requests.post(
'https://api.conv2pdf.com/v1/convert/image-to-pdf',
headers={'Authorization': 'Bearer cpdf_live_...'},
files={'file': f}
)
print(r.json()['download_url'])
Examples — PHP
Official SDK (recommended)
Install the SDK: composer require conv2pdf/php. See the repo and its examples.
use Conv2pdf\Conv2pdf;
$c = new Conv2pdf('cpdf_live_...');
$job = $c->convert('image-to-pdf', 'photo.jpg');
$c->download($job['download_url'], 'photo.pdf');
Without a dependency (raw request)
$ch = curl_init('https://api.conv2pdf.com/v1/convert/image-to-pdf');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer cpdf_live_...'],
CURLOPT_POSTFIELDS => ['file' => new CURLFile('photo.jpg')],
]);
$data = json_decode(curl_exec($ch), true);
echo $data['download_url'];
Error codes
Every error response carries an error field: a stable code you can branch on. The extra fields listed below tell you what to fix. Refusals at the door (upload, plan, content) also carry plan; errors raised during conversion carry job_id and status (rejected for a verdict on the file, failed for a breakdown).
| Code | Error | Cause |
|---|---|---|
| 400 | not_enough_files / too_many_files | File count outside tool bounds. The body carries min and provided, or max. |
| 400 | field_required | Required field missing or empty: ranges on split-pdf, text on watermark-pdf. The body carries field, plus example when the expected shape is not obvious. |
| 400 | invalid_rotation / invalid_page_range / invalid_quality / invalid_format | Parameter outside the accepted values. The body carries what is accepted (allowed, valid) and, for a page range, range with total_pages. |
| 400 | password_too_short / password_too_long | Password outside bounds on protect-pdf. The body carries min or max. |
| 400 | bad_request | Multipart body refused by the parser: forbidden field name, invalid JSON field, or a body cut short in flight (code: "malformed_multipart"). The body carries code. |
| 401 | missing_bearer_token / invalid_api_key / account_not_provisioned | Missing auth, invalid or revoked key, or a key with no API account attached. |
| 402 | plan_limit_files | More files than the plan allows per conversion: 1 on the Dev plan, 20 on paid plans. The body carries plan and max_allowed — the limit actually applied, which is higher on a tool that requires several files (merge-pdf takes 2 even on Dev). |
| 402 | credits_expired | Dev plan trial credits past their validity date (12 months from when they were granted), whatever is left. The body carries expired_at and upgrade. |
| 403 | forbidden | Job belongs to another key |
| 404 | tool_not_found / job_not_found | Tool or job does not exist |
| 409 | job_not_ready | The job exists but has no output: conversion pending, failed or rejected. The body carries status (pending / failed / rejected). |
| 410 | file_expired / job_deleted | Resource is gone for good: 1 h TTL exceeded, or job removed via DELETE /v1/job/:jobId. Do not retry. |
| 413 | file_too_large | File beyond the plan limit: 10 MB on the Dev plan, 200 MB on paid plans. When a higher plan would accept the file, the response carries upgrade with that plan, its monthly price excl. tax and its limit. |
| 413 | payload_too_large | Whole request beyond 220 MB, files and fields combined. The body carries max_bytes. |
| 413 | too_many_parts | Too many elements in the multipart body: 20 files, 10 fields, 32 parts in total. The body carries limit (the bound that was crossed) and max. |
| 415 | unsupported_media_type | The request Content-Type cannot be parsed. Conversions are sent as multipart/form-data; everything else lands here — a raw body (application/pdf, application/octet-stream…), application/json, application/x-www-form-urlencoded, a missing header, or multipart/form-data without a boundary. The refusal happens before the body is read. The response carries the received type (received) and the list of parsable types (accepted). |
| 415 | unsupported_content | File content does not match the tool. The type is determined from CONTENT, never from the filename extension: a valid PDF is accepted even without an extension, and a file renamed to .pdf is rejected. The response carries file, the detected type (detected_type) and bytes. |
| 415 | unsupported_format | image-to-pdf: image in a format the tool cannot read. The body carries received and accepted. |
| 422 | empty_file | Empty file (0 bytes). The body carries file. |
| 422 | password_protected | Password-protected (encrypted) file on a tool that cannot decrypt it; remove the protection before converting. The body carries file. |
| 422 | needs_password / wrong_password | Encrypted PDF on a tool that can handle it: password missing, or refused by the file. Send the password field. Neither a job nor quota is consumed. |
| 422 | pdf_not_protected / pdf_already_protected | unlock-pdf on an unprotected PDF, or protect-pdf on an already encrypted one. |
| 422 | pdf_scanned_needs_ocr | PDF → Word: scanned document with no text layer. OCR is website-only, for Premium accounts; the API always returns this code. |
| 422 | pdf_too_many_pages / too_many_pages | PDF → Word: more than 500 pages. Other PDF tools have their own ceiling, returned in max_pages. |
| 422 | unsupported_characters | watermark-pdf: the text contains characters the embedded fonts cannot render. The body carries field. |
| 422 | output_too_large | The output would exceed the tool ceiling (pdf-to-image on a heavy document). The body carries max_bytes. |
| 422 | source_unreadable | File of the right type, but the engine cannot open it: corrupt, truncated, or non-standard structure. The body carries engine. |
| 429 | rate_limited | Rate exceeded: 20 conversions per minute per key on POST /v1/convert/:tool, 300 requests per minute per IP on the other endpoints. The response carries retry_after (seconds) and the Retry-After header; the monthly quota is not consumed. |
| 429 | quota_exceeded | Quota reached. On paid plans, the body carries quota_period_end, the next reset date: do not retry before it. On the Dev plan, both quota_period_end and period_end are null — the included conversions do not renew, and a trial is not billed — and the body carries upgrade, the plan that reopens access. |
| 500 | conversion_failed | The engine failed. The body carries job_id, status: "failed" and, when identified, engine. Quota is refunded: our own breakdowns are not billed. |
| 500 | internal_error | Breakdown outside conversion. No detail is published — 5xx responses are masked server-side. |
| 503 | server_busy | Conversion queue saturated (retry within 5 s — Retry-After header) |
| 504 | conversion_timeout | Engine time budget exceeded. The body carries engine and timeout_ms, and the job is failed. Quota is refunded. |
Quotas and reset
Each account has a quota according to its plan (see API pricing), shared by all its keys. On paid plans, the quota is monthly and resets on an anniversary date (not on the 1st of the month): the timestamp of the next reset is returned in quota_period_end, and the used counter goes back to zero. On the Dev plan, the 300 conversions included at signup are valid for 12 months from when they were granted (credits_expire_at) and do not renew (quota_period_end is null, and so is period_end: a trial is not billed). An email warns you at 80% of your quota and when it runs out, on every plan, and 30 days before your trial credits expire. A small overage is tolerated (soft_cap_limit, +10%) before the 429 block.
Privacy
No file (input or output) is kept beyond 1 hour: this API has no exception to that rule. Only one thing can extend a file's life, and it goes through the website: from the dashboard, the account holder can create a share link for a job — including one produced by the API — in which case the shared copy lives until the link expires (seven days at most on Free, thirty on Premium) or until it is revoked. No result caching is applied. All processing happens on servers in France (OVH Gravelines). See our privacy policy for details.
Support
For any technical question, use our contact form (subject "Technical question"). Business and Custom plans: priority support with contractual SLA.