Permafrost
Overview
The routes beside the S3 surface — certification status, custody, verify, and your own keys.
Permafrost API 0.2.0-beta.0
Section titled “Permafrost API 0.2.0-beta.0”Most of what a Permafrost tenant does is plain S3: create a bucket, PUT an object, GET it back. This specification covers the routes that sit beside that surface, on the same host and behind the same credential:
- Certification status.
/v1/api/meta/objectsreports whether an object has cleared certification on Walrus and what its blob id is. - Custody.
/v1/api/custody/*holds a document as ciphertext under a key held for that one object, serves it while the key lives, and destroys the key on request — with a certificate on Sui. - Verify.
/v1/api/verifyanswers, with no credential at all, whether Permafrost holds provenance for a content hash. - Your keys.
/meand/me/keys/rotateare the tenant’s own view of its tier, rate ceiling and keys.
Authentication
Every authenticated route takes the same header:
Authorization: Bearer <credential>
The credential is one of two things, and the service cannot tell them apart once the request is through:
- an API key —
pf_sk_…, minted by redeeming an invite; - a signed token from an issuer this instance has been configured with. The token’s subject is the tenant, and a tenant appears on first use. No key is minted for it.
The credential is resolved at the edge, before the request reaches any service. On success the edge resolves the calling tenant and forwards the request with the tenant’s context attached; a caller never sets that context itself, and it cannot be forged from outside.
Two routes take no credential: GET /v1/api/verify, and
POST /invites/{token}/redeem — where the unguessable single-use token in
the path is the credential.
One 401, whatever went wrong
Every authentication failure answers the same 401 with the same body: an
absent header, a malformed one, an unknown key, a revoked key, a token from
an unconfigured issuer, a bad signature, a wrong audience, an expired token,
a subject that is not a canonical Sui address. Nothing in the response says
which. A caller cannot learn from a rejection whether a key exists, whether
an issuer is configured, or which claim failed.
A tenant whose row has been revoked is different, and answers 403.
Rate limiting is per tenant
Each tenant carries its own requests-per-second ceiling and its own token bucket, keyed by the tenant address, so one tenant spending its allowance does not consume another’s. Over the ceiling, the answer is:
429 {"error":"rate_limited","retry_after_ms":N}
Retry-After: <whole seconds, rounded up>
Both credentials share one bucket per tenant: arriving with a key and with a
token does not buy two allowances. An overall limit also exists at the edge,
across all callers; it can answer 429 on any route, including the keyless
ones.
Scope
This describes the hosted testnet instance. Object bytes move over the S3 surface, which is documented as a compatibility matrix rather than here.
Authentication
Section titled “Authentication”bearerAuth
Section titled “bearerAuth”One header, two credentials: an API key (pf_sk_…) or a signed token
from a configured issuer. Both are resolved at the edge, and a rejection
of either is the same 401 with the same body.
Security scheme type: http
Permafrost runs on Sui testnet and Walrus testnet. Everything here describes a shipped testnet instance, not a production service.