Management API
The management API handles everything that is not object data: buckets, access keys, grants, usage, and audit. It is separate from the S3 endpoint and is never co-hosted with it.
- Base:
https://storey.scailabs.ai/api/v1 - JSON in, JSON out,
snake_casefields, RFC 3339 UTC timestamps. - Every response carries
X-Request-Id. Quote it when reporting a problem.
Authentication#
The console authenticates with your ScaiLabs account through a server-side session — no access token is ever stored in your browser. Requests from the console carry a session cookie plus a CSRF header.
For anything that is not a person — a CI job, an orchestrator, a provisioning tool — use a service token.
Service tokens#
A service token is a bearer credential that holds Storey roles of its own:
1 2 | |
They are not S3 access keys, and the distinction is worth keeping straight: an access key signs requests against object data, a service token calls this API and can create or delete a bucket. A service token gives no access to object bytes at all.
What to expect:
| Shape | stk_<prefix>_<secret> |
| Shown | Once, in the create response. Storey stores only a hash and has no endpoint that returns it again |
| Scope | One or more roles, each at its own scope — a single tenant, a partner, or global |
| Expiry | Optional for a tenant- or partner-scoped token; a global token always has one (90 days by default, one year at most) |
| Revocation | Immediate — the next request with it fails, rather than waiting for expiry |
| Audit | Calls are attributed to the token's label and prefix, never to a person |
The prefix is not secret. It appears in listings and in audit entries, which is
what lets you identify a token after the secret is gone — and what makes the
label worth choosing carefully.
Tenant context. If a token can reach more than one tenant, say which one a request is about:
1 | |
Without it you get 422. A token that can reach exactly one tenant needs no
header. Naming a tenant the token cannot reach returns 404, not 403 — the
same rule as every other identifier.
If a token leaks, revoke it and mint a replacement. There is no rotation in place for a service token, because there is nothing to preserve: the token is the credential, and a new one with the same grants is a two-minute change in one config file.
Service tokens are managed by ScaiLabs platform operators. Ask for one, saying what it is for, which tenant it needs, and the narrowest role that does the job.
Pagination#
Listings are cursor-based: pass ?limit= (default 50, max 200) and follow
next_cursor until it is null. There is no offset pagination — bucket and
object listings are unbounded, and offsets over an unbounded set produce
duplicates and gaps under concurrent writes.
Errors#
Every error uses one shape:
1 2 3 4 5 6 7 8 | |
The code is the contract — match on it, not on the message. Messages state
what to do next and may be reworded.
| Code | Status | Meaning |
|---|---|---|
Unauthenticated |
401 | No valid session |
Forbidden |
403 | Your role does not allow this |
NotFound |
404 | No such resource in your tenant |
NameConflict |
409 | That name is already used |
BucketNotEmpty |
409 | Delete the objects first |
PlacementUnavailable |
409 | No cluster serves that region and tier |
EntitlementExceeded |
409 | You are at a limit on your plan |
KeyClusterMismatch |
409 | The key belongs to a different region |
OperationInProgress |
409 | Still provisioning; retry shortly |
ValidationError |
422 | The request was malformed |
RateLimited |
429 | Slow down |
ClusterUnavailable |
503 | Temporary; retry |
A resource belonging to another tenant returns NotFound, never
Forbidden — a 403 would confirm the id exists somewhere.
Idempotency#
Mutating requests accept an Idempotency-Key header. Replaying a request with
the same key returns the original result instead of acting twice. Bucket and key
creation are long-running: the response carries an operation_id you can poll
at GET /v1/operations/{id}.
Endpoint groups#
| Group | Purpose |
|---|---|
/v1/regions, /tiers, /placements |
What you can order |
/v1/tenant, /tenant/entitlements, /tenant/usage |
Your account |
/v1/buckets |
Bucket lifecycle, stats, usage |
/v1/buckets/{id}/objects |
Listing, metadata, presigned URLs, delete, copy |
/v1/buckets/{id}/share |
Time-boxed public links |
/v1/keys, /v1/buckets/{id}/grants |
Access keys and their grants |
/v1/operations, /v1/audit, /v1/events |
Progress, history, live updates |
/v1/tokens |
Service tokens (operator-managed) |
Objects: bytes never pass through this API#
Object listing and metadata come from the management API, but the bytes do not. Uploads and downloads use presigned URLs that your client sends directly to the S3 endpoint:
1 2 3 | |
That keeps a large transfer off the control plane and means a presigned request is metered exactly like any other S3 request — there is no cheaper path.
Presigned URLs are credentials. Their lifetime is capped by your role, every one is recorded in the audit log, and a share link can be revoked.