Upload lifecycle
The states an upload passes through, from upload session to scanned revision, and every check that can stop a file along the way.
On this page
An upload is not finished when the bytes arrive. SteadyLink checks the size, the real file type, your bucket's rules, and the file's contents for malware before the file becomes a revision anyone can download. This page walks through each stage so you can build reliable upload code, read upload status correctly, and explain to a user why a file did not go through.
The three steps#
Every API upload follows the same three steps. The dashboard, the TypeScript SDK's upload(), and the CLI run them for you.
Create an upload batch
POST /api/upload-batcheswith the destination bucket and up to 100 files. For each file you send its name, an optional folder path, and its exact size in bytes. SteadyLink checks each file against your plan's size limit, reserves storage for it, and returns one upload session per file with a presigneduploadUrl.Send the bytes
PUTthe raw file to the session'suploadUrlwithContent-Type: application/octet-stream. This request goes straight to object storage, not to the SteadyLink API, so it does not use your API key and does not count as an API operation.Complete the session
POST /api/upload-sessions/{session_id}/complete. SteadyLink answers202 Acceptedright away and finalizes the file in the background. PollGET /api/upload-batches/{batch_id}or listen onGET /api/events/streamuntil the session reaches a terminal state.
The Uploads API reference has full request and response examples. The browser uploads guide shows how to run the second step from a user's browser without exposing your key.
Session states#
status | Meaning | Terminal |
|---|---|---|
created | The session exists and is waiting for bytes and completion. | No |
uploaded | Bytes were streamed through the API instead of the presigned URL. | No |
committing | Completion was requested. SteadyLink is checking and storing the file. | No |
scanning | The file is stored as a revision and the malware scan is running. | No |
ready | The file passed every check and is available under its visibility settings. | Yes |
blocked | A bucket policy or the malware scanner rejected the file. Nothing is delivered. | Yes |
failed | Finalization could not complete. The error object says why. | Yes |
cancelled | The session was cancelled before it finished. | Yes |
A batch's own status is active while any session is still moving, then complete if every file is ready, or partial if any file ended blocked or failed.
What is checked, and where#
Checks run in this order. The first one that fails stops the file.
When the batch is created#
- Size is required. A file without
sizeis rejected with422 upload_size_required. SteadyLink needs the size up front to enforce limits and reserve storage. - Plan file size limit. A file larger than your plan allows is rejected with
413 upload_size_limit. Uploads made with an API key are also held to the API plan's upload limit, whichever is lower. See Usage and limits. - Storage. Each file's size is reserved against your storage allowance while the session is open. If the workspace would go over, the batch is refused. Reservations are released when a session is cancelled, expires, fails, or is blocked.
- Batch size. More than 100 files in one batch is rejected with
422. Split larger sets into several batches.
During finalization#
- The bytes arrived. If nothing was uploaded to the presigned URL, or the temporary bytes expired, the session ends
failedwithupload_missing. - Exact size. The stored bytes must match the declared
size. A different size endsfailedwithupload_size_mismatch; a file over the limit endsfailedwithupload_too_large. - Real file type. SteadyLink reads the bytes to detect what the file really is, and stores and serves the detected type. When a request declares a specific type that belongs to a different family than the detected one (for example a "PNG" that is really a PDF), the file is rejected with
415 MIME mismatch. A bucket can turn this check off with itsblock_mime_mismatchsetting. - Bucket policy. A bucket can set a maximum upload size in MB (
max_upload_mb), allowed MIME type prefixes such asimage/(allowed_mime_prefixes), and allowed filename extensions such aspdf(allowed_extensions). The type prefix is compared with the detected type, not the name. A file that breaks any of these endsblockedwith the error codepolicyand a message such asFile type not allowed by bucket policy. See Buckets and folders.
After the revision is stored#
- Malware scan. Every revision is scanned with ClamAV. A clean result moves the session to
ready. A detection moves it toblockedwithmalware_detected, and the infected revision can never be delivered or restored.
If the file was a replacement, the new revision becomes current as soon as it is stored, before the scan finishes. While the scan runs, the file's stable link answers 423 Locked rather than serve unscanned bytes. See The scan window after a replacement.
Time limits#
| Limit | Value | What happens when it passes |
|---|---|---|
Presigned uploadUrl | 15 minutes from batch creation | Object storage refuses the PUT. Create a new batch. |
Upload session (expiresAt) | Shown on each session; 24 hours by default | Completing returns 410 Upload session expired, the session ends failed with upload_expired, and its storage reservation is released. |
Start the PUT promptly after creating the batch. The 15-minute window covers starting the request; for large files on slow connections, create the batch just before you upload rather than queuing many batches ahead of time.
Completing safely more than once#
Completion is designed to be retried. Send an Idempotency-Key header with a value that is stable for the session, such as upload- followed by the session ID:
curl -X POST "https://api.steadylink.io/api/upload-sessions/$SESSION_ID/complete" \
-H "X-API-Key: $STEADYLINK_API_KEY" \
-H "Idempotency-Key: upload-$SESSION_ID"- If the session already reached a terminal state, the call returns that state again instead of doing anything.
- If the session is still
committing, the call returns409 Upload is already committing. Treat this as "in progress" and poll the batch. - If the session was completed earlier with a different idempotency key, the call returns
409 Idempotency key mismatch.
So a client that times out can always repeat the same completion call. It never creates a second revision.
If background finalization hits a temporary problem, SteadyLink retries it on its own, with the session showing committing and the error code commit_retrying. After repeated failures the session ends failed with commit_failed and its storage reservation is released.
Cancelling#
DELETE /api/upload-sessions/{session_id} cancels a session that has not finished and releases its storage reservation. Sessions that already ended ready, blocked, or failed cannot be cancelled and return 409.