Relayplex
Site accounts

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

This is the one human step. 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 and upgrade links default to a 7-day lifetime (ttlSec accepted between 60 seconds and 30 days); recovery links default to 24 hours.

Recovery is privileged and loud. Issuing it requires an API key created by an org owner or admin — keys minted before role tracking always fail with 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)

GET /_relayplex/sessionWhoami: { signedIn, accountId, type, email }. Never cached.
GET/PUT/DELETE /_relayplex/private/{path}The signed-in user's own private space; bare /private/ lists it.
GET/POST /_relayplex/accountAccount page + self-serve erase (password + typed email).
GET/POST /_relayplex/loginSign in; sets the first-party rlpx_session cookie.
GET/POST /_relayplex/signupSelf-serve signup (signupMode 'open' only). Shows recovery codes once.
GET/POST /_relayplex/claimRedeem invite / upgrade / recovery links and Stripe checkouts (?session_id=...).
GET/POST /_relayplex/recoverSelf-recovery with a recovery code.
GET /_relayplex/subscribe/:planIdPlan link: redirects into a server-minted Stripe Checkout Session.
GET/POST /_relayplex/logoutClears the session cookie.

Agent API

GET /v1/sites/:siteId/accountsList accounts (email, type, status).
POST /v1/sites/:siteId/accountsCreate an identity (email + type). No credential — pair with an invite claim.
PATCH /v1/sites/:siteId/accounts/:idChange type or disable. Audited.
POST /v1/sites/:siteId/accounts/claimsIssue invite/upgrade links (shown once, email-pinned).
POST /v1/sites/:siteId/accounts/:id/recoveryRe-issue access (owner/admin-created keys only).
GET/PUT/DELETE /v1/sites/:siteId/accounts/:id/private/{path}Act in a named user's private space (write results, read state). Same limits as the user surface.
DELETE /v1/sites/:siteId/accounts/:idErase the account (owner/admin-created keys only; idempotent).
POST /v1/sites/:siteId/session/issueWith siteAccountId: mint a session FOR a user — identity and type come from the record; audited.
POST /v1/sites/:siteId/plansCreate a plan link (aligned billing via your vault-held Stripe key).

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

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.

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:

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.