Skip to content

Files and folders

List, search, and inspect files, create and move folders, rename files, change visibility, replace a file's bytes without changing its link, and delete files.

On this page

Use these endpoints to work with what is inside a bucket: browse folders, find a file and its stable link, move it, make it public or private, publish new bytes under the same link, or delete it. To add new files, use Uploads.

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 files are identified#

A file has three identifiers. Mixing them up is the most common source of 404 responses.

IdentifierExampleWhat it isUsed by
Asset ID3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71The file's permanent identity. It appears in the delivery URL https://cdn.steadylink.io/a/{asset_id} and never changes, even when the file is renamed, moved, or replaced. Listings call it objectAssetId.Revisions, signed links, delivery, Get a file by asset ID
Keycampaign/launch/hero.webpThe file's path inside its bucket. It changes when you rename or move the file.Rename, visibility, replace, delete, Get a file by key
Listing IDa91d3c70-...The id of a row in a folder listing. Internal bookkeeping.Rarely needed

Keys are bucket-relative, have no leading slash, and use / between folders. Send them URL-encoded in query strings.

Endpoints#

OperationMethod and pathScope
List a folderGET /api/assets/{bucket_id}/objectsassets:read
List every file in the workspaceGET /api/assets/objectsassets:read
Get a file by asset IDGET /api/assets/object/{asset_id}/statassets:read
Get a file by keyGET /api/assets/{bucket_id}/objects/statassets:read
SearchGET /api/assets/_searchassets:read
Create a folderPOST /api/assets/{bucket_id}/foldersassets:write
Rename or move a folderPOST /api/assets/{bucket_id}/folders/renameassets:write
Delete a folderDELETE /api/assets/{bucket_id}/foldersassets:write
Rename or move a filePOST /api/assets/{bucket_id}/objects/renameassets:write
Set file visibilityPOST /api/assets/{bucket_id}/objects/visibilityassets:write
Create a replacement uploadPOST /api/assets/{bucket_id}/objects/upload-tempassets:write
Finish a replacementPOST /api/assets/{bucket_id}/objects/replaceassets:write
Delete a fileDELETE /api/assets/{bucket_id}/objectsassets:write

Browse and inspect#

List a folder#

GET/api/assets/{bucket_id}/objects
Requiresassets:read

Returns the folders and files directly inside one folder, not its subfolders' contents. Files are sorted by name. There is no pagination: the whole folder is returned at once.

Query parameters

prefixstring
Folder path, for example campaign/launch/. A trailing slash is added if missing and a leading slash is removed. Omit it for the bucket root.
rev_onlybooleanDefault false
Return only prefix and revision. Use it to poll cheaply for changes.
curl "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/objects?prefix=campaign/" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{
  "prefix": "campaign/",
  "folders": [
    { "name": "launch", "path": "campaign/launch/" }
  ],
  "items": [
    {
      "id": "a91d3c70-4e2b-4f18-9c6d-7b0e5a2f8c13",
      "objectAssetId": "3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71",
      "key": "campaign/hero.webp",
      "name": "hero.webp",
      "size": 2841024,
      "lastModified": "2026-10-08T14:40:12.551930",
      "visibility": "public",
      "contentType": "image/webp"
    }
  ],
  "revision": "5c0f2b8e9a7d41c3b6e0f9a2d8c7b1e4f3a6d509"
}

Response fields

items[].objectAssetIduuid | null
The file's asset ID, used in its stable link.
items[].visibilitystring
public or private. When the file has no explicit visibility, the bucket's privacy is reported.
items[].sizeinteger
Size of the current revision in bytes.
revisionstring | null
A hash of the folder's contents. It changes when a file in this folder is added, replaced, renamed, or removed, or a subfolder appears or disappears.

Files come straight from the database, so new and changed files appear immediately. Folder entries that exist only in storage are cached for up to three seconds. Compare revision values to decide whether to re-render a file browser.

List every file in the workspace#

GET/api/assets/objects
Requiresassets:read

Returns every file in every bucket of the workspace as a flat list, without walking folders. Use it for inventories, audits, and sync jobs.

Query parameters

