Skip to content

Private links

Decide who can open each file, share private files with expiring signed links, and revoke access before a link expires.

On this page

Every file is either public, so anyone with its link can open it, or private, so it opens only with a valid signed link. This page is for anyone sharing files that should not be open to the world: contracts, unreleased media, customer documents. You will learn how visibility is decided, how to create and revoke signed links, and how to handle them safely.

Anyone/a/3f2a…200 · public fileAnyone/a/3f2a…denied · private fileLink holder/a/3f2a…?token=…200 · until expiry
Public files open for anyone with the link. Private files return 403 without a valid token. A signed link opens a private file until it expires or is revoked.

How visibility is decided#

Each file has a visibility setting of public, private, or inherit:

SettingResult
publicAnyone with the stable link can open the file.
privateThe file opens only with a valid signed link.
inheritThe file follows its bucket's default.

New files take their visibility from the bucket's default for new files, and a bucket without its own default uses the workspace default. Admins set the workspace default under Settings > Workspace > Defaults for new files > New files start as. New workspaces default to private, so nothing becomes public unless someone chooses it.

To change one file in the dashboard, open it from Files and choose Make public or Make private. Over the API, use POST /api/assets/{bucket_id}/objects/visibility with the file's key and the new visibility; see Files API.

What a private file returns#

Without a token, or with a token that is expired, revoked, or for another file, a private file's link returns 403 Forbidden. The response does not reveal anything about the file.

Private responses are sent with Cache-Control: private, no-store, so browsers and CDNs never keep a shared copy. Every request comes back to SteadyLink and the token is checked each time.

A signed link is the file's normal stable link with a token added:

Signed link
https://cdn.steadylink.io/a/3f2a9c1e-7b4d-4e8a-9c61-2d5f0a8b7e13?token=v2.3f2a9c1e-7b4d-4e8a-9c61-2d5f0a8b7e13.1791556330.b81e4c2a-5d3f-4a9b-8e17-0c6d2f9a4b53.5e0c9a7f...

Each signed link is backed by a grant that SteadyLink stores, so it can be listed and revoked. A grant has:

  • A lifetime (ttl, in seconds). The default is 300 seconds (five minutes). Any value is clamped to between 60 seconds and 2,592,000 seconds (30 days).
  • An optional name, up to 200 characters, for your own records, such as Acme legal review. Names are visible to workspace members only.
  • An optional revision. Without one, the link follows the file's current revision, so a replacement is visible to the recipient. With one, the link opens only that revision.

In the dashboard, open a private file and choose Create private link. Pick 15 minutes, 1 hour, 1 day, or 7 days, or enter a number of seconds, then Create link and Copy private link.

From code:

curl -X POST "https://api.steadylink.io/api/assets/$ASSET_ID/signed-url?ttl=86400&name=Acme%20legal%20review" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
201 CreatedResponse
{
  "id": "b81e4c2a-5d3f-4a9b-8e17-0c6d2f9a4b53",
  "token": "v2.3f2a9c1e-7b4d-4e8a-9c61-2d5f0a8b7e13.1791556330.b81e4c2a-5d3f-4a9b-8e17-0c6d2f9a4b53.5e0c9a7f...",
  "name": "Acme legal review",
  "revisionNumber": null,
  "expiresAt": "2026-10-09T14:32:10.512000"
}

The token is returned only in this response. SteadyLink stores the grant, not a copy of the token, so keep the URL if you need to send it again. The token carries the file ID, the expiry time, and the grant ID in readable form, followed by a signature; changing any part of it makes it invalid.

Creating a signed link needs the assets:write scope.

Pass revision to make a link that opens exactly one revision, for example the version of a contract a customer is signing:

Create a revision-pinned signed link
curl -X POST "https://api.steadylink.io/api/assets/$ASSET_ID/signed-url?ttl=604800&revision=3" \
  -H "X-API-Key: $STEADYLINK_API_KEY"

A revision-pinned token works only together with the matching v parameter. Build the link as ?v=3&token=.... The same token without v=3, or with another revision, returns 403.

GET /api/assets/{asset_id}/signed-urls returns the 200 most recent grants for a file, newest first, with id, name, revisionNumber, expiresAt, revokedAt, and createdAt. Tokens are never included, so listing is safe to show in an admin screen. Listing needs assets:read.

Revoking ends access immediately, even if the link has not expired:

curl -X DELETE "https://api.steadylink.io/api/assets/$ASSET_ID/signed-urls/$GRANT_ID" \
  -H "X-API-Key: $STEADYLINK_API_KEY"

Because private responses are never cached, the next request with a revoked token returns 403. Bytes already downloaded stay with whoever downloaded them.

A signed link to a public file works, but the token is not checked: the file is open to everyone anyway. Signed links only restrict access while the file is private. If you create signed links for a public file and later make it private, those grants start to matter from that moment.

Anyone who has a signed link can open the file until it expires or is revoked. They do not need an account, and the link can be forwarded.

  • Use the shortest lifetime that works. Minutes for a download button, a day for a review, seven days for an external partner. Mint a new link when a recipient needs more time instead of starting long.
  • Create links when they are needed, on your server, for the person asking. Do not generate a long-lived link once and paste it into source code, a public page, or a support macro.
  • Name every link after who it is for, so a list of grants shows who still has access and what to revoke.
  • Revoke on change. When a contract ends, a reviewer leaves, or a link is posted somewhere it should not be, revoke the grant.
  • Watch where links go. Signed links appear in browser history, proxy logs, and the Referer of requests from pages that embed them.

To give a group access without handing out individual tokens, a collection portal with a password or an expiry can be a better fit.

Next steps#