Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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_case fields, 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:

bash
1
2
curl -H "Authorization: Bearer $STOREY_TOKEN" \
  https://storey.scailabs.ai/api/v1/buckets

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:

text
1
X-Storey-Tenant: <tenant slug>

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:

json
1
2
3
4
5
6
7
8
{
  "error": {
    "code": "BucketNotEmpty",
    "message": "This bucket still contains objects. Delete them first, then delete the bucket.",
    "request_id": "3f2b…",
    "details": null
  }
}

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:

bash
1
2
3
curl -X POST https://storey.scailabs.ai/api/v1/buckets/$BUCKET/objects/presign \
  -H "Content-Type: application/json" \
  -d '{"key": "reports/q2.pdf", "method": "GET", "ttl_seconds": 900}'

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.

Updated 2026-09-27 09:58:22 View source (.md) rev 2