Skip to content

Asset requests

Collect a defined set of files from people outside your workspace through one expiring link, then review each file before it lands in a bucket.

On this page

An asset request (an "upload form" in the dashboard) is a link you send to a client, partner, or contributor so they can upload the files you asked for, without an account and without seeing anything else in your workspace. Each file they send waits in temporary storage until someone in your workspace approves it into a bucket, uses it to replace an existing file, or rejects it. This guide covers building the request, what the sender experiences, and how review decisions work.

To get a new version of one specific file, a replacement request is simpler. To accept uploads continuously from your own website, use the upload widget.

Plan the request#

A request has a name, optional instructions, an expiration, and between 1 and 25 requested files (called fields in the API). Each requested file is one slot the sender fills with one upload, and each has its own rules:

RuleAPI fieldNotes
LabellabelWhat the sender sees, such as "Hero image". Up to 200 characters, cannot be blank.
DescriptiondescriptionOptional per-file instructions, up to 2,000 characters.
RequiredrequiredDefaults to true. The sender cannot submit until every required file is uploaded.
Accepted typesacceptedTypesUp to 20 entries. Each is an exact MIME type (application/pdf), a wildcard (image/*), or an extension with its dot (.ai). A file passes if it matches any entry. An empty list accepts any type.
Size limitmaxBytesPer-file maximum. It cannot exceed your plan's upload size limit, otherwise creating the request fails with 413 and the code upload_size_limit.
Destination bucketbucketIdWhere an approved file goes. Each requested file can target a different bucket.
Replacement targetreplacementAssetIdOptional. When set, approving this file publishes it as a new revision of that existing file instead of creating a new one.

Plan upload size limits are 100 MB on Free, 250 MB on Personal, 500 MB on Pro, 2 GB on Business, and 5 GB on Enterprise. The limit is checked again when the sender uploads, so a request created on a higher plan uses the current plan's limit if the workspace changes plan.

The expiration must be in the future and no more than one year away. After it passes, the link stops accepting uploads.

Create the request#

In the dashboard#

  1. Open upload forms

    Open Requests, switch to Upload forms, and choose New upload form.

  2. Describe what you need

    Enter a form name (for example "Q3 partner logos"), optional instructions, the Requested item label, and an Expires date. The link stays open until the end of that day.

  3. Set the rules

    Choose the destination Bucket, the accepted file types, and Max size (MB).

  4. Share the link

    Choose Create, then Copy link on the new row.

The dashboard creates a request with one requested file. For several files with different rules or destinations, create the request through the API.

With the API#

curl -X POST https://api.steadylink.io/api/request-forms \
  -H "X-API-Key: $STEADYLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Northwind launch package",
    "instructions": "Use the final approved exports. Vector logos only.",
    "expiresAt": "2026-10-31T23:59:59Z",
    "files": [
      {
        "label": "Primary logo",
        "description": "SVG or AI, transparent background.",
        "required": true,
        "acceptedTypes": [".svg", ".ai"],
        "maxBytes": 26214400,
        "bucketId": "0c9e4b1a-6d2f-4a7e-b3c5-8f1d2e3a4b5c"
      },
      {
        "label": "Hero image",
        "required": true,
        "acceptedTypes": ["image/*"],
        "maxBytes": 104857600,
        "bucketId": "0c9e4b1a-6d2f-4a7e-b3c5-8f1d2e3a4b5c",
        "replacementAssetId": "3f2a9c1e-7b4d-4e8a-9c21-5d6f0a1b2c3d"
      },
      {
        "label": "Press release",
        "required": false,
        "acceptedTypes": ["application/pdf", ".docx"],
        "maxBytes": 10485760,
        "bucketId": "7a2d9f40-1b3c-4e5d-8f6a-0b1c2d3e4f50"
      }
    ]
  }'
201 CreatedResponse
{
  "id": "e2b6c8d4-5f1a-4c7e-9a3b-6d0e1f2a3b4c",
  "token": "pX3kV9qL...",
  "publicPath": "/request/pX3kV9qL..."
}

Validation you may run into:

  • 422 "Every destination must be a bucket in this workspace" when a bucketId is wrong.
  • 422 "Every replacement target must be an asset in this workspace" when a replacementAssetId is wrong.
  • 422 "Expiration must be within the next year".
  • 409 with the code feature_limit_reached when the workspace already has its maximum number of open requests (see Limits).

Unlike replacement links, the request link can be read again later: GET /api/request-forms returns each form's publicPath.

What the sender sees#

The link opens a page at https://steadylink.io/request/{token} with your request name, instructions, and one drop zone per requested file. The sender can add their name and email (both optional), uploads each file, and chooses Send for approval. The page checks type and size before uploading and explains a mismatch ("This file type is not accepted", or the size limit in readable units). If a required file is missing, it names the missing items.

Each file goes straight to temporary storage over a presigned URL that is valid for 15 minutes. If an upload is interrupted, starting it again for the same slot discards the partial upload. Once a file has finished uploading, its slot is filled for that submission and another upload for it returns 409 "This requested file has already been uploaded". After submitting, the sender can choose Request more files to send another set through the same link, which creates a separate submission.

The page tells the sender that uploads stay in temporary storage until they are approved. Nothing is added to your buckets, and no existing file changes, until you decide.

Public route limits#

The public routes are rate-limited per IP address to protect your workspace from abuse:

ActionLimit
Open the request120 per minute
Start a submission20 per hour
Start a file upload60 per hour
Confirm a file upload60 per hour
Submit30 per hour

Uploads also count against the workspace's temporary intake capacity, so a sender can be refused when the workspace is out of room.

Review submissions#

Submitted files appear in Requests > Review queue, newest first, with who sent them, which request and requested item they belong to, and their size. Select a file to preview it (images, audio, video, PDF, plain text, CSV, and JSON preview inline; SVG and HTML never do, for safety) or download it, then choose Approve or Reject.

In the API, GET /api/request-forms/submissions lists the latest 200 submissions with their files, and each file is decided with POST /api/request-forms/files/{file_id}/decision. There are three actions:

actionWhat happens
approveSaves the file. With a replacement target (from the requested file, or replacementAssetId in the decision), it becomes a new revision of that file and keeps its stable link. Otherwise it is added as a new file in the destination bucket, inside a requests/ folder.
rejectDeletes the temporary upload. The file status becomes rejected.
assignChanges the destination bucket without approving. A later approve uses the assigned bucket.

The bucket used on approval is, in order of preference: bucketId in the decision, the bucket set by an earlier assign, then the requested file's own bucket.

Terminal
# Route a file to another bucket, then approve it
curl -X POST https://api.steadylink.io/api/request-forms/files/9d4c2a71-0e3b-4f6a-8c1d-2b3e4f5a6b7c/decision \
  -H "X-API-Key: $STEADYLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "action": "assign", "bucketId": "7a2d9f40-1b3c-4e5d-8f6a-0b1c2d3e4f50" }'

curl -X POST https://api.steadylink.io/api/request-forms/files/9d4c2a71-0e3b-4f6a-8c1d-2b3e4f5a6b7c/decision \
  -H "X-API-Key: $STEADYLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "action": "approve", "note": "Approved for the launch campaign" }'
200 OKResponse
{ "status": "approved", "assetId": "5b8e1f2a-3c4d-4e6f-9a0b-1c2d3e4f5a6b" }

Details that matter during review:

  • Names never overwrite. A new file approved into requests/ gets a unique name if one with the same name exists (logo.svg becomes logo-2.svg).
  • Replacements must match the bucket. The replacement target has to live in the bucket used for approval; otherwise approval fails with 422 "Replacement asset is not in the selected bucket".
  • Decisions are final. Approving or rejecting a file that is no longer waiting returns 409 "This file is not awaiting approval".
  • Notes are recorded. The optional note (up to 2,000 characters) is stored with the decision, along with who decided and when.
  • Edit with PUT /api/request-forms/{form_id}, sending the full request. You can always change the name, instructions, and expiration of an open request. The requested files themselves can only change until the first submission arrives; after that, changing them returns 409 "File requirements cannot change after responses begin". Include each existing requested file's id to keep it.
  • Generate a new link with Generate new link in the dashboard or POST /api/request-forms/{form_id}/rotate. The old link stops working immediately.
  • Revoke with Revoke in the dashboard or DELETE /api/request-forms/{form_id}. The link returns 410 Gone with "This request link was revoked". Files already received stay in the review queue.

Only open requests (not revoked, not expired) can be edited or given a new link.

Expiry#

An expired link returns 410 Gone with "This request link has expired" and accepts nothing. SteadyLink then cleans up: temporary files from that request that were never decided are deleted and marked expired, so review anything you need before the expiration. Members who have the link.expiring notification turned on are told when an open request has less than 24 hours left (see Notifications).

Limits#

PlanOpen requests at once
Free2
Personal10
Pro50
Business250
Enterprise2,500

Only requests that are neither revoked nor expired count. Revoking one frees a slot immediately.

Creating, editing, and deciding requires a workspace role that can edit files (member, admin, or owner) or an API key with assets:write. Listing requests and submissions, previewing, and downloading need assets:read.

Next steps#