Skip to content

Redeem an invite into a tenant and a key.

POST
/invites/{token}/redeem
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.

token
required
string
Example
9tK2mQx7Vb3TcYw6RfN1sLpA4dZhG7uEoX5nK0iMrQv

The single-use token from the invite link. It is the credential for this route.

Media typeapplication/json

The only two things a redeemer supplies. Tier, permissions and rate ceiling come from the invite, never from here.

object
sui_address
required

The Sui address that becomes the tenant: 0x followed by 64 hex characters. Case is folded before the address is used.

string
/^0[xX][0-9a-fA-F]{64}$/
display_name

A label for the tenant. Optional, and trimmed to a modest length.

string
Example
{
"sui_address": "0x2c4f6a8b0d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e8f0a1b3c5d7e9f10",
"display_name": "Northwind Media"
}

The tenant exists and the key is yours. raw_key is shown once and is never returned again by any route.

Media typeapplication/json

The provisioned tenant and its one key.

object
tenant_address
required
string
tier
required
string
raw_key
required

The key, shown once. It is the only credential this redemption produces.

string
key_id
required
string format: uuid
key_prefix
required
string
permissions
required
Array<string>
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.

Media typeapplication/json

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
error
required

The code, or a short fixed sentence.

string
detail

A human-readable note, where a route carries one.

string
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.

Media typeapplication/json

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
error
required

The code, or a short fixed sentence.

string
detail

A human-readable note, where a route carries one.

string
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.