Skip to content

Overview

The routes beside the S3 surface — certification status, custody, verify, and your own keys.

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/objects reports 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/verify answers, with no credential at all, whether Permafrost holds provenance for a content hash.
  • Your keys. /me and /me/keys/rotate are 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 keypf_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.

Information

  • License: Proprietary
  • OpenAPI version: 3.1.0

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.