S3-compatible API
Connect rclone, restic, Cyberduck, the AWS CLI, boto3, and NAS backup apps to SteadyLink buckets with app keys and AWS Signature Version 4.
On this page
SteadyLink buckets speak the Amazon S3 protocol, so tools built for S3 can sync, back up, and browse them. Use this page to create an app key, configure your tool, and understand where SteadyLink behaves differently from Amazon S3.
Files written through S3 follow the same rules as dashboard uploads: plan upload limits, storage quota, malware scanning, workspace encryption, and audit logs. A PUT to a key that already exists adds a new revision behind the same stable link, so you can sync a folder with rclone and every published https://cdn.steadylink.io/a/{asset_id} link keeps serving the latest file.
Connection details#
| Setting | Value |
|---|---|
| Endpoint | https://api.steadylink.io/s3 (path style) |
| Dedicated host | https://s3.steadylink.io, for tools that only accept a bare host (restic, Cyberduck, many NAS apps) |
| Region | auto. Any region name is accepted; it only has to match what the client signs with. |
| Addressing | Path style: https://api.steadylink.io/s3/{bucket}/{key}. Virtual-host style ({bucket}.s3.steadylink.io) works only on the dedicated host. |
| Signature | AWS Signature Version 4, in the Authorization header or as presigned query parameters. Version 2 is not supported. |
| Bucket name | The bucket's slug, or its ID when it has no S3-compatible slug. rclone lsd or aws s3 ls shows the names. |
| Key | The file path inside the bucket, for example photos/2024/beach.jpg. |
App keys#
S3 tools authenticate with an app key: an access key ID that starts with SL and a 40-character secret. App keys are separate from workspace API keys.
- An app key belongs to one user in one workspace and acts with that user's role. Removing the user from the workspace, or lowering their role, takes effect on the next request.
- Each user can have up to 20 active app keys per workspace.
- The secret is shown only in the response that creates the key. Store it right away.
- App keys bypass workspace SSO enforcement, in the same way workspace API keys do. Revoke a person's keys when they leave.
Create an app key#
/api/s3-keysApp keys are currently created through the API, with a signed-in user's session token (Authorization: Bearer). Workspace API keys cannot create them, because an app key acts as a person; a request with X-API-Key returns 403. A dashboard screen for app keys is coming. Send X-Workspace-Id if you belong to more than one workspace.
Body
namestringRequired- A label, 1 to 200 characters, for example
NAS backup. readOnlybooleanDefaultfalse- The key can list and download, but every write returns
AccessDenied. Viewers can create read-only keys; a read-write key needs a role that can write files. bucketIduuid- Limit the key to one bucket.
ListBucketsthen returns only that bucket, and the key cannot create or delete buckets. An unknown bucket returns404.
curl -X POST https://api.steadylink.io/api/s3-keys \
-H "Authorization: Bearer $STEADYLINK_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "NAS backup", "readOnly": false, "bucketId": "8b1f6c2e-4a7d-4f0e-b3c9-2d5e7a9f1c04"}'{
"id": "5c2e9a1f-3b7d-4e8a-9f0c-1d2b3a4c5e6f",
"name": "NAS backup",
"accessKeyId": "SLQ4M7ZK2XB5N3TJ6WRA",
"readOnly": false,
"bucketId": "8b1f6c2e-4a7d-4f0e-b3c9-2d5e7a9f1c04",
"createdAt": "2026-10-08T09:30:12.418211",
"lastUsedAt": null,
"revokedAt": null,
"secretAccessKey": "q7Xb2LwP9sKd4RmT1vYc8NfJ3hZa6GeU5oWi0BtE",
"endpoint": "https://api.steadylink.io/s3",
"region": "auto",
"forcePathStyle": true
}Creating a twenty-first active key returns 409 with the code s3_key_limit_reached.
List your app keys#
/api/s3-keysReturns your active keys in the current workspace, newest first, as { "items": [...], "endpoint", "region", "forcePathStyle" }. Each item has the same fields as the create response without secretAccessKey. lastUsedAt is updated at most every five minutes.
Revoke an app key#
/api/s3-keys/{key_id}Returns { "revoked": true }. You can revoke your own keys; workspace admins can revoke anyone's, for offboarding. Revoking an already revoked key also returns { "revoked": true }. Creation and revocation are recorded in the audit log as workspace.s3_key_create and workspace.s3_key_revoke.
Set up your tool#
The examples use the access key ID SLXXXXXXXXXXXXXXXXXX, the secret YOUR_SECRET, and a bucket named my-bucket.
To install rclone and create this remote in one step, see Connect rclone in one command.
Create the remote from the command line:
rclone config create steadylink s3 \
provider=Other \
access_key_id=SLXXXXXXXXXXXXXXXXXX \
secret_access_key=YOUR_SECRET \
endpoint=https://api.steadylink.io/s3 \
region=auto \
force_path_style=trueOr add it to rclone.conf:
[steadylink]
type = s3
provider = Other
access_key_id = SLXXXXXXXXXXXXXXXXXX
secret_access_key = YOUR_SECRET
endpoint = https://api.steadylink.io/s3
region = auto
force_path_style = trueThen:
rclone lsd steadylink: # list buckets
rclone copy ~/Pictures steadylink:my-bucket/pictures
rclone sync ~/Documents steadylink:my-bucket/documents
rclone check ~/Documents steadylink:my-bucket/documentsrclone stores modification times in X-Amz-Meta-Mtime and, for multipart uploads, the MD5 in X-Amz-Meta-Md5chksum. Both are kept with each revision, so sync and check work without re-uploading. Changing only a modification time (rclone touch, or a sync where only the time differs) updates the current revision's metadata and does not create a new revision.
Keep the chunk size at 5 MiB or more; rclone's default is fine.
restic, and other tools built on minio-go, only accept a bare host, so use the dedicated host:
export AWS_ACCESS_KEY_ID=SLXXXXXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET
export RESTIC_REPOSITORY=s3:https://s3.steadylink.io/my-bucket/restic
restic init -o s3.region=auto
restic backup ~/Documents -o s3.region=autoEach restic pack file becomes a file in the bucket and counts against your storage quota like any other upload. Keep the repository in its own folder (or its own bucket with a bucket-limited key) so it stays out of the way of files you publish.
- Choose Open Connection and pick Amazon S3.
- Set Server to
s3.steadylink.ioand Port to443. - Enter the access key ID and the secret.
If s3.steadylink.io is not reachable from your network, use the S3 (HTTPS) connection profile with server api.steadylink.io and path /s3 instead.
Folders you create in Cyberduck are stored as folder markers, the same way the dashboard stores empty folders.
aws configure set default.s3.addressing_style path
aws --endpoint-url https://api.steadylink.io/s3 --region auto s3 ls s3://my-bucket/
aws --endpoint-url https://api.steadylink.io/s3 --region auto s3 cp ./report.pdf s3://my-bucket/reports/report.pdfUse a named profile (--profile steadylink) with the app key as aws_access_key_id and aws_secret_access_key so it does not replace your AWS credentials.
import boto3
from botocore.config import Config
s3 = boto3.client(
"s3",
endpoint_url="https://api.steadylink.io/s3",
region_name="auto",
aws_access_key_id="SLXXXXXXXXXXXXXXXXXX",
aws_secret_access_key="YOUR_SECRET",
config=Config(s3={"addressing_style": "path"}),
)
s3.upload_file("report.pdf", "my-bucket", "reports/report.pdf")
for item in s3.list_objects_v2(Bucket="my-bucket", Prefix="reports/").get("Contents", []):
print(item["Key"], item["Size"])NAS backup apps usually ask for a server address rather than a URL: enter s3.steadylink.io, choose a custom S3-compatible provider, and keep HTTPS on.
Supported operations#
| Operation | Notes |
|---|---|
| ListBuckets, HeadBucket, GetBucketLocation | The location is empty, so clients fall back to their configured region. |
| CreateBucket | Creates a bucket whose slug is the S3 name. Names are global and must be 3 to 63 lowercase letters, digits, dots, or hyphens. A taken name returns BucketAlreadyExists. |
| DeleteBucket | Only when the bucket is empty (BucketNotEmpty otherwise). |
| ListObjects (v1) and ListObjectsV2 | prefix, delimiter (CommonPrefixes for folders), max-keys, marker, start-after, continuation tokens, and encoding-type=url. |
| HeadObject, GetObject | A single Range, If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since, response-* overrides, and presigned URLs. |
| PutObject | Streaming, Content-MD5 and x-amz-content-sha256 checks, aws-chunked uploads (signed chunks and checksum trailers), and x-amz-meta-* metadata up to 2 KB in total. |
| CopyObject | Server-side, with x-amz-metadata-directive COPY or REPLACE. Clients use it for renames. |
| DeleteObject, DeleteObjects | Deleting a key removes the file and all of its revisions, as in the dashboard. Its stable link stops working. |
| Multipart upload | CreateMultipartUpload, UploadPart, ListParts, CompleteMultipartUpload, AbortMultipartUpload, and ListMultipartUploads. |
| GetObjectAcl, PutObjectAcl, GetBucketAcl | Accepted for client compatibility, but they change nothing. Access is controlled by app keys only. |
ETags#
The ETag is the MD5 of the whole file, including for multipart uploads, where Amazon S3 would return an {md5-of-parts}-{N} value instead. Tools that compare ETags to local MD5 sums therefore work for every file.
Revisions created before the S3 API existed have an ETag ending in -1, which tells clients that it is not an MD5. Tools then fall back to size and modification time, or to the X-Amz-Meta-Md5chksum value rclone stores.
Errors#
Errors use the standard S3 XML body, with codes such as NoSuchKey, NoSuchBucket, AccessDenied, SignatureDoesNotMatch, InvalidRange, and EntityTooLarge. Error responses carry an x-amz-request-id header and the same ID in RequestId.
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>QuotaExceeded</Code><Message>...</Message><Resource>/s3/my-bucket/video.mp4</Resource><RequestId>7d3c1f2e-...</RequestId></Error>Two cases are specific to SteadyLink:
| Code | HTTP | Meaning |
|---|---|---|
QuotaExceeded | 403 | The workspace's storage or delivery allowance is used up. It is a 403 so clients fail fast instead of retrying. Free space or upgrade, then run the job again. |
AccessDenied with a message starting Upload blocked | 403 | Malware was found in the file (Upload blocked: malware detected), or the bucket's file-type or size policy rejected it (Upload blocked by bucket policy). Retrying the same file will fail again. |
A read-only key that tries to write, or a bucket-limited key that touches another bucket, also gets AccessDenied.
Limits and rate limits#
- The largest object is the plan's maximum upload size, and storage counts against the plan quota. See Usage and limits.
- Multipart parts are limited to 5 GiB each. Every part except the last must be at least 5 MiB and all parts except the last must be the same size; rclone and the AWS tools already do this.
- Downloads count against the workspace delivery allowance, like
/alinks. - Each app key can make 1,200 reads and 600 writes per minute. Requests over the limit get
SlowDown(HTTP 503), which S3 clients retry with backoff. Failed signature checks are limited per client IP address.
Known limitations#
- No versioning API.
versionIdand ListObjectVersions are not supported. Revisions exist and are visible in the dashboard and the revisions API, but S3 always reads and writes the current one. - No bucket policies, CORS, lifecycle rules, tagging, object lock, or SSE-C. ACL calls are accepted and ignored.
- No UploadPartCopy or
GetObject?partNumber. - Large files in memory. Committing a file reads it into memory on the API server, as dashboard commits do. In workspaces with encryption, a
Rangerequest decrypts the whole object first, so reading parts of very large files is slow. - Malware scanning timing. With synchronous scanning an infected upload is rejected. With asynchronous scanning the upload succeeds and the object later becomes unreadable (
AccessDenied) if the scan finds malware. Objects still waiting for a scan can be read with an app key. - Key rules. Keys cannot start with
/or contain//,.or..segments, or control characters. Folder paths and file names are limited to 512 characters each (KeyTooLongError). - Content checks. A file whose bytes clearly contradict its declared
Content-Typeis rejected, as in the dashboard. - Abandoned multipart uploads expire after 7 days.
- Empty folders. Folders created in the dashboard appear in listings only while they have a folder marker. rclone does not create empty folders on S3 remotes.