limitintegerDefault 1000
Files per page, 1 to 1000. Values outside the range return 400.
cursoruuid
The nextCursor from the previous page. An invalid value returns 422 Invalid cursor.
includeFoldersbooleanDefault false
Also return every folder path in every bucket. This scans storage and is slower on large workspaces; request it once rather than on every page.
curl "https://api.steadylink.io/api/assets/objects?limit=500" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{
  "items": [
    {
      "id": "a91d3c70-4e2b-4f18-9c6d-7b0e5a2f8c13",
      "objectAssetId": "3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71",
      "bucketId": "7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15",
      "bucketName": "Marketing",
      "key": "campaign/hero.webp",
      "name": "hero.webp",
      "parent": "campaign/",
      "size": 2841024,
      "lastModified": "2026-10-08T14:40:12.551930",
      "visibility": "public",
      "contentType": "image/webp"
    }
  ],
  "nextCursor": null,
  "folders": []
}

nextCursor is null on the last page. Items are ordered by listing ID, not by name or date.

Get a file by asset ID#

GET/api/assets/object/{asset_id}/stat
Requiresassets:read

Returns a file's bucket, key, and details of its current revision, or of one revision with v. This is the most reliable way to go from a stable link back to the file.

Query parameters

vinteger
Revision number to describe. Omit it for the current revision.
curl "https://api.steadylink.io/api/assets/object/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/stat" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{
  "bucketId": "7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15",
  "key": "campaign/hero.webp",
  "id": null,
  "objectAssetId": "3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71",
  "size": 2841024,
  "contentType": "image/webp",
  "etag": "9b2e6f0c4a7d18e3b5f2c9a0d6e4b7f1a3c8e2d5f0b9a6c4e7d1f3b8a2c5e9d0",
  "lastModified": "2026-10-08T14:40:12.551930",
  "metadata": {
    "image": { "width": 2400, "height": 1600, "format": "WEBP", "mode": "RGB", "animated": false, "frames": 1 }
  },
  "metadataStatus": "complete",
  "assetVersionId": "e4d7b1a9-2c6f-4083-9a5e-1b8d3f0c6e27",
  "scanStatus": "clean",
  "scanEngine": "clamav",
  "scanAt": "2026-10-08T14:40:15.002461",
  "scanDetails": null
}

Response fields

etagstring
SHA-256 of the revision's bytes. The same value is the delivery ETag.
metadataobject
Metadata extracted after upload. For raster images it has an image object (width, height, format, color mode, animation) and, when present, up to 100 exif tags. Other file types get an empty object.
metadataStatusstring
pending or processing until extraction has run, then complete, or error if the file could not be read.
scanStatusstring | null
pending, clean, infected, or error. null when scanning is off. Only clean revisions are delivered.
assetVersionIduuid
ID of the revision described.

A v that does not exist, or an ID that is not a file, returns 404 Not found.

Get a file by key#

GET/api/assets/{bucket_id}/objects/stat
Requiresassets:read

Looks a file up by its path and returns its asset ID, size, and storage metadata.

Query parameters

keystringRequired
Bucket-relative key, for example campaign/hero.webp.
exifbooleanDefault false
For images, add EXIF tags and image_width, image_height, and image_format to metadata.
curl "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/objects/stat?key=campaign%2Fhero.webp" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{
  "key": "campaign/hero.webp",
  "id": "a91d3c70-4e2b-4f18-9c6d-7b0e5a2f8c13",
  "objectAssetId": "3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71",
  "size": 0,
  "contentType": "application/octet-stream",
  "etag": "\"d41d8cd98f00b204e9800998ecf8427e\"",
  "lastModified": "2026-10-08T14:52:03+00:00",
  "metadata": { "asset-id": "3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71" }
}

This route reads the file's entry in bucket storage, not its revision. For files whose bytes live in revisions, the entry is a small marker, so size, contentType, and etag describe the marker rather than the file. Rely on objectAssetId from this response, then call Get a file by asset ID for the real size, type, and scan state. When no storage entry exists for the key, the route returns 404 Not found even if the file appears in listings; in that case find the asset ID from List a folder.

GET/api/assets/_search
Requiresassets:read

Finds files by name or path, buckets by name, PDFs by their extracted text, and recent workspace activity.

