Skip to content

proven on the hosted testnet instance · 2026-09-13 · QA-14

Keys, rotation, and the portal

Your tenant’s own facts in front of you — tier, rate ceiling, usage, and the keys that authenticate for it — and a freshly rotated key, with the one you were using revoked by the same call.

No operator is involved in any of it. You do it with the key you already hold.

Key lane only. Both routes on this page authenticate by pf_sk_, and a tenant that arrived through a signed token has no key to present — so neither route answers for it, and the portal has nothing to accept. If that is you, Bring your own issuer says what to do instead.

https://testnet.permafrost.live/portal

A single page, and it opens with no credential — there is no login of its own. Paste your key into the field and press Load. The key stays in your browser; the page calls the same-origin route below with it and renders the answer.

What it shows you:

  • Your endpoint, and the header every request needs.
  • Tier, rate ceiling, and your tenant address, as three badges.
  • Usage — reads and data accumulated for your tenant, plus prepaid credit standing when the instance has a credit pool configured. It reads not enabled when it does not.
  • Your active keys — one row each, with the key you pasted flagged as the current one, its permissions, its status and when it was last used.
  • Rotate this key — one button, described below.
  • Features by tier — a matrix, with your tier highlighted. It is illustrative: the exact split is a product decision, not something this instance enforces today.

The page is thin. Everything it shows comes from one route, and everything it does comes from the other, so a client can do both directly.

Terminal window
curl -sS -H "Authorization: Bearer $PF_SK" https://testnet.permafrost.live/me
{
"tenant_address": "0x…",
"display_name": "",
"tier": "free",
"rate_limit_rps": 10,
"keys": [
{
"key_id": "",
"key_prefix": "pf_sk_…",
"permissions": ["read", "write"],
"status": "active",
"created_at": "",
"last_used_at": "",
"current": true
}
],
"usage": {
"total_reads": 0,
"total_bytes": 0,
"first_seen": null,
"last_settled": null
},
"credit": null
}

Everything is scoped to the caller. keys lists only your active keys, and current: true marks the one this request authenticated with — which is how you tell which key you are actually holding when you have several.

usage is operational, not a bill: counts and bytes, no price. credit is prepaid standing read from chain, and it is null when no credit pool is configured or when the read failed — the route will not fail over a balance lookup.

A missing, malformed, unknown or revoked key, and an inactive tenant, all answer 401 {"error": "invalid or inactive API key"}.

Terminal window
curl -sS -X POST -H "Authorization: Bearer $PF_SK" \
https://testnet.permafrost.live/me/keys/rotate
{
"raw_key": "pf_sk_…",
"key_id": "",
"key_prefix": "pf_sk_…",
"permissions": ["read", "write"],
"revoked_key_id": ""
}

Mint, then revoke, in one atomic step. The new key carries the same permissions as the old one. revoked_key_id is the key you just called with, and it is dead the moment this returns.

raw_key is shown once. There is no route that shows it again, and no operator copy — if you close the terminal without saving it, the way back is to rotate again from a key you still hold, or to ask us for a new invite.

So: switch to the new key immediately, and rotate from a shell where you can see the answer, not from a script that discards the body.

Call GET /me with the old key: it answers 401. Call it with the new one, or paste the new one into the portal: your tenant loads, and the key list shows the new key flagged current with the old one gone from it.

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