Switch from CloudConvert and keep your code
switchfile accepts CloudConvert v2 jobs. Change the base URL and the API key; request bodies and responses keep their structure. Webhooks are set per job with webhook_url. Webhooks for the whole account, set in the CloudConvert dashboard, do not exist at switchfile. Once paid plans start, plans with 2,000, 10,000 or 50,000 credits a month cost 50 to 54% less than CloudConvert subscriptions of the same size. The table below shows other volumes too, including those where CloudConvert costs less.
- https://api.cloudconvert.com/v2/jobs+ https://api.switchfile.io/v2/jobs
# Same request body, same response,
# same signed webhooks.Who the switch is for
You call the v2 API from your own code
You pay CloudConvert for credits every month
Your files should stay in the EU
You want to try it without paying
What stays the same
What changes
Prices by volume
CloudConvert prices as shown to a visitor from Germany, ours from our pricing page. Both include 19% VAT. CloudConvert counts credits per minute of conversion time with a base cost per type, for example 2 for Office to PDF. At switchfile: Credits follow the conversion time: one credit per started minute. Each conversion costs at least 1 credit, and converting Office files or ebooks to PDF costs at least 2 credits.
Limits to know first
What CloudConvert offers that we do not, from its pages and from our comparison of both format lists on 7 November 2026. Option names and defaults that differ are in the migration guide.
- Archives as output in rar, tar, tar.gz and tar.bz2.CloudConvert format list (API), compared with ours, checked 7 November 2026
- Drawings and CAD: dwg and dxf as output, and ai, cdr, eps and ps as input.CloudConvert format list (API), compared with ours, checked 7 November 2026
- Apple Pages, Numbers and Keynote files as input, and PDF to Excel or PowerPoint.CloudConvert format list (API), compared with ours, checked 7 November 2026
- Webhooks for the whole account, set in the dashboard or through /v2/webhooks.CloudConvert webhook documentation, checked 7 November 2026
- Options for each pair in the format list (include=options), and video options such as watermarks and subtitles.CloudConvert API documentation, checked 7 November 2026
- No file size limit on paid plans.CloudConvert pricing, checked 7 November 2026
- A processing region in the USA, next to the one in Germany.CloudConvert API documentation, checked 7 November 2026CloudConvert privacy policy, checked 7 November 2026
- ISO 27001 certification, as stated on its security page.CloudConvert security, checked 7 November 2026
Move in three steps
- Step 1
Create a key
- Step 2
Change the base URL and the key
- Step 3
Set webhooks per job
# was: https://api.cloudconvert.com/v2/jobs and your CloudConvert key. Needs jq. RES=$(curl -sS https://api.switchfile.io/v2/jobs \ -H "Authorization: Bearer $SWITCHFILE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tasks": { "import": { "operation": "import/url", "url": "https://example.com/talk.mkv" }, "convert": { "operation": "convert", "input": "import", "output_format": "mp4" }, "export": { "operation": "export/url", "input": "convert" } } }') # Errors come back as {"message", "code"} with the HTTP status, as at CloudConvert. JOB_ID=$(echo "$RES" | jq -er .data.id) || { echo "$RES" | jq -r '"\(.code): \(.message)"' >&2; exit 1; } # /wait answers when the job ends, or after a server-side timeout with the job still running. RES=$(curl -sS --max-time 360 https://api.switchfile.io/v2/jobs/$JOB_ID/wait \ -H "Authorization: Bearer $SWITCHFILE_API_KEY") case $(echo "$RES" | jq -r .data.status) in finished) echo "$RES" | jq -r '.data.tasks[] | select(.name == "export") | .result.files[0].url' ;; error) echo "$RES" | jq -r '.data.tasks[] | select(.status == "error") | "\(.name) failed: \(.code) \(.message)"' >&2; exit 1 ;; waiting|processing) echo "Job $JOB_ID is still running: call /wait again or send webhook_url" >&2; exit 1 ;; *) echo "$RES" | jq -r '"\(.code): \(.message)"' >&2; exit 1 ;; esac
// was: https://api.cloudconvert.com/v2 and your CloudConvert key const BASE = "https://api.switchfile.io/v2"; const headers = { Authorization: `Bearer ${process.env.SWITCHFILE_API_KEY}`, "Content-Type": "application/json", }; async function api(path, init = {}) { // /wait answers when the job ends or after a server-side timeout; allow a little longer here. const res = await fetch(`${BASE}${path}`, { ...init, headers, signal: AbortSignal.timeout(360_000) }); const body = await res.json().catch(() => ({})); // Errors come back as { message, code } with the HTTP status, as at CloudConvert. if (!res.ok) throw new Error(`${res.status} ${body.code}: ${body.message}`); return body.data; } const job = await api("/jobs", { method: "POST", body: JSON.stringify({ tasks: { import: { operation: "import/url", url: "https://example.com/talk.mkv" }, convert: { operation: "convert", input: "import", output_format: "mp4" }, export: { operation: "export/url", input: "convert" }, }, }), }); const done = await api(`/jobs/${job.id}/wait`); if (done.status === "error") { const failed = done.tasks.find((t) => t.status === "error"); throw new Error(`Task ${failed?.name} failed: ${failed?.code} ${failed?.message}`); } if (done.status !== "finished") { // Timed out while the job is still running: call /wait again, or send webhook_url with the job. throw new Error(`Job ${done.id} is still ${done.status}`); } const file = done.tasks.find((t) => t.name === "export").result.files[0]; console.log(file.filename, file.url);
<?php // composer require guzzlehttp/guzzle // was: https://api.cloudconvert.com/v2/ and your CloudConvert key $http = new GuzzleHttp\Client([ 'base_uri' => 'https://api.switchfile.io/v2/', 'headers' => ['Authorization' => 'Bearer ' . getenv('SWITCHFILE_API_KEY')], // /wait answers when the job ends or after a server-side timeout; allow a little longer here. 'timeout' => 360, ]); function fail(string $message) { fwrite(STDERR, $message . PHP_EOL); exit(1); } try { $job = json_decode($http->post('jobs', ['json' => ['tasks' => [ 'import' => ['operation' => 'import/url', 'url' => 'https://example.com/talk.mkv'], 'convert' => ['operation' => 'convert', 'input' => 'import', 'output_format' => 'mp4'], 'export' => ['operation' => 'export/url', 'input' => 'convert'], ]]])->getBody(), true)['data']; $done = json_decode($http->get("jobs/{$job['id']}/wait")->getBody(), true)['data']; } catch (GuzzleHttp\Exception\GuzzleException $e) { // HTTP errors carry {"message", "code"} as at CloudConvert; timeouts and network errors have no response. $res = $e instanceof GuzzleHttp\Exception\RequestException ? $e->getResponse() : null; $body = $res ? json_decode($res->getBody(), true) : null; fail('Request failed: ' . (isset($body['code']) ? "{$body['code']}: {$body['message']}" : $e->getMessage())); } if ($done['status'] === 'error') { foreach ($done['tasks'] as $task) { if ($task['status'] === 'error') fail("Task {$task['name']} failed: {$task['code']} {$task['message']}"); } } if ($done['status'] !== 'finished') { // Timed out while the job is still running: call /wait again, or send webhook_url with the job. fail("Job {$done['id']} is still {$done['status']}"); } foreach ($done['tasks'] as $task) { if ($task['name'] === 'export') echo $task['result']['files'][0]['url'], PHP_EOL; }
import os import requests # was: https://api.cloudconvert.com/v2 and your CloudConvert key BASE = "https://api.switchfile.io/v2" headers = {"Authorization": f"Bearer {os.environ['SWITCHFILE_API_KEY']}"} def api(method, path, **kwargs): # /wait answers when the job ends or after a server-side timeout; allow a little longer here. # A timeout on our side raises requests.Timeout. r = requests.request(method, f"{BASE}{path}", headers=headers, timeout=360, **kwargs) if not r.ok: # Errors come back as {"message", "code"} with the HTTP status, as at CloudConvert. try: err = r.json() except ValueError: err = {} raise RuntimeError(f"{r.status_code} {err.get('code')}: {err.get('message')}") return r.json()["data"] job = api("POST", "/jobs", json={"tasks": { "import": {"operation": "import/url", "url": "https://example.com/talk.mkv"}, "convert": {"operation": "convert", "input": "import", "output_format": "mp4"}, "export": {"operation": "export/url", "input": "convert"}, }}) done = api("GET", f"/jobs/{job['id']}/wait") if done["status"] == "error": failed = next(t for t in done["tasks"] if t["status"] == "error") raise RuntimeError(f"Task {failed['name']} failed: {failed['code']} {failed['message']}") if done["status"] != "finished": # Timed out while the job is still running: call /wait again, or send webhook_url with the job. raise RuntimeError(f"Job {done['id']} is still {done['status']}") export = next(t for t in done["tasks"] if t["name"] == "export") print(export["result"]["files"][0]["url"])
Questions
About moving from CloudConvert.
Do I have to rewrite my integration?
No, if it uses the v2 jobs API. Change the base URL and the API key. If you use webhooks, add webhook_url to your jobs and check the options you send against the migration guide.
Can I keep using the official CloudConvert SDK?
The Python SDK takes endpoint and sync_endpoint settings in cloudconvert.configure(), so you can point it at https://api.switchfile.io and https://api.switchfile.io/sync. The Node.js and PHP SDKs have no setting for the base URL; call the API over HTTP as in the examples on this page. Links to the SDK source are in the sources below.
What happens to options you do not support?
We ignore them instead of rejecting the job, so the file converts with our defaults. The migration guide lists the options we do not read yet.
Can I test before I switch?
Yes. Create a sandbox key in the dashboard. Sandbox jobs run like live jobs and never use credits.
Can I buy a plan today?
Paid plans are not open yet. The prices shown apply once they start. Until then, use the free daily quota and a free API key.
How did you calculate the price difference?
We put each CloudConvert subscription step in the table, from 500 to 100,000 credits a month, next to our cheapest plan with at least as many credits and compared the monthly prices. At 2,000, 10,000 or 50,000 credits both include the same credits, and ours costs 50 to 54% less a month. At 1,000, 5,000 or 25,000 credits ours costs 5 to 15% less a month and includes more credits. At 500, 3,000 or 15,000 credits CloudConvert costs less a month, because our plan includes more credits than the step. Above 50,000 credits a month we have no plan. Per credit, ours costs 50 to 62% less at every step we have a plan for. CloudConvert's price slider has more steps in between. The one-time packages of 1,000 credits differ by 44%. Both sides include 19% VAT. Both charge by conversion time and charge more for some types (at switchfile at least 2 credits for Office to PDF), so check with your own mix of files.
Where are my files processed?
In the EU. Files and results are deleted after 24 hours.