Query parameters

qstringRequired
Search text. Matching is case-insensitive and finds the text anywhere in the name or path. An empty value returns no results.
scopestringDefault org
org searches the whole workspace. bucket searches one bucket and skips bucket names and activity.
bucket_iduuid
Required when scope=bucket. Missing returns 400.
limitintegerDefault 20
Maximum results, clamped to 1 to 50.
curl
curl "https://api.steadylink.io/api/assets/_search?q=hero&limit=10" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{
  "query": "hero",
  "scope": "org",
  "items": [
    {
      "type": "object",
      "assetId": "3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71",
      "bucketId": "7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15",
      "name": "hero.webp",
      "path": "campaign/hero.webp",
      "visibility": "public"
    },
    {
      "type": "content",
      "assetId": "8d0b4e2f-6a19-4c73-b5e8-0f2d7a9c1b46",
      "bucketId": "7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15",
      "name": "brand-guide.pdf",
      "path": "guides/brand-guide.pdf",
      "version": 3,
      "snippet": "...place the hero image above the fold on every landing page..."
    }
  ]
}

Results have a type: object (a file whose name or path matches), bucket, content (text inside a PDF, with the revision version and a snippet), or event (an activity entry with createdAt). Exact name matches come first, then prefix matches, then other matches, then content and events.

Folders#

Folders are paths. A file's folder is the part of its key before the last /, and a folder exists as long as a file uses it or it was created explicitly. You do not need to create a folder before uploading into it: an upload with path: "campaign/launch/" creates the path.

Create a folder#

POST/api/assets/{bucket_id}/folders
Requiresassets:write

Creates an empty folder so it shows up in listings before any file is added.

Query parameters

pathstringRequired
Folder path, for example campaign/launch. Leading slashes are removed and a trailing slash is added.
curl -X POST "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/folders?path=campaign/launch" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "created": true }

Rename or move a folder#

POST/api/assets/{bucket_id}/folders/rename
Requiresassets:write

Moves a folder and everything under it to a new path. The files keep their asset IDs and stable links; only their keys change. POST /api/assets/{bucket_id}/folders/move is an alias with the same parameters.

Query parameters

from_pathstringRequired
Current folder path, for example campaign/launch.
to_pathstringRequired
New folder path, for example archive/2026/launch.
overwritebooleanDefault false
Accepted for compatibility. Do not rely on the route to protect an existing destination: check that to_path is empty or new before you move.
curl
curl -X POST "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/folders/rename?from_path=campaign/launch&to_path=archive/2026/launch" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "renamed": true, "moved": 1 }

moved counts storage entries that were copied, which can differ from the number of files. Moving a folder into its own subtree, such as campaign into campaign/old, returns 400.

Delete a folder#

DELETE/api/assets/{bucket_id}/folders
Requiresassets:write

Query parameters

pathstringRequired
Folder path to remove, for example campaign/old.
curl
curl -X DELETE "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/folders?path=campaign/old" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "deleted": true, "objectsDeleted": 4 }

Removes the folder and every file listing under it, including subfolders. objectsDeleted counts storage entries removed, not files. It does not delete the files' revisions, so their storage is not released. When you want files gone completely, delete each file first, then the folder.

Change a file#

Rename or move a file#

POST/api/assets/{bucket_id}/objects/rename
Requiresassets:write

Changes a file's key. Use a key in another folder to move it. The asset ID, stable link, and revisions do not change, so nothing that links to the file breaks.

Query parameters

from_keystringRequired
Current key, for example campaign/hero.webp.
to_keystringRequired
New key, for example campaign/launch/hero.webp. If a file already uses it, the request returns 409 Destination already exists.
curl -X POST "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/objects/rename?from_key=campaign%2Fhero.webp&to_key=campaign%2Flaunch%2Fhero.webp" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "renamed": true }

The route answers {"renamed": true} even when no file has from_key. If you are not sure the key exists, check it in a listing first.

Set file visibility#

POST/api/assets/{bucket_id}/objects/visibility
Requiresassets:write

Makes one file public, private, or dependent on its bucket.

Query parameters

