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

VariableWhereNotes
EPDS_API_KEYBackend only (Cloudflare, etc.)Never expose client-side
EPDS_AUTH_URLBackendhttps://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.


FAQ

Who is this for?

For developers creating single purpose accounts for users.

Why single purpose?

Anytime the user authenticates into an app it resets their password and logs them out on other services which is terrible user experience for using multiple apps with one account.

What about multi purpose accounts?

Sign in with OAuth (Bluesky, Mastodon, et al.)

What is self.surf?

Selfsurf is a Personal Data Server (PDS) that is gating access for developers to seamlessly create accounts and log users in.

Why use this service?

Get the best user experience for your app or service without compromising on security.

What security measures are in place?

The PDS has invite codes enabled in order to protect from programmatic spam. We'll implement Osprey (more on this soon), the code is open source under MIT and/or Apache 2.0. Security measures on the server (TBA).

Who uses self.surf?

Linkname uses this PDS to create PDS accounts onsite without having to redirect to another page.

How is it different to other PDS instances?

Selfsurf runs ePDS (made by Hypercerts), a passwordless authentication layer on top of the official PDS. Selfsurf runs an API for gated apps to receive an API key with which a developer can create accounts and have users log back in without having to enter a password and without having to redirect to any OAuth page. Passwordless, no redirects, as simple as authentication gets.

How can I use your PDS for my app?

If you build an app and want to use self.surf for user account creation, contact us.

What about the server? Where is it located? Is there backups?

The PDS is hosted on our server in the European Union. Backups TBA.

Where is the source code?

For this frontend visit tangled and for the ePDS visit github.