File conversions
Convert images, documents, spreadsheets, audio, video, archives, and ebooks between formats, understand what each job costs, and save results into a bucket.
On this page
SteadyLink converts files between formats as temporary jobs: you upload a source (or point at a file already in your workspace), a worker converts it, and you download the result before it expires. Nothing is added to your storage unless you deliberately save a result into a bucket. This guide covers what can be converted, how the limits and credits work, and how to run conversions from the dashboard, the public converter, or the API.
Where to convert#
- Dashboard > Convert: drop several files, pick an output format per group or per file, and download results. Jobs use your workspace's plan limits and credits.
- steadylink.io/convert: the public converter. It works without an account (guest limits apply) and offers Save to workspace when you are signed in. A guest who signs up can still save a result they converted before signing in, as long as it has not expired.
- The API: create jobs with an API key or a signed-in session. Useful for converting files already stored in SteadyLink without downloading them first.
Supported formats#
A conversion is supported when the source format is an input of a group and the target is an output of the same group.
| Group | Inputs | Outputs |
|---|---|---|
| Images | JPG, JPEG, PNG, WebP, AVIF, GIF, TIFF, BMP, HEIC, ICO, SVG | JPG, PNG, WebP, AVIF, GIF, TIFF, BMP, ICO |
| Documents | DOC, DOCX, ODT, RTF, TXT, HTML | PDF, DOCX, ODT, RTF, TXT, HTML |
| Spreadsheets | XLS, XLSX, ODS, CSV | PDF, XLSX, ODS, CSV |
| Presentations | PPT, PPTX, ODP | PDF, PPTX, ODP |
| JPG, PNG, TXT, DOCX, PDF | ||
| Audio | MP3, WAV, AAC, FLAC, OGG, M4A, OPUS | MP3, WAV, AAC, FLAC, OGG, M4A, OPUS |
| Video | MP4, WebM, MOV, AVI, MKV | MP4, WebM, MOV, AVI, MKV, GIF, MP3 |
| Archives | ZIP, TAR, TAR.GZ, 7Z | ZIP, TAR, TAR.GZ, 7Z |
| Ebooks | EPUB, MOBI, AZW3, PDF, TXT | EPUB, MOBI, AZW3, PDF, TXT |
Format names are case-insensitive, a leading dot is ignored, and jpeg, tif, and tgz are treated as jpg, tiff, and tar.gz. A PDF source always uses the PDF group, so PDF to EPUB is not available. An unsupported pair is rejected with 422 and a message such as "Conversion from PNG to PDF is not supported". GET /api/conversions/catalog returns this table along with the limits for every plan, and needs no authentication.
Note that SVG and HEIC are inputs only: you can convert from them but not to them.
Options#
Options are optional and only affect the formats they apply to.
| Option | Values | Effect |
|---|---|---|
width, height | 1 to 16,384 px | Resize while keeping the aspect ratio. Give one to scale proportionally. Images are only ever scaled down. |
quality | 1 to 100, default 85 | Encoder quality for JPG, WebP, and AVIF output. Out-of-range values are clamped. |
effort | fast, balanced (default), maximum | Encoding effort for WebP and AVIF. More effort gives smaller files and takes longer. |
background | Up to 32 characters, default white | Fill color for transparent areas when the output (JPG, BMP) has no transparency. |
fps | 1 to 30, default 12 | Frame rate of GIFs made from video. GIFs default to 720 px wide. |
Any other option name is rejected with 422 "Unsupported conversion option". Converted images have their metadata stripped, and photos are rotated according to their EXIF orientation first. Video and audio outputs also drop source metadata.
Limits by plan#
Guest jobs on the public converter, without an account:
| Limit | Value |
|---|---|
| Source size | 25 MB, or 15 MB for video |
| Jobs | 10 per rolling 24 hours |
| Running at once | 2 |
| Result retention | 1 hour |
Signed-in and API-key jobs use the workspace's plan (with an active subscription; otherwise Free limits):
| Plan | Max source | Max video source | Running at once | Video running at once | Monthly credits | Monthly video credits | Max video length | Max video output | Result retention |
|---|---|---|---|---|---|---|---|---|---|
| Free | 100 MB | 50 MB | 2 | 1 | 250 | 50 | 5 min | 1920 x 1080 | 24 hours |
| Personal | 250 MB | 100 MB | 2 | 1 | 2,500 | 500 | 15 min | 1920 x 1080 | 24 hours |
| Pro | 500 MB | 250 MB | 4 | 2 | 25,000 | 5,000 | 1 hour | 3840 x 2160 | 48 hours |
| Business | 1 GB | 500 MB | 6 | 4 | 250,000 | 50,000 | 3 hours | 3840 x 2160 | 72 hours |
| Enterprise | 2 GB | 1 GB | 8 | 8 | 2,500,000 | 500,000 | 8 hours | 7680 x 4320 | 7 days |
"Running at once" counts jobs that are created, uploaded, queued, or processing, across the whole workspace. A video longer than the plan allows fails with video_duration_limit. A video output larger than the plan's maximum is not rejected: it is scaled down to fit, keeping the aspect ratio, and the job's stageDetail says so, for example "Adjusted to 1920x1080 to fit the Free plan". Enterprise contracts can raise any of these values.
When a limit is hit at creation time:
| Status | Code | Cause |
|---|---|---|
413 | conversion_size_limit, video_size_limit | Source larger than the plan allows. |
429 | conversion_concurrency_limit, video_concurrency_limit | Too many jobs running. Wait for one to finish. |
429 | conversion_daily_limit | Guest daily job limit reached. |
429 | usage_limit_exceeded | Not enough monthly credits left for the job's estimate. The response includes resetAt. |
How credits are calculated#
Credits measure the work a job causes. Regular conversions and video conversions draw from separate monthly allowances. Guest jobs do not use credits.
Before a job runs, SteadyLink reserves a conservative estimate, so a job only starts if the workspace can afford it. For audio and video the worker measures the real duration and resolution after downloading and reserves the difference if the estimate was low. When the job finishes, the reservation is replaced by the measured cost. Cancelled jobs, malware blocks, missing sources, and worker failures refund their reservation.
The measured cost of a finished job is:
credits = max(1, base estimate + output blocks + compute seconds x compute weight)- output blocks: the result size divided by 10 MB, rounded up.
- compute seconds: total job time from download to upload, rounded up to a whole second.
- compute weight: 4 for video, 2 for audio, 1 for everything else.
The base estimate depends on the group. "Input blocks" is the source size divided by 5 MB, rounded up.
| Group | Base estimate |
|---|---|
| Images | 1 + input blocks + requested output megapixels / 8, rounded up (at least 1; it is 1 when you do not set both width and height) |
| Documents | 8 + 2 per page + 2 per input block |
| 10 + 2 per page + 2 per input block | |
| Spreadsheets | 14 + 3 per page + 2 per input block |
| Presentations | 16 + 3 per page + 2 per input block |
| Ebooks | 12 + 2 per page + 2 per input block |
| Archives | 12 + 2 per file inside the archive + 3 per input block |
| Audio | 30 + 5 per started 15 seconds + 2 per input block |
| Video | 100 + 12 per started 5 seconds x resolution factor + 4 per input block |
Pages are counted for sources in the PDF group; every other document counts as one page. The video resolution factor is the output's pixel count divided by 1280 x 720, rounded up, and never less than 3 (a 1080p output, or anything smaller); a 4K output has a factor of 9.
Two worked examples:
- A 3 MB PNG converted to WebP at 1,200 px wide in under a second: 1 + 1 input block + 1 for pixels = 3, plus 1 output block and 1 compute second, for 5 credits.
- A 40-second, 60 MB 1080p MOV converted to MP4 in 25 seconds, producing 30 MB: 100 + (8 blocks x 12 x 3) + (12 input blocks x 4) = 436, plus 3 output blocks and 25 x 4 compute, for 539 video credits.
Convert with the API#
Convert a file already in SteadyLink#
Pass sourceAssetId and the job is queued immediately; there is nothing to upload. The source must have passed malware scanning (409 otherwise), must not be encrypted at rest (409 "Encrypted stored files cannot be converted in place yet"), and inputFormat must match the stored file's extension. Add sourceVersion to convert an older revision instead of the current one.
curl -X POST https://api.steadylink.io/api/conversions \
-H "X-API-Key: $STEADYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sourceAssetId": "3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d",
"inputFormat": "png",
"outputFormat": "avif",
"options": { "width": 1200, "quality": 70 }
}'const job = await steadylink.request<{ id: string; status: string }>("/api/conversions", {
method: "POST",
body: JSON.stringify({
sourceAssetId: "3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d",
inputFormat: "png",
outputFormat: "avif",
options: { width: 1200, quality: 70 },
}),
});job = client.request("POST", "/api/conversions", {
"sourceAssetId": "3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d",
"inputFormat": "png",
"outputFormat": "avif",
"options": {"width": 1200, "quality": 70},
}){
"id": "d7e9f1a3-5b6c-4d8e-a0f2-3b4c5d6e7f80",
"status": "queued",
"category": "image",
"inputFormat": "png",
"outputFormat": "avif",
"sourceName": "hero.png",
"sourceBytes": 3145728,
"outputName": null,
"outputBytes": null,
"outputMime": null,
"progress": 5,
"stage": "queued",
"stageDetail": "Waiting for conversion capacity",
"serviceTier": "priority",
"timings": {},
"error": null,
"expiresAt": "2026-10-09T15:42:10.551820",
"savedAssetId": null,
"saveUploadSessionId": null,
"registered": true,
"createdAt": "2026-10-08T15:42:10.551820"
}Convert a new file#
Send filename, size (in bytes), and the formats. The response includes an uploadUrl; PUT the exact bytes there with Content-Type: application/octet-stream, then call start.
# 1. Create the job
curl -X POST https://api.steadylink.io/api/conversions \
-H "X-API-Key: $STEADYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "filename": "brochure.docx", "size": 482113, "inputFormat": "docx", "outputFormat": "pdf" }'
# 2. Upload the source to the returned uploadUrl
curl -X PUT "$UPLOAD_URL" -H "Content-Type: application/octet-stream" --data-binary @brochure.docx
# 3. Queue it
curl -X POST https://api.steadylink.io/api/conversions/d7e9f1a3-5b6c-4d8e-a0f2-3b4c5d6e7f80/start \
-H "X-API-Key: $STEADYLINK_API_KEY"start checks that the uploaded object exists and that its size matches size exactly. A missing upload returns 400 "Upload has not completed"; a size mismatch returns 400 "Uploaded file size does not match" and refunds the credit reservation. If a browser cannot reach the storage host, it can stream the bytes through the API instead with PUT /api/conversions/{job_id}/upload.
Follow progress and download#
Poll GET /api/conversions/{job_id}. The stage moves through queued, downloading, scanning, converting, verifying, uploading, and complete, and timings reports milliseconds per stage when it finishes. A failed job has status: "failed" and an error with a code and message.
When status is complete, GET /api/conversions/{job_id}/download returns a JSON body with a url that downloads the result under its output name. The URL is valid for 15 minutes; call the route again for a fresh one. Calling it before the job completes returns 409 "Conversion is not ready".
To download several results in one file, POST /api/conversions/archive with 2 to 25 completed job IDs and "format": "zip" or "tar.gz". The combined results can be up to 1 GB.
Guest jobs and access tokens#
A job created without credentials is a guest job. Its creation response includes an accessToken, which is the only way to reach the job afterwards: send it as X-Conversion-Token on start, GET, download, save, and DELETE. Keep it for the hour the job lives. Jobs created with an API key belong to that exact key; another key in the same workspace cannot read them. Jobs created in a signed-in session belong to that user.
GET /api/conversions/recent lists the caller's unexpired, unfailed jobs (up to 100), which is how the dashboard restores its list after a reload. It requires an API key or a session.
Save a result into a bucket#
Results are deleted when they expire. To keep one, save it into a bucket:
curl -X POST https://api.steadylink.io/api/conversions/d7e9f1a3-5b6c-4d8e-a0f2-3b4c5d6e7f80/save \
-H "X-API-Key: $STEADYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"bucketId": "0c9e4b1a-6d2f-4a7e-b3c5-8f1d2e3a4b5c",
"path": "converted/2026",
"filename": "hero.avif"
}'The route answers 202 Accepted and the save runs in the background through the same pipeline as a normal upload: the file is finalized, scanned, encrypted if the bucket requires it, and given a stable link. Poll the job until savedAssetId is set; that is the new file's asset ID. path is an optional folder (.. is rejected), and filename defaults to the result's output name.
Saving uses workspace storage like any other file but no additional credits. Saving the same job twice does nothing the second time. Only the job's owner can save it, and a signed-in user needs a role that can edit files; viewers cannot save results or run conversions with workspace credits. With API keys, reading jobs needs assets:read and creating, starting, saving, and cancelling need assets:write.
Cancel or clean up#
DELETE /api/conversions/{job_id} cancels a job that has not finished and refunds its credit reservation. On a finished job, it expires the job immediately, hiding it from recent lists; its stored files are removed shortly after.