keystringRequired
Bucket-relative key.
visibilitystringRequired
public serves the file to anyone with the link. private requires a signed link. inherit follows the bucket's privacy. Anything else returns 400.
curl -X POST "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/objects/visibility?key=campaign%2Fhero.webp&visibility=private" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "updated": true }

Public responses may already be cached by browsers and shared caches for up to five minutes. Making a file private stops new public responses right away but cannot recall copies that were already cached. See Caching.

Replace a file#

Replacing a file publishes new bytes as the next revision of the same asset. The asset ID and every link to it stay the same, and the previous revisions are kept so you can roll back. This takes three requests: reserve a temporary upload, send the bytes, and finish.

import { fileFromPath } from "@steadylink/sdk/node";

// By asset ID...
const result = await steadylink.replace(
  "3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71",
  await fileFromPath("./hero-v2.webp"),
);
// ...or by bucket and key
await steadylink.replace(
  { bucketId: "7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15", key: "campaign/hero.webp" },
  await fileFromPath("./hero-v2.webp"),
);
console.log(result.version, result.url); // new revision number, same link

Create a replacement upload#

POST/api/assets/{bucket_id}/objects/upload-temp
Requiresassets:write

Returns a presigned URL for the new bytes and a temporary key that identifies them.

Query parameters

sizeintegerRequired
Exact size of the new file in bytes. Larger than the plan's upload limit returns 413 with the code upload_size_limit.
content_typestringDefault application/octet-stream
The file's type, recorded for your audit trail. The stored type is detected from the bytes when you finish.
curl
curl -X POST "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/objects/upload-temp?size=2950112&content_type=image%2Fwebp" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{
  "uploadUrl": "https://storage-endpoint.example/uploads/temp/52e8a1c7-...?X-Amz-Signature=...",
  "tempKey": "uploads/temp/52e8a1c7-9d3b-4f60-8e2a-b4c1d7f09a35"
}

Then send the bytes to uploadUrl with a PUT and Content-Type: application/octet-stream. The URL is signed for that content type, so any other value fails with 403 from storage. It expires after 15 minutes. Do not send your API key to it.

curl
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @hero-v2.webp

Finish a replacement#

POST/api/assets/{bucket_id}/objects/replace
Requiresassets:write

Turns the uploaded bytes into the file's next revision and makes it current.

Query parameters

keystringRequired
Key of the file to replace, for example campaign/hero.webp.
upload_temp_keystringRequired
The tempKey from Create a replacement upload. If no bytes were uploaded to it, the request returns 400 upload_temp_key not found.
original_filenamestring
Filename of the new bytes, for example hero-v2.webp. It is used as the revision's stored filename, which delivery uses in Content-Disposition. The key does not change.
curl
curl -X POST "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/objects/replace?key=campaign%2Fhero.webp&upload_temp_key=uploads%2Ftemp%2F52e8a1c7-9d3b-4f60-8e2a-b4c1d7f09a35&original_filename=hero-v2.webp" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "replaced": true, "version": 4 }

What to expect:

  • The new revision becomes current immediately. While its malware scan is running, the stable link answers 423 Scanning. Once the scan finishes clean, the link serves the new bytes. If the scan finds malware, the link answers 403 until you promote an earlier clean revision.
  • Caches catch up within minutes. Browsers and shared caches can keep the previous bytes for up to five minutes. Links pinned with ?v= never change. See Caching.
  • Storage is reserved before the bytes are stored. If the new revision would push the workspace over its storage limit, the request returns 413. Every revision you keep counts toward storage.
  • A key with no file creates one. If nothing exists at key, the request creates a new file there with a new asset ID. Check version: 1 means a new file was created.

Delete#

Delete a file#

DELETE/api/assets/{bucket_id}/objects
Requiresassets:write

Deletes a file, every revision, and every cached image variant, and releases their storage. The stable link stops working and returns 404.

Query parameters

keystringRequired
Bucket-relative key, for example campaign/old-hero.webp.
curl -X DELETE "https://api.steadylink.io/api/assets/7c1e4b2a-5d3f-4e8a-9b61-2f0c8d4a7e15/objects?key=campaign%2Fold-hero.webp" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "deleted": true }

To delete only an old revision and keep the file, use Delete a revision.

Next steps#