Replacement requests
Send someone outside your workspace a link to upload a new version of one file, review it, then publish it now or at a time you choose.
On this page
A replacement request is a single-purpose upload link for one existing file. You send it to an agency, a designer, or a colleague without a SteadyLink account; they upload a candidate; nothing changes on your live link until someone in your workspace approves it. Approval can publish immediately, at a scheduled time, or with an automatic rollback later. This guide walks through the whole loop and the API calls behind it.
Use a replacement request when you know which file needs updating and want the new version to keep the same stable link. To collect several new files at once, use an asset request instead.
How it works#
- You create a request for one file. SteadyLink returns a secret link of the form
https://steadylink.io/replace/{token}. - The recipient opens the link, sees the file name, your workspace name, and your note, and uploads a candidate. They cannot see the bucket, other files, or earlier revisions.
- The candidate sits in temporary storage. The live file is untouched.
- You approve, reject, or revoke. Approval creates a new revision of the file behind the same delivery URL, either live right away or on a schedule.
A request moves through these statuses:
| Status | Meaning |
|---|---|
pending | Link created, nothing uploaded yet. |
submitted | A candidate is waiting for review. The recipient can still upload a different file, which replaces the candidate. |
approved | The candidate was published as the current revision. |
scheduled | The candidate was approved for a future publish time. |
rejected | The candidate was discarded. The link no longer works. |
revoked | You cancelled the request. The link no longer works. |
Create a request#
Open replacement links
In the dashboard, open Requests, switch to Replacement links, and choose New replacement link.
Pick the file and add context
Choose the file to replace. Optionally add a recipient name and a note, such as "Please send the final hero image at 2400 px wide." Both are shown to the recipient on the upload page.
Copy the link
Choose Create, then copy the link from the row. The dashboard creates links that last 7 days.
Through the API you can set the lifetime yourself with expiresInDays, from 1 to 30 (default 7). The response contains the secret token and sharePath exactly once.
curl -X POST https://api.steadylink.io/api/assets/3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d/replacement-requests \
-H "X-API-Key: $STEADYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipientName": "Avery at Northwind Studio",
"note": "Please send the final hero image at 2400 px wide.",
"expiresInDays": 14
}'const request = await steadylink.request<{ id: string; sharePath: string }>(
"/api/assets/3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d/replacement-requests",
{
method: "POST",
body: JSON.stringify({
recipientName: "Avery at Northwind Studio",
note: "Please send the final hero image at 2400 px wide.",
expiresInDays: 14,
}),
},
);
const link = `https://steadylink.io${request.sharePath}`;request = client.request(
"POST",
"/api/assets/3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d/replacement-requests",
{
"recipientName": "Avery at Northwind Studio",
"note": "Please send the final hero image at 2400 px wide.",
"expiresInDays": 14,
},
)
link = "https://steadylink.io" + request["sharePath"]{
"id": "c41f7e0a-9b2d-4a63-8e15-7d0c2b9a6f48",
"assetId": "3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d",
"recipientName": "Avery at Northwind Studio",
"note": "Please send the final hero image at 2400 px wide.",
"status": "pending",
"filename": null,
"byteSize": null,
"mime": null,
"expiresAt": "2026-10-22T14:10:03.118204",
"submittedAt": null,
"decidedAt": null,
"createdAt": "2026-10-08T14:10:03.118204",
"token": "q8XbN2vR...",
"sharePath": "/replace/q8XbN2vR..."
}The file must be stored in a bucket; a request for a file that is not connected to one returns 409.
Lost the link#
SteadyLink stores only a hash of the token, so it cannot show you the link again. Instead you generate a new one: in the dashboard, choose Generate new link from the row's menu; through the API, call POST /api/assets/{asset_id}/replacement-requests/{request_id}/rotate-link. The old link stops working the moment the new one is created, so send the new link to the recipient. Only pending and submitted requests that have not expired can get a new link.
What the recipient sees#
The /replace/ page names your workspace and the file ("Northwind requested a replacement for hero.webp"), shows your note and the recipient name, and offers a drop zone. The page tells the recipient that the current public file stays untouched until the upload is approved. After uploading they see File sent for approval, and they can choose Submit a different file until you make a decision; each new upload replaces the previous candidate.
Things that can go wrong for the recipient:
- Request unavailable. The link expired, was revoked, or the candidate was rejected. The public routes return
410 Gonein all three cases and404for a token that never existed. - File too large. A single candidate can be up to 2 GiB.
- Size mismatch. SteadyLink compares the uploaded bytes with the size the browser declared. An interrupted upload fails with "Uploaded file size does not match" and the recipient can try again.
Review the candidate#
The Replacement links tab lists the latest requests in the workspace with their status. Approving, rejecting, and scheduling are done through the API. List an asset's requests with GET /api/assets/{asset_id}/replacement-requests (latest 50) or the whole workspace with GET /api/assets/replacement-requests (latest 200); a request ready for review has status: "submitted" and shows the candidate's filename, byteSize, and mime.
Approve and publish now#
curl -X POST https://api.steadylink.io/api/assets/3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d/replacement-requests/c41f7e0a-9b2d-4a63-8e15-7d0c2b9a6f48/approve \
-H "X-API-Key: $STEADYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'{ "approved": true, "version": 7, "scheduledJobId": null }The candidate becomes revision 7 and the delivery URL starts serving it. Everything that points at the file, including following-current collection items, picks it up.
Approve and publish later#
Send publishAt to keep the current revision live until a specific time. Add rollbackAt to restore today's revision automatically afterwards, which suits a sale banner or an event poster.
curl -X POST https://api.steadylink.io/api/assets/3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d/replacement-requests/c41f7e0a-9b2d-4a63-8e15-7d0c2b9a6f48/approve \
-H "X-API-Key: $STEADYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"publishAt": "2026-11-27T05:00:00Z",
"rollbackAt": "2026-12-01T05:00:00Z"
}'{ "approved": true, "version": 7, "scheduledJobId": "5e9a0c3b-2d7f-4b18-a6e4-91c0d8f2b7a5" }The rules for times:
publishAtmust be more than 10 seconds in the future and no more than one year away.rollbackAtmust be afterpublishAt.- Send times with an explicit offset, ideally UTC with a trailing
Z.
A time outside these limits returns 422 with "Publish time must be at least 10 seconds from now", "Publish time must be within one year", or "Rollback time must be after the publish time".
With a schedule, the new revision is created right away and appears in the file's revision history, but the current revision does not change until publishAt. At that moment SteadyLink makes it current, warms the delivery cache for public files, and, if you set rollbackAt, queues a second job that restores the revision that was current when you approved. The request's status becomes scheduled.
You can also send only rollbackAt: the candidate goes live now and the previous revision comes back at rollbackAt (which must be at least 10 seconds ahead and within a year).
A scheduled revision only goes live once it has passed malware scanning. If scanning has not finished at publishAt, the job retries until it has.
Reject or revoke#
- Reject (
POST .../reject) discards asubmittedcandidate and closes the request. Use it when the upload is wrong; create a new request if you still need a file. Rejecting a request that has no candidate returns409. - Revoke (
DELETE /api/assets/{asset_id}/replacement-requests/{request_id}) closes the request in any state and deletes a waiting candidate. The dashboard's Revoke action does this.
In both cases the temporary upload is deleted and the link returns 410 Gone.
Manage scheduled publications#
Scheduled publications are jobs attached to the file, whether they came from a replacement request or from scheduling an existing revision directly with POST /api/assets/{asset_id}/scheduled-revisions:
# Schedule revision 12 of a file to become current, with no rollback
curl -X POST https://api.steadylink.io/api/assets/3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d/scheduled-revisions \
-H "X-API-Key: $STEADYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "version": 12, "publishAt": "2026-11-27T05:00:00Z", "rollbackAt": null }'
# List queued and running publications for the file
curl https://api.steadylink.io/api/assets/3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d/scheduled-revisions \
-H "X-API-Key: $STEADYLINK_API_KEY"
# Cancel one
curl -X DELETE https://api.steadylink.io/api/assets/3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d/scheduled-revisions/5e9a0c3b-2d7f-4b18-a6e4-91c0d8f2b7a5 \
-H "X-API-Key: $STEADYLINK_API_KEY"The list returns each job's id, version, publishAt, rollbackAt, and status (queued or processing). A job can be cancelled only while it is queued; once the worker has picked it up, cancelling returns 409 with "This scheduled revision can no longer be cancelled". Cancelling the publish job also prevents its rollback, because the rollback is only queued when the publication runs. To stop a rollback that is already queued, list the jobs again after publishAt and cancel the rollback job.
The revision you schedule must still exist at publishAt. If it has been deleted by then, or the file itself has been deleted, the job fails permanently and the current revision stays as it is. Scheduling a revision number that does not exist returns 404 Revision not found straight away.
Permissions#
Creating, approving, rejecting, revoking, and scheduling require a workspace role that can edit files (member, admin, or owner) or an API key with assets:write. Listing requests and schedules needs assets:read. The public /api/replacement-requests/{token} routes the recipient's browser uses are authorized by the token alone and never see your API key.