Docs
TLDR; Selfsurf is a free service for devs to offer secure, seamless, and easy account signup and login, for apps built with DIDs; account identifiers that are strongly-consistent, recoverable, and allows for key rotation (see web.plc.directory). See our API Client Terms.
How it works
Account creation and login on self.surf are handled via email OTP through ePDS (extended Personal Data Server, source). Your backend sends and verifies OTP codes using an API key, the user enters their email, receives a code, and is logged in. No passwords, no redirects, no invite codes to manage (the PDS has invite codes enabled to prevent programmatic spam but your frontend never requires the user to enter an invite code for the purpose of creating an account).
Architecture
User → your app → your backend → /_internal/otp/send → self.surf ePDS (sends email)
→ /_internal/otp/verify → self.surf ePDS (returns session)
Internet → Cloudflare Tunnel → ePDS auth-service (OTP + login)
→ ePDS pds-core (AT Protocol)Getting an API Key
Contact the self.surf operator to register your app. Or self host, see source.
Backend Flow (Two Steps)
const AUTH_URL = process.env.EPDS_AUTH_URL; // https://auth.self.surf
const API_KEY = process.env.EPDS_API_KEY; // store as a secret
// Step 1: Send OTP code to user's email
await fetch(`${AUTH_URL}/_internal/otp/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
body: JSON.stringify({
email: 'alice@example.com',
purpose: 'signup', // or 'login'
}),
});
// Step 2: Verify the code the user entered
const res = await fetch(`${AUTH_URL}/_internal/otp/verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
body: JSON.stringify({
email: 'alice@example.com',
otp: '12345678', // code from email
purpose: 'signup', // or 'login'
handle: 'alice', // required for signup only
}),
});
const session = await res.json();
// { did, handle, accessJwt, refreshJwt, created: true }Environment Variables
| Variable | Where | Notes |
|---|---|---|
EPDS_API_KEY | Backend only (Cloudflare, etc.) | Never expose client-side |
EPDS_AUTH_URL | Backend | https://auth.self.surf |
Verification
# PDS is healthy
curl https://self.surf/xrpc/_health
# → {"version":"0.4.x"}
# Send a test OTP (returns success even for non-existent accounts)
curl -X POST https://auth.self.surf/_internal/otp/send \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"email":"test@test.com","purpose":"login"}'
# → {"success":true}Note: self.surf runs ePDS, a passwordless authentication layer built on top of the official AT Protocol PDS. Full AT Protocol compatibility and federation are preserved. Invite codes are handled internally.