Your site has accounts now
End-user accounts as a primitive: identities with customer-defined types, password logins backed by a blind credential store, invite / upgrade / recovery links, always-on recovery codes, and type-gated paths — enforced at the edge, managed by your agent, no auth code to write.
Types, not roles
Every account holds exactly one type — a label you define
(free, advanced, founding…). Types are disjoint:
nothing is inherited or implied. A protected path lists exactly which types may enter,
so to know who can see a path you read one line:
{
"accountTypes": ["free", "advanced"],
"defaultType": "free",
"signupMode": "invite",
"mode": "401",
"protected": [
{ "prefix": "/members", "requiredTypes": ["free", "advanced"] },
{ "prefix": "/members/pro", "requiredTypes": ["advanced"] }
]
}
PUT that as the site auth-config and accounts are on. The EdDSA session signing key and the site JWKS are provisioned automatically — the safe way is the only way. Sessions remain stateless edge-verified JWTs; an account is the durable identity a session proves. Type changes propagate within the session TTL (default 1 hour).
PUT /api/account/sites/:siteId/auth-config
is authenticated by your console session — agent API keys get 401 and there is no
/v1 equivalent. Publish it once from the dashboard; every account operation
after it is agent-drivable.
signupMode: open (self-serve at /_relayplex/signup),
invite (claim links only — the default), or closed (agent-created only).
The blind credential store
Passwords have exactly two operations: set and verify. There is no read path — not for you, not for your AI agent, not for anyone. Material is peppered per-site under keys that never co-locate with the hashes, so a leaked database is uncrackable on its own. What you can see is metadata: method, last changed, recovery codes remaining.
Claim links: one primitive, three jobs
A claim link is a single-use, expiring URL redeemed at
{site}/_relayplex/claim. Redemption always requires typing the exact
email the link was issued for — a mis-sent link is inert, and a wrong email never
consumes it. Relayplex sends no end-user email; you deliver links over the channel your
customer relationship already runs on.
- invite — creates the account; the person picks their own password.
- upgrade — changes an existing account's type ("here's your advanced access").
- recovery — replaces a lost password. The old password keeps working until the link is redeemed, so issuing recovery can never lock anyone out.
Invite and upgrade links default to a 7-day lifetime
(ttlSec accepted between 60 seconds and 30 days); recovery links default
to 24 hours.
ROLE_REQUIRED and need to be re-minted from an owner/admin console session.
Issuance is limited to 30 per hour per site, and every issuance and redemption lands in
the account's audit trail.
Recovery codes — always on
Every time a password is set, the account gets 8 one-time recovery codes,
shown exactly once with instructions to store them securely. Redeeming one at
/_relayplex/recover (email + code + new password) restores access with no
human in the loop and regenerates the full set. Codes are stored as peppered hashes —
like passwords, unreadable by anyone.
Private per-user data
Every account gets a private space. The signed-in user reads and writes
it at /_relayplex/private/{path}; your agent acts in any user's space by
naming them: /v1/sites/:siteId/accounts/:id/private/{path}. There is no
account id in the user-facing URL and nothing to configure — the signed-in identity
is the address, so two users fetching the same URL see two different spaces, and
cross-user access is not a request that can be made.
Ask who is signed in with GET /_relayplex/session
({ signedIn, accountId, type, email }), then personalize. Limits: 1MB per
object, 50MB per user (the space-used total settles within the hour), 30 mutations/min.
Private operations are Relay expenses like pageviews: 10 operations = 1 Relay; bytes
held count toward account storage.
Erase — the name is the guarantee
Erasing an account removes the identity, the credential, all recovery codes, all
outstanding claim links, the entire private space, and cancels a subscription created
through a plan link. What remains is a pseudonymized audit trail ending in an
erased entry — the proof it ran. Both sides can trigger it:
the person at /_relayplex/account (password + typed email —
a stolen browser session alone cannot erase an account), and you via
DELETE /v1/sites/:siteId/accounts/:id (owner/admin-created key), because as
the controller you must be able to honor requests that arrive outside the product or for
users who can no longer sign in. Idempotent; large spaces may report
in_progress and finish clearing shortly after.
End-user surfaces (hosted on your domain)
Agent API
Turning accounts on is one step
In the console, name your tiers and pick how people sign up. That's the whole human
step — no JSON, no access rules to hand-write. Enabling accounts provisions the session
signing key for you. From there your agent wires which paths each tier
can reach (PUT /v1/sites/:siteId/accounts/access), and the console shows that
wiring back to you read-only under Access by tier. You decide what tiers
exist; the agent decides how they're wired — and the agent can never change
your tiers.
Signup modes
- Free & open — anyone self-signs up for a free account.
- Paid subscription — public paywall: your signup page becomes a pricing front door, and an account exists only after the visitor subscribes.
- Invite only — accounts exist only via links you hand out.
- Agent only — no public signup; your agent creates accounts.
Paywall (no free tier): choose Paid subscription and create a
plan link — visitors subscribe to join, and a lapsed subscriber drops to your default tier
(locked out of gated paths) without being deleted.
Freemium (free, pay to unlock): choose Free & open, gate your
premium paths to a paid tier, and set an upgradePath so a free user hitting a
paid path is sent to your pricing page instead of a dead end.
How plan links manage subscriptions
A plan link is not itself a subscription — it's a small durable binding of a Stripe price to an account type, plus billing alignment. It manages a subscription across two moments, both using your own Stripe key held in the vault:
- Start — the subscribe link opens Stripe Checkout for that price (with aligned billing or a trial). A real subscription is created in your Stripe account — Relayplex is not the merchant; you are.
- Sync — your managed Stripe hook keeps the tier in lockstep with the subscription: pay → tier granted, price change → tier re-mapped, cancel → back to your default tier. "Has an active subscription to price X" becomes "account is type Y," automatically.
Requirements & limits (stated plainly): a plan link needs a managed Stripe hook with a vault key, so it's a Pro-and-above feature; creating one without that errors. There is no end-user billing portal in Relayplex today — your end-users change cards or cancel in your Stripe (or a portal you provide). Deactivating a plan link stops new subscriptions but leaves existing ones running. On account erasure, Relayplex best-effort cancels the subscription it started. Set up the webhook receiver: docs.relayplex.com/hooks.
Accounts themselves work on every plan with zero Stripe dependency — only
the paid-signup path is Pro+. Full walkthrough:
relayplex.com/accounts and the
billing_enabled_accounts recipe in the
canonical API reference.