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

  1. Create an account (free, email — no password required)
  2. From your dashboard, create an API key (free Dev plan, 300 conversions included to test, valid 12 months, no renewal)
  3. Authenticate your calls with the Authorization: Bearer cpdf_live_… header

Base URL

https://api.conv2pdf.com/v1

SDK, OpenAPI & Postman

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)InputFilesOutput
image-to-pdfPNG, JPG, WEBP, GIF, TIFF1PDF
heic-to-jpgHEIC, HEIF1JPG
heic-to-pdfHEIC, HEIF1PDF
office-to-pdfDOC(X/M), ODT, RTF, TXT, XLS(X/M), ODS, CSV, PPT(X/M), ODP, ODG, SXW/SXC/SXI/SXD, FODT/FODS/FODP/FODG1PDF
pdf-to-wordPDF1DOCX
pdf-to-imagePDF1ZIP (1 image/page)
merge-pdfPDF2 to 20PDF
split-pdfPDF1PDF
compress-pdfPDF1PDF
rotate-pdfPDF1PDF
protect-pdfPDF1PDF
unlock-pdfPDF1PDF
watermark-pdfPDF1PDF
page-numbers-pdfPDF1PDF

Optional parameters (form-data fields):

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

CodeErrorCause
400not_enough_files / too_many_filesFile count outside tool bounds. The body carries min and provided, or max.
400field_requiredRequired 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.
400invalid_rotation / invalid_page_range / invalid_quality / invalid_formatParameter outside the accepted values. The body carries what is accepted (allowed, valid) and, for a page range, range with total_pages.
400password_too_short / password_too_longPassword outside bounds on protect-pdf. The body carries min or max.
400bad_requestMultipart 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.
401missing_bearer_token / invalid_api_key / account_not_provisionedMissing auth, invalid or revoked key, or a key with no API account attached.
402plan_limit_filesMore 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).
402credits_expiredDev plan trial credits past their validity date (12 months from when they were granted), whatever is left. The body carries expired_at and upgrade.
403forbiddenJob belongs to another key
404tool_not_found / job_not_foundTool or job does not exist
409job_not_readyThe job exists but has no output: conversion pending, failed or rejected. The body carries status (pending / failed / rejected).
410file_expired / job_deletedResource is gone for good: 1 h TTL exceeded, or job removed via DELETE /v1/job/:jobId. Do not retry.
413file_too_largeFile 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.
413payload_too_largeWhole request beyond 220 MB, files and fields combined. The body carries max_bytes.
413too_many_partsToo 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.
415unsupported_media_typeThe 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).
415unsupported_contentFile 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.
415unsupported_formatimage-to-pdf: image in a format the tool cannot read. The body carries received and accepted.
422empty_fileEmpty file (0 bytes). The body carries file.
422password_protectedPassword-protected (encrypted) file on a tool that cannot decrypt it; remove the protection before converting. The body carries file.
422needs_password / wrong_passwordEncrypted 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.
422pdf_not_protected / pdf_already_protectedunlock-pdf on an unprotected PDF, or protect-pdf on an already encrypted one.
422pdf_scanned_needs_ocrPDF → Word: scanned document with no text layer. OCR is website-only, for Premium accounts; the API always returns this code.
422pdf_too_many_pages / too_many_pagesPDF → Word: more than 500 pages. Other PDF tools have their own ceiling, returned in max_pages.
422unsupported_characterswatermark-pdf: the text contains characters the embedded fonts cannot render. The body carries field.
422output_too_largeThe output would exceed the tool ceiling (pdf-to-image on a heavy document). The body carries max_bytes.
422source_unreadableFile of the right type, but the engine cannot open it: corrupt, truncated, or non-standard structure. The body carries engine.
429rate_limitedRate 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.
429quota_exceededQuota 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.
500conversion_failedThe engine failed. The body carries job_id, status: "failed" and, when identified, engine. Quota is refunded: our own breakdowns are not billed.
500internal_errorBreakdown outside conversion. No detail is published — 5xx responses are masked server-side.
503server_busyConversion queue saturated (retry within 5 s — Retry-After header)
504conversion_timeoutEngine 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.