API key safety
Give each integration the least access it needs, expire and rotate keys without downtime, and revoke them the moment something leaks.
On this page
API keys and S3 app keys let software act on your workspace without a person signing in. They can do anything their permissions allow and never ask for a second factor, so they deserve the same care as a password. This page is for the developers and admins who create and look after those keys.
Two kinds of key#
| Workspace API key | S3 app key | |
|---|---|---|
| Looks like | slk_ followed by a random string | An access key ID starting with SL, plus a 40-character secret |
| Belongs to | The workspace | The person who created it |
| Permissions | Its scopes | The creator's current role, optionally read-only and limited to one bucket |
| Created in | Dashboard > Developers > New API key, or the API, by an owner or admin | The API, by any member for themselves |
| Limit | Per plan: 1 active key on Free, 2 on Personal, 10 on Pro, 50 on Business | 20 active keys per person per workspace |
| Works for | The REST API, SDKs, CLI, GitHub Action | S3-compatible tools |
Both are shown in full only once, when they are created. SteadyLink stores only a hash of an API key, so a lost key cannot be shown again; create a new one.
Least privilege#
Give each integration its own key with only the scopes it uses:
| Integration | Scopes |
|---|---|
| A build job that publishes release files | assets:write, plus assets:read if it looks files up |
| A website that renders file lists or creates signed links on the server | assets:read; add assets:write only if it creates signed links |
| A reporting job | analytics:read |
| Infrastructure code that manages webhooks, delivery policy, or SSO | workspace:admin |
A request outside a key's scopes returns 403 with the code insufficient_scope and the scope it needed:
{ "detail": { "code": "insufficient_scope", "required": "assets:write" } }The dashboard starts a new key with Read files only; tick more permissions as needed. Keys created through the API without a scopes list get assets:read and assets:write, so always pass scopes explicitly.
Separate keys also make incidents smaller. If one integration's key leaks, you revoke that key and nothing else breaks.
Never put a key in a browser or an app#
Anything shipped to a browser, a mobile app, or a desktop app can be read by its users. A key in client-side JavaScript, a public repository, or a mobile bundle should be treated as already leaked.
To let people upload from a browser, keep the key on your server and hand the browser only short-lived presigned upload URLs, or use the embeddable upload widget. See Browser uploads.
Store keys in a secret manager or a protected environment variable such as STEADYLINK_API_KEY. In GitHub Actions, store them as encrypted repository or environment secrets.
Send one credential per request#
Send a key in the X-API-Key header:
curl https://api.steadylink.io/api/assets/ \
-H "X-API-Key: $STEADYLINK_API_KEY"A request that carries both X-API-Key and Authorization: Bearer is rejected with 400, so a proxy or SDK that adds a user's session cannot silently change which identity a request runs as.
Expiry#
Keys created through the API can expire. Pass expiresInDays, from 1 to 365:
curl -X POST https://api.steadylink.io/api/workspaces/current/api-keys \
-H "Authorization: Bearer $STEADYLINK_ACCESS_TOKEN" \
-H "X-Workspace-Id: $STEADYLINK_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "name": "Release pipeline", "scopes": ["assets:read", "assets:write"], "expiresInDays": 90 }'Managing keys requires a signed-in owner or admin; an API key cannot create, list, or revoke keys, even with workspace:admin.
An expired key returns 401 API key expired or revoked. Expiring keys force rotation to happen on a schedule rather than only after an incident. Keys created in the dashboard do not expire; revoke them when they are no longer needed.
Rotate without downtime#
Create the replacement key
Create a new key with the same scopes and a name that says what it replaces, such as
Release pipeline 2026-10.Deploy it
Update the secret wherever the old key is used and redeploy.
Confirm the old key is idle
In Dashboard > Developers, the old key's Used time (
lastUsedAtin the API) stops moving once nothing uses it. It is updated at most every five minutes, so wait a little longer than that.Revoke the old key
Revoke it in Dashboard > Developers. Anything still using it fails with
401straight away.
Rotation needs one free key slot. On Free, which allows a single active key, revoke the old key and create the new one in quick succession, and expect a short gap.
Revoke immediately when a key leaks#
If a key appears in a log, a commit, a screenshot, or a chat:
- Revoke it in Dashboard > Developers (open the key's actions and choose Revoke key), or with
DELETE /api/workspaces/current/api-keys/{key_id}. Revocation takes effect on the next request. - Create a replacement with the same scopes and deploy it.
- Review recent activity for actions you do not recognize. Key creation and revocation are recorded there.
Removing a commit from Git history does not make a pushed key safe again. Revoke first.
Offboarding and single sign-on#
API keys belong to the workspace, not to the person who created them. When someone leaves, their workspace API keys keep working until an admin revokes them. Review the key list when people change roles or leave.
Single sign-on enforcement applies to people signing in, not to keys. A workspace that requires SSO still accepts its API keys and S3 app keys. Keep that in mind when you turn enforcement on: existing keys remain a way in until you revoke them.
S3 app keys#
S3 app keys follow the person who created them:
- SteadyLink checks the creator's membership and role on every S3 request. Remove the person from the workspace, or change them to viewer, and their keys lose that access immediately.
- Create keys as read-only for backup and sync tools that only need to read, and limit a key to one bucket when the tool works on one bucket.
- The secret is shown once. Each person can list and revoke only their own keys, with
GET /api/s3-keysandDELETE /api/s3-keys/{key_id}.
See the S3-compatible API for setup.