Revisions
List the revisions behind a stable link, label them, roll back by promoting an earlier revision, and delete revisions you no longer need.
On this page
Every time a file gets new bytes, SteadyLink keeps the old bytes as a revision and makes the new one current. The file's asset ID and link never change; the link simply serves whichever revision is current. Use these endpoints to see a file's history, annotate it, undo a bad replacement in one request, and clean up revisions that take up storage.
Revisions belong to a file, so every route here takes the file's asset ID (the ID in https://cdn.steadylink.io/a/{asset_id}), not the bucket ID. Revisions are addressed by their versionNumber: 1 for the first upload, then 2, 3, and so on.
The JavaScript examples assume const steadylink = new SteadyLink({ apiKey: process.env.STEADYLINK_API_KEY! }) and the Python examples assume client = SteadyLink(api_key=os.environ["STEADYLINK_API_KEY"]).
How revisions are created#
A new revision is created when:
- you replace a file,
- an upload lands on a key that already has a file,
- someone approves a replacement request or an upload widget submission for the file.
The new revision becomes current. The previous ones stay retained, and each counts toward the workspace's storage until you delete it. Revisions are immutable: you can label them, promote them, or delete them, but never change their bytes.
A revision is delivered only after its malware scan is clean. While a current revision is still scanning, the link answers 423. See Delivery.
| Endpoint | Method and path | Scope |
|---|---|---|
| List revisions | GET /api/assets/{asset_id}/versions | assets:read |
| Label a revision | PATCH /api/assets/{asset_id}/versions/{version} | assets:write |
| Promote a revision | POST /api/assets/{asset_id}/versions/{version}/promote | assets:write |
| Delete a revision | DELETE /api/assets/{asset_id}/versions/{version} | assets:write |
Operations#
List revisions#
/api/assets/{asset_id}/versionsassets:readReturns every retained revision, newest first, as a JSON array. There is no pagination.
curl "https://api.steadylink.io/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions" \
-H "X-API-Key: $STEADYLINK_API_KEY"const revisions = await steadylink.listVersions("3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71");
const current = revisions.find((revision) => revision.isCurrent);revisions = client.request("GET", "/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions")
current = next(r for r in revisions if r["isCurrent"])steadylink versions 3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71[
{
"versionNumber": 4,
"storageKey": "assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions/4/hero-v2.webp",
"byteSize": 2950112,
"width": 2400,
"height": 1600,
"mime": "image/webp",
"hash": "c47e1d9a0b3f62e8d5a9f1c0b7e4d2a68f3c9e1b0d7a5f2c8e6b4d1a9f0c3e7b",
"id": "5d8a2f1c-7e3b-4a90-b6c4-e0f9d2a1b738",
"label": "Launch approved",
"note": "Final color correction from the agency",
"createdBy": null,
"isCurrent": true,
"scanStatus": "clean",
"metadataStatus": "complete",
"createdAt": "2026-10-08T15:10:44.391027"
},
{
"versionNumber": 3,
"storageKey": "assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions/3/hero.webp",
"byteSize": 2841024,
"width": 2400,
"height": 1600,
"mime": "image/webp",
"hash": "9b2e6f0c4a7d18e3b5f2c9a0d6e4b7f1a3c8e2d5f0b9a6c4e7d1f3b8a2c5e9d0",
"id": "e4d7b1a9-2c6f-4083-9a5e-1b8d3f0c6e27",
"label": null,
"note": null,
"createdBy": "1a7c4e92-8b3d-4f06-a5e1-c2d9b0f7e384",
"isCurrent": false,
"scanStatus": "clean",
"metadataStatus": "complete",
"createdAt": "2026-10-01T09:02:18.775310"
}
]Response fields
versionNumberinteger- The revision's number. Use it in
?v=delivery URLs and in the routes below. iduuid- The revision's own ID. Webhooks and some responses call it
revisionId. isCurrentbooleantruefor the revision the stable link serves. Exactly one revision is current while any exist.hashstring- SHA-256 of the bytes. Compare hashes to see whether two revisions are identical.
byteSizeinteger- Stored size. Every retained revision counts toward storage.
width, heightinteger | null- Pixel dimensions for images, otherwise
null. label, notestring | null- Your annotations. See Label a revision.
createdByuuid | null- The user who created the revision.
nullfor revisions created with an API key, through an upload batch, or by an automated workflow. scanStatusstring | nullpending,clean,infected, orerror.nullwhen scanning is off.metadataStatusstringpending,processing,complete, orerror.
A file ID from another workspace returns 403, and an unknown ID returns 404 Asset not found. A bucket ID returns an empty array.
Label a revision#
/api/assets/{asset_id}/versions/{version}assets:writeSets the human-readable label and note on one revision, for example to record who approved it. Both fields are written on every call: a field you omit is cleared. Send the current value of the other field if you only want to change one.
Body
labelstring | null- Short label, up to 120 characters, such as
Launch approved. Empty or whitespace-only clears it. notestring | null- Longer note, up to 1000 characters. Empty or whitespace-only clears it.
curl -X PATCH "https://api.steadylink.io/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions/4" \
-H "X-API-Key: $STEADYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "label": "Launch approved", "note": "Final color correction from the agency" }'await steadylink.updateVersion("3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71", 4, {
label: "Launch approved",
note: "Final color correction from the agency",
});client.request("PATCH", "/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions/4", {
"label": "Launch approved",
"note": "Final color correction from the agency",
}){
"id": "5d8a2f1c-7e3b-4a90-b6c4-e0f9d2a1b738",
"versionNumber": 4,
"label": "Launch approved",
"note": "Final color correction from the agency"
}A label longer than 120 characters or a note longer than 1000 returns 400 Validation error. A revision number that does not exist returns 404 Revision not found.
Promote a revision#
/api/assets/{asset_id}/versions/{version}/promoteassets:writeMakes an existing revision current. This is how you roll back: promote the last good revision and the stable link serves it again. Promotion does not copy bytes or create a new revision, so it is instant and uses no extra storage. You can promote forward again later; nothing is lost.
curl -X POST "https://api.steadylink.io/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions/3/promote" \
-H "X-API-Key: $STEADYLINK_API_KEY"await steadylink.promoteVersion("3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71", 3);
// rollback() is an alias
await steadylink.rollback("3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71", 3);client.request("POST", "/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions/3/promote")steadylink rollback 3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71 3{
"promoted": true,
"versionNumber": 3,
"revisionId": "e4d7b1a9-2c6f-4083-9a5e-1b8d3f0c6e27"
}| Status | Message | Cause |
|---|---|---|
404 | Revision not found | No revision with that number. Revision numbers are per file; list them first. |
409 | Only clean revisions can be promoted | The revision's scan is still pending, or it ended infected or error. Only clean revisions can become current. |
After a promotion, SteadyLink's own lookups switch immediately, but browsers and shared caches can keep serving the previous revision for up to five minutes. URLs pinned with ?v= are unaffected. See Caching.
To make a revision current at a future time, with an optional automatic rollback, use scheduled revisions.
Delete a revision#
/api/assets/{asset_id}/versions/{version}assets:writePermanently deletes one revision's bytes and its cached image variants, and releases its storage.
Query parameters
forcebooleanDefaultfalse- Required to delete the current revision. Without it, deleting the current revision returns
400 Cannot delete current version without force=true.
curl -X DELETE "https://api.steadylink.io/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions/2" \
-H "X-API-Key: $STEADYLINK_API_KEY"await steadylink.deleteVersion("3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71", 2);
// The current revision needs force
await steadylink.deleteVersion("3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71", 4, { force: true });client.request("DELETE", "/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/versions/2"){ "deleted": true }What happens next depends on which revision you delete:
- An older revision. It disappears from the list. URLs pinned to it with
?v=return404once caches expire. - The current revision, with
force=true. The highest-numbered remaining revision becomes current, even if it is not the one that was current before. Promote a different one afterwards if needed. - The last remaining revision. The file itself is deleted: its listing, its asset ID, and its link all go away, as with Delete a file.
An unknown revision number returns 404 Version not found.