Redeem an invite into a tenant and a key.
const url = 'https://testnet.permafrost.live/invites/9tK2mQx7Vb3TcYw6RfN1sLpA4dZhG7uEoX5nK0iMrQv/redeem';const options = { method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{"sui_address":"0x2c4f6a8b0d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e8f0a1b3c5d7e9f10","display_name":"Northwind Media"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://testnet.permafrost.live/invites/9tK2mQx7Vb3TcYw6RfN1sLpA4dZhG7uEoX5nK0iMrQv/redeem \ --header 'Content-Type: application/json' \ --data '{ "sui_address": "0x2c4f6a8b0d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e8f0a1b3c5d7e9f10", "display_name": "Northwind Media" }'No bearer. The unguessable single-use token in the path is the credential. This is the one write path on the instance that is reachable without one.
You supply an address and, optionally, a display name. Everything else about the tenant — its permissions, its tier, its rate ceiling — comes from the invite as it was minted, never from this request body.
A redemption provisions a fresh tenant. It will not attach a key to an address that already has one, so a valid invite cannot be redeemed into somebody else’s address. The address is folded to lower case before that check, so one address cannot become two tenants under two spellings.
One generic 400. Not found, expired, already redeemed, revoked and
malformed input are one answer, so a caller cannot probe for valid
tokens.
Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Example
9tK2mQx7Vb3TcYw6RfN1sLpA4dZhG7uEoX5nK0iMrQvThe single-use token from the invite link. It is the credential for this route.
Request Bodyrequired
Section titled “Request Bodyrequired”The only two things a redeemer supplies. Tier, permissions and rate ceiling come from the invite, never from here.
object
The Sui address that becomes the tenant: 0x followed by 64 hex
characters. Case is folded before the address is used.
A label for the tenant. Optional, and trimmed to a modest length.
Example
{ "sui_address": "0x2c4f6a8b0d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e8f0a1b3c5d7e9f10", "display_name": "Northwind Media"}Responses
Section titled “Responses”The tenant exists and the key is yours. raw_key is shown once and
is never returned again by any route.
The provisioned tenant and its one key.
object
The key, shown once. It is the only credential this redemption produces.
Example
{ "tenant_address": "0x2c4f6a8b0d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e8f0a1b3c5d7e9f10", "tier": "free", "raw_key": "pf_sk_0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0", "key_id": "0a7c4e18-2b93-4f06-8d5a-1c6e9b3f2d47", "key_prefix": "pf_sk_0f1e2", "permissions": [ "read", "write" ]}The invite cannot be redeemed, or the address is not a Sui address. One answer for every cause.
The refusal shape. detail is present on a few routes and deliberately
absent from the custody surface, where a detail string could carry an
internal path.
object
The code, or a short fixed sentence.
A human-readable note, where a route carries one.
Example
{ "error": "invalid or expired invite"}Too many redemption attempts from this caller. This is the redeem
route’s own limit — a redeemer has no tenant yet, so the per-tenant
bucket cannot apply. The body differs from the per-tenant 429 and
carries no Retry-After.
The refusal shape. detail is present on a few routes and deliberately
absent from the custody surface, where a detail string could carry an
internal path.
object
The code, or a short fixed sentence.
A human-readable note, where a route carries one.
Example
{ "error": "too many attempts, retry later"}Permafrost runs on Sui testnet and Walrus testnet. Everything here describes a shipped testnet instance, not a production service.