Workspaces API
Read workspace identity and API entitlement, change workspace settings, manage members and invitations, and issue or revoke API keys.
On this page
- Who can call what
- The workspace object
- Workspaces
- List workspaces
- Get the current workspace
- Get the API entitlement
- Update workspace settings
- Create a workspace
- Delete the current workspace
- Members
- List members
- Change a member's role
- Remove a member
- Transfer ownership
- Invitations
- Invite a member
- List invitations
- Revoke an invitation
- Accept an invitation
- API keys
- List API keys
- Create an API key
- Revoke an API key
- Next steps
A workspace owns every bucket, file, member, and API key. Use these routes to check which workspace a key belongs to and what its plan allows, or to build internal admin tooling that manages members and keys.
Who can call what#
Most routes on this page change who has access to the workspace, so they deliberately accept only a signed-in person, not an API key. An API key cannot create other keys, invite people, or change roles, even if it has the workspace:admin scope. Those requests pass the scope check and then fail with 403 and "code": "insufficient_workspace_permission".
| Route | API key | Signed-in session |
|---|---|---|
GET /api/workspaces, GET /api/workspaces/current, GET /api/workspaces/current/members | workspace:read | Any member |
GET /api/workspaces/current/api-entitlement | workspace:read | Owner or admin |
PATCH /api/workspaces/current, members, invitations, API keys | Not allowed | Owner or admin |
POST /api/workspaces, accept an invitation | Not allowed | Any signed-in user |
DELETE /api/workspaces/current, transfer ownership | Not allowed | Owner only |
A signed-in session sends Authorization: Bearer <access token>. If the person belongs to more than one workspace, also send X-Workspace-Id; without it the request fails with 400 and "code": "workspace_required". API keys are bound to one workspace and do not need the header. Sending a different workspace ID with a key returns 403. See Authentication for both methods.
Admins can manage members, viewers, and invitations, but only the owner can promote, demote, invite, or remove an admin.
The workspace object#
{
"id": "6e0b2f7a-1c94-4d3b-a8e5-2f71c0d9b6a3",
"name": "Northwind Studio",
"pictureUrl": null,
"role": "admin",
"permissions": ["analytics:read", "assets:read", "assets:write", "billing:manage", "conversions:run", "workspace:admin", "workspace:read"],
"bucketCount": 6,
"encryptionEnabled": false,
"settings": {
"defaultVisibility": "private",
"requireCleanDelivery": true,
"accentTheme": "steadylink"
},
"plan": "pro",
"billingStatus": "active"
}Fields
rolestring- The caller's role:
owner,admin,member, orviewer. For an API key it isapi_key, andpermissionsis empty because a key's access comes from its scopes. permissionsstring[]- What the caller's role allows in this workspace.
bucketCountinteger- Number of buckets. Only
GET /api/workspacescounts them; the single-workspace routes always return0. encryptionEnabledboolean- Whether newly committed originals are encrypted. See Encryption.
settingsobject- Workspace defaults.
defaultVisibility(defaultprivate) andrequireCleanDelivery(defaulttrue) are always present. The object can also hold keys managed by other routes, such asdeliveryPolicy. planstring- Plan identifier, for example
hobby,personal,pro, orbusiness.
Workspaces#
List workspaces#
/api/workspacesworkspace:readFor a signed-in user, returns every workspace they belong to, sorted by name. For an API key, returns only the key's workspace.
curl https://api.steadylink.io/api/workspaces \
-H "X-API-Key: $STEADYLINK_API_KEY"{
"items": [
{
"id": "6e0b2f7a-1c94-4d3b-a8e5-2f71c0d9b6a3",
"name": "Northwind Studio",
"role": "api_key",
"permissions": [],
"bucketCount": 6,
"...": "other workspace fields"
}
]
}Get the current workspace#
/api/workspaces/currentworkspace:readReturns the workspace the key belongs to, or the one selected with X-Workspace-Id. A quick way to confirm which workspace a key points at before running a script against it.
curl https://api.steadylink.io/api/workspaces/current \
-H "X-API-Key: $STEADYLINK_API_KEY"The response is a workspace object with status 200 OK.
Get the API entitlement#
/api/workspaces/current/api-entitlementworkspace:readReturns whether developer API access is active for the workspace, why not if it is not, and the limits that apply to every key in the workspace. All keys share these limits; creating more keys does not add capacity.
A signed-in owner or admin can read this even while API access is paused, which is how the dashboard explains a paused state. An API key cannot: while access is paused, every key request, including this one, fails with 403 before it reaches the route.
curl https://api.steadylink.io/api/workspaces/current/api-entitlement \
-H "X-API-Key: $STEADYLINK_API_KEY"{
"enabled": true,
"reason": null,
"plan": "pro",
"billingStatus": "active",
"currentPeriodEnd": "2026-11-01T00:00:00",
"limits": {
"readRequestsPerMinute": 1200,
"writeRequestsPerMinute": 300,
"burstRequestsPer10Seconds": 300,
"expensiveOperationsPerMinute": 120,
"expensiveOperationsPerDay": 25000,
"requestsPerMonth": 2000000,
"activeApiKeys": 10,
"maxUploadBytes": 5368709120
},
"activeApiKeys": 2
}Response fields
enabledboolean- Whether API keys work for this workspace right now.
reasonstring | null- Why access is off:
subscription_inactive(a paid plan whose billing is not active or whose period has ended),paid_plan_required, orapi_access_disabled(turned off for the workspace by SteadyLink).nullwhen enabled. limitsobject | null- Per-plan limits for the whole workspace.
activeApiKeysinsidelimitsis the maximum; the top-levelactiveApiKeysis how many unrevoked, unexpired keys exist now.
The free plan includes API access with one active key and 50,000 requests a month. For how these limits are applied and what a 429 looks like, see Usage and limits.
Update workspace settings#
/api/workspaces/currentChanges the workspace name, settings, or both. Send only what you want to change. Settings you omit keep their current values.
Body
namestring- New name, 1 to 200 characters.
settings.defaultVisibilitystringpublicorprivate. The default visibility for new uploads. A bucket can override it. Workspaces with no stored value behave asprivate.settings.requireCleanDeliveryboolean- A stored workspace preference. Any value is saved as a boolean.
settings.accentThemestring- Dashboard accent:
steadylink,lime,iris,sunset, orsky.
curl -X PATCH https://api.steadylink.io/api/workspaces/current \
-H "Authorization: Bearer $STEADYLINK_ACCESS_TOKEN" \
-H "X-Workspace-Id: 6e0b2f7a-1c94-4d3b-a8e5-2f71c0d9b6a3" \
-H "Content-Type: application/json" \
-d '{ "name": "Northwind Studio", "settings": { "defaultVisibility": "public" } }'The response is the updated workspace object. Any other key inside settings returns 422 with "code": "invalid_settings" and the rejected keys in invalid.
Create a workspace#
/api/workspacesCreates a workspace on the free plan with the caller as owner. Send { "name": "Client portal" }. Returns 201 Created with the new workspace object.
Delete the current workspace#
/api/workspaces/currentPermanently deletes the workspace, every file and revision in it, and its stored bytes. To confirm, the body must repeat the exact workspace name: { "name": "Northwind Studio" }. A mismatch returns 422.
You cannot delete your only workspace. Create another first, or the request fails with 409 and "code": "last_workspace". On success the response names the workspace to switch to:
{ "deleted": true, "nextWorkspaceId": "b8f41d2c-5e60-4a17-93cb-0c2e7d4f1a85" }Members#
List members#
/api/workspaces/current/membersworkspace:readcurl https://api.steadylink.io/api/workspaces/current/members \
-H "X-API-Key: $STEADYLINK_API_KEY"{
"items": [
{ "userId": "0d9e3a71-48c2-4b5f-a1e6-7c2b9f0e4d38", "name": "Ana Ruiz", "email": "[email protected]", "role": "admin", "avatarUrl": null },
{ "userId": "5b7c1f20-93ad-4e8b-b6f4-2a0d8e1c7f95", "name": "Sam Lee", "email": "[email protected]", "role": "member", "avatarUrl": null }
]
}Change a member's role#
/api/workspaces/current/members/{user_id}Body
rolestringRequiredadmin,member, orviewer.
Returns the updated member. The owner's role cannot be changed this way (409); transfer ownership instead. An admin who tries to change an admin, or to make someone an admin, gets 403.
Remove a member#
/api/workspaces/current/members/{user_id}Removes the person's access immediately and returns { "removed": true }. The owner cannot be removed (409), and only the owner can remove an admin.
Transfer ownership#
/api/workspaces/current/ownershipMakes another member the owner and turns the current owner into an admin. Send { "userId": "0d9e3a71-48c2-4b5f-a1e6-7c2b9f0e4d38" }. Returns { "transferred": true, "ownerUserId": "..." }.
Invitations#
Invite a member#
/api/workspaces/current/invitationsEmails an invitation link to the address. The invitation reserves a seat until it is accepted, revoked, or expires.
Body
emailstringRequired- The invitee's email address. It is matched case-insensitively when they accept.
rolestringDefaultmemberadmin,member, orviewer. Only the owner can invite an admin.expiresInDaysintegerDefault7- From 1 to 30.
curl -X POST https://api.steadylink.io/api/workspaces/current/invitations \
-H "Authorization: Bearer $STEADYLINK_ACCESS_TOKEN" \
-H "X-Workspace-Id: 6e0b2f7a-1c94-4d3b-a8e5-2f71c0d9b6a3" \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]", "role": "viewer", "expiresInDays": 14 }'{
"id": "e2a7c9d4-0b18-4f63-9c5e-71d4a3b8f026",
"email": "[email protected]",
"role": "viewer",
"status": "pending",
"expiresAt": "2026-10-22T10:04:31.118204",
"createdAt": "2026-10-08T10:04:31.118204",
"token": "q8Vw...",
"acceptPath": "/api/workspaces/invitations/q8Vw.../accept"
}token is returned only in this response. Inviting the same address again revokes the earlier pending invitation. Errors: 409 if the person is already a member, 409 with "code": "seat_limit_reached" if members plus pending invitations already fill the plan's seats.
List invitations#
/api/workspaces/current/invitationsReturns every invitation, newest first, with status of pending, accepted, revoked, or expired. Tokens are never included.
Revoke an invitation#
/api/workspaces/current/invitations/{invitation_id}Returns { "revoked": true } and frees the reserved seat.
Accept an invitation#
/api/workspaces/invitations/{token}/acceptThe signed-in user's email must match the invitation. Returns { "accepted": true, "workspaceId": "...", "role": "viewer" }. Errors: 404 if the invitation is not pending, 410 if it expired, 403 if it belongs to another email address.
API keys#
List API keys#
/api/workspaces/current/api-keysReturns metadata for every key, newest first, including revoked and expired keys. The raw key value is never returned after creation.
{
"items": [
{
"id": "a3c5e7f9-1b2d-4e6f-8a0c-2e4f6a8c0b1d",
"name": "production uploader",
"scopes": ["assets:read", "assets:write"],
"createdAt": "2026-09-14T08:30:00.000000",
"lastUsedAt": "2026-10-08T09:55:12.402113",
"expiresAt": "2026-12-13T08:30:00.000000",
"revokedAt": null
}
]
}lastUsedAt is updated at most every five minutes, so treat it as approximate.
Create an API key#
/api/workspaces/current/api-keysBody
namestringRequired- A label you will recognize later, 1 to 200 characters.
scopesstring[]Default["assets:read", "assets:write"]- Any of
assets:read,assets:write,analytics:read,workspace:read,workspace:admin. Grant only what the integration needs. expiresInDaysinteger- From 1 to 365. Omit for a key that does not expire.
curl -X POST https://api.steadylink.io/api/workspaces/current/api-keys \
-H "Authorization: Bearer $STEADYLINK_ACCESS_TOKEN" \
-H "X-Workspace-Id: 6e0b2f7a-1c94-4d3b-a8e5-2f71c0d9b6a3" \
-H "Content-Type: application/json" \
-d '{ "name": "production uploader", "scopes": ["assets:read", "assets:write"], "expiresInDays": 90 }'{
"id": "a3c5e7f9-1b2d-4e6f-8a0c-2e4f6a8c0b1d",
"name": "production uploader",
"scopes": ["assets:read", "assets:write"],
"createdAt": "2026-10-08T10:12:00.000000",
"lastUsedAt": null,
"expiresAt": "2027-01-06T10:12:00.000000",
"revokedAt": null,
"key": "slk_..."
}Errors:
| Status | Code | Meaning |
|---|---|---|
403 | subscription_inactive, paid_plan_required, api_access_disabled | API access is not active for the workspace. |
409 | api_key_limit_reached | The plan's active key limit is reached. Revoke an unused key first. |
422 | invalid_scopes | An unknown scope, or an empty list. The response lists allowed scopes. |
Most people create keys in Dashboard > Developers instead. See API keys for scope choices and rotation.
Revoke an API key#
/api/workspaces/current/api-keys/{key_id}Revokes the key immediately and returns { "revoked": true }. Requests that use it fail with 401 from then on. Revoked keys stay in the list with revokedAt set.