Skip to content

shipped · unexercised on the hosted instance

Limits: rate, quota, parts, timeouts

Four things bound a request here. Three of them have been driven on this instance; the quota has not, which is why this page carries an amber badge rather than a green one.

Every tenant carries its own rate_limit_rps — a requests-per-second ceiling — and its own token bucket, keyed by the tenant address. The bucket refills at that rate and its burst allowance is sized from the same number, with a small floor so that even a modest ceiling still admits the several requests an S3 client opens for one logical upload.

Where the number comes from:

How the tenant arrived Where its ceiling is set
Redeeming an invite On the invite, when the operator minted it.
First use of a signed token The default for the free tier — the same default a redemption gets.

Read your own with GET /me, or on the portal. It is the field named rate_limit_rps, and it is the one number the bucket is sized by. Ask for a different one when you ask for an evaluation window.

Over the ceiling, the answer is a 429 carrying retry_after_ms and a Retry-After header — Headers, status codes, 401 and 429.

Rate isolation holds within one instance. The bucket lives in the process that resolves your credential, so a deployment running several of them would admit a tenant’s full rate independently in each. This is one instance, so today the property holds; it is stated here because it is a property of the deployment rather than of the design.

An overall limit also exists at the edge, across every caller at once. It is a floor under the whole listener rather than a per-tenant allowance, and it can answer 429 on any route — including the keyless verify route, which has no tenant to key a bucket by.

POST /v1/api/custody/store is checked against the tenant’s storage ceiling before the request body is read, on the size the request declares. Over it:

413 {"error":"quota_exceeded","limit_mib":…,"used_mib":…}

Read that check precisely, because it is narrower than the word “quota” suggests:

It is a per-request size check against the ceiling. It is not a running total of what the tenant has stored on this surface. Custody objects are not accumulated into the figure the check compares against, so storing many documents does not walk used_mib upward.

limit_mib is the tenant’s ceiling and used_mib is what the tenant is already recorded as holding — both are the caller’s own numbers, which is why they are returned rather than hidden.

This is the unexercised part. Quota is recorded and it is shown, and the check is in the request path; driving a tenant past its ceiling has not been done on this instance. Treat the 413 as specified behaviour rather than as witnessed behaviour, and tell us if you drive it.

Beyond the per-tenant bucket at the edge, the custody surface keeps a local one:

Bucket Rate
Reads and store the tenant’s own ceiling
incinerate a fraction of it

Incineration draws on the tighter budget because it is irreversible. A burst is allowed — a team forgetting several documents back to back is not throttled on the second call — while the sustained rate stays well below the read rate, so a runaway client exhausts its budget long before it gets through a meaningful number of objects.

This limiter is local to the process: it resets when that process restarts, and it is a backstop beneath the edge’s own limit rather than a replacement for it. Its refusal is the same 429 code without retry_after_ms.

Every part except the last must meet S3’s minimum part size. That is S3’s rule, not an extra one imposed here, and any S3 SDK already enforces it on the client side — but a hand-rolled uploader that splits a file into equal small pieces will fail at CompleteMultipartUpload rather than at UploadPart, which is a confusing place to discover it.

The last part may be any size. There is no additional part-count ceiling beyond S3’s own.

Part bytes are not encrypted by this instance; see Encryption at rest and Upload media in parts.

CompleteMultipartUpload on a large upload does real work before it answers, and the edge’s timeout on the S3 route is set to outlast it deliberately — it matches the storage layer’s own. A completion that takes a while is not going to be cut off at the edge and returned to you as a gateway timeout after it has already succeeded, which is the failure this setting exists to prevent.

No number is published for it, for the reason the whole site gives no timing figures: the instance is stopped between evaluation windows and runs on testnet, so a figure measured in one window is not a figure to build a retry policy on. Set your client’s own timeout generously on the completion call and normally everywhere else.

Permafrost runs on Sui testnet and Walrus testnet. Everything here describes a shipped testnet instance, not a production service.