chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
# PDS Endpoint Comparison: pds.js vs atproto/packages/pds
Comparison of endpoints and parameters between this implementation and the official AT Protocol PDS.
---
## Endpoints Missing from pds.js
### com.atproto.admin.* (entire namespace missing)
| Endpoint | Params |
|----------|--------|
| deleteAccount | did |
| disableAccountInvites | account, note |
| disableInviteCodes | accounts, codes |
| enableAccountInvites | account, note |
| getAccountInfo | did |
| getAccountInfos | dids |
| getInviteCodes | cursor, limit, sort |
| getSubjectStatus | blob, did, uri |
| searchAccounts | cursor, email, limit |
| sendEmail | comment, content, recipientDid, senderDid, subject |
| updateAccountEmail | account, email |
| updateAccountHandle | did, handle |
| updateAccountPassword | did, password |
| updateAccountSigningKey | did, signingKey |
| updateSubjectStatus | deactivated, subject, takedown |
### com.atproto.identity.* (partially implemented)
Still missing:
| Endpoint | Params | Notes |
|----------|--------|-------|
| refreshIdentity | identifier | |
| resolveDid | did | |
| resolveIdentity | identifier | |
*pds.js implements: resolveHandle, updateHandle, getRecommendedDidCredentials, requestPlcOperationSignature, signPlcOperation, submitPlcOperation*
`updateHandle` submits a did:plc operation repointing the DID document's
`alsoKnownAs` at the new handle, persists it, and emits an `#identity` firehose
event so consumers re-resolve. It only accepts a handle that resolves back to the
account: the server's own address is taken as-is, a subdomain of it is refused
(a single-user PDS answers only at its own host), and any other domain must
already resolve to the DID via a `_atproto` DNS TXT record or the HTTPS
well-known.
`requestPlcOperationSignature` mails a confirmation code when the deployment
has a sender and the account has a confirmed address, and `signPlcOperation`
then requires it — one code, one operation. Without both, no code is issued
and the session remains the only authorization, as before.
### com.atproto.server.* (many missing)
| Endpoint | Params |
|----------|--------|
| confirmEmail | email, token |
| createInviteCode | forAccount, useCount |
| createInviteCodes | codeCount, forAccounts, useCount |
| deleteAccount | did, password, token |
| getAccountInviteCodes | createAvailable, includeUsed |
| requestAccountDelete | (none) |
| requestEmailConfirmation | (none) |
| requestEmailUpdate | (none) |
| requestPasswordReset | email |
| reserveSigningKey | did |
| resetPassword | password, token |
| updateEmail | email, emailAuthFactor, token |
*pds.js implements: createAccount, createSession, getSession, refreshSession, deleteSession, createAppPassword, listAppPasswords, revokeAppPassword, describeServer, activateAccount, deactivateAccount, checkAccountStatus, getServiceAuth*
*createAccount supports the account-migration shape only (adopt an existing `did` onto an empty single-tenant server), authorized by a service token from the current PDS or the configured account password. The node and cloudflare packages verify the token's signature against the issuer's atproto key — P-256 in core, secp256k1 (the curve the reference PDS gives every account) via @noble/curves — resolving the issuer's DID document to find the key. A token that cannot be verified is rejected. A core-only deployment with no verifier injected falls back to validating the token's claims alone.*
### com.atproto.sync.* (some missing)
| Endpoint | Params |
|----------|--------|
| getBlocks | cids, did |
| getHostStatus | hostname |
| listHosts | cursor, limit |
| listReposByCollection | collection, cursor, limit |
| notifyOfUpdate | hostname |
| requestCrawl | hostname |
*pds.js implements: listRepos, getLatestCommit, getRepoStatus, getRepo, getRecord, getBlob, listBlobs, subscribeRepos*
### com.atproto.repo.* (complete)
*pds.js implements: createRecord, deleteRecord, putRecord, applyWrites, getRecord, describeRepo, listRecords, uploadBlob, importRepo, listMissingBlobs*
### com.atproto.moderation.*
| Endpoint | Params |
|----------|--------|
| createReport | modTool, reason, reasonType, subject |
### com.atproto.temp.* (entire namespace missing)
| Endpoint | Params |
|----------|--------|
| addReservedHandle | handle |
| checkHandleAvailability | birthDate, email, handle |
| checkSignupQueue | (none) |
| dereferenceScope | scope |
| fetchLabels | limit, since |
| requestPhoneVerification | phoneNumber |
| revokeAccountCredentials | account |
---
## Missing Parameters in Shared Endpoints
Endpoints that exist in both implementations, but pds.js is missing parameters:
| Endpoint | pds.js has | Missing from pds.js |
|----------|------------|---------------------|
| repo.createRecord | collection, record, rkey, **validate** | **repo**, swapCommit |
| repo.deleteRecord | collection, rkey | **repo**, swapCommit, swapRecord |
| repo.putRecord | collection, rkey, record, **validate** | **repo**, swapCommit, swapRecord |
| repo.applyWrites | writes, **validate** | **repo**, swapCommit |
| sync.getRepo | did | since |
| sync.listBlobs | did, cursor, limit | since |
| sync.listRepos | (none) | cursor, limit |
| server.createSession | identifier, password | allowTakendown, authFactorToken |
**Bold** = likely important for compatibility
---
## app.bsky.* Coverage
Both implementations handle app.bsky.* the same way:
| Category | Endpoints | Notes |
|----------|-----------|-------|
| Native (stored in PDS) | actor.getPreferences, actor.putPreferences | Both implementations |
| Proxied to AppView | ~87 endpoints | feed.*, graph.*, notification.*, etc. |
---
## Extra in pds.js (not in atproto spec)
Custom endpoints specific to this implementation:
| Endpoint | Purpose |
|----------|---------|
| `POST /init` | Initialize PDS with DID/keys |
| `GET /status` | Health check |
### OAuth 2.0 Stack (full implementation)
| Endpoint | Purpose |
|----------|---------|
| `GET /.well-known/oauth-authorization-server` | OAuth server metadata |
| `GET /.well-known/oauth-protected-resource` | Protected resource metadata |
| `GET /oauth/jwks` | JSON Web Key Set |
| `POST /oauth/par` | Pushed Authorization Request |
| `GET/POST /oauth/authorize` | Authorization endpoint |
| `POST /oauth/authorize/passkey` | Approve a request with a passkey assertion |
| `POST /oauth/token` | Token endpoint (with DPoP) |
| `POST /oauth/revoke` | Token revocation |
### Account pages
Server-rendered, like the consent page; the reference PDS serves the same paths
from the React app in `@atproto/oauth-provider-ui`.
| Endpoint | Purpose |
|----------|---------|
| `GET /account` | Account home, or the sign-in form when signed out |
| `POST /account/sign-in` | Verify the account password, start a cookie session |
| `POST /account/sign-out` | Clear the cookie session |
| `GET /account/apps` | Apps holding an OAuth session |
| `POST /account/apps/revoke` | End one app's OAuth session |
| `GET /account/sessions` | Logins opened with a password |
| `POST /account/sessions/revoke` | End one password login |
| `GET /account/spaces` | Permissioned-data spaces, when enabled |
| `GET /account/spaces/records` | The records in one space collection |
| `GET /account/passkeys` | Registered passkeys, and the form to add one |
| `POST /account/passkeys/register` | Store a credential the browser created |
| `POST /account/passkeys/revoke` | Forget one passkey |
| `GET /account/passkey/challenge` | Start a passkey sign-in |
| `POST /account/passkey/sign-in` | Open a session from a passkey assertion |
| `GET/POST /account/email` | Address and confirmation state; POST sets it |
| `POST /account/email/verify` | Exchange the emailed code |
| `POST /account/email/cancel` | Drop a pending address change |
| `POST /account/email/remove` | Forget the address |
| `GET /account/app-passwords` | App password list and creation form |
| `POST /account/app-passwords/create` | Create one, shown exactly once |
| `POST /account/app-passwords/revoke` | Revoke one by name |
| `GET /account/repo` | Record counts by collection, blobs, latest commit |
| `GET /account/repo/records` | The records in one repo collection |
| `GET /account/identity` | Published identity and account status |
| `POST /account/status` | Activate or deactivate the account |
---
## Summary
| Category | pds.js | atproto PDS |
|----------|--------|-------------|
| com.atproto.admin.* | 0 | 15 |
| com.atproto.identity.* | 6 | 9 |
| com.atproto.moderation.* | 0 | 1 |
| com.atproto.repo.* | 10 | 10 |
| com.atproto.server.* | 9 | 25 |
| com.atproto.sync.* | 8 | 14 |
| com.atproto.temp.* | 0 | 7 |
| app.bsky.* (native) | 2 | 2 |
| app.bsky.* (proxied) | ~87 | ~87 |
| **Total XRPC (native)** | **34** | **85** |
| Custom endpoints | 2 | 0 |
| OAuth endpoints | 8 | 7 (via @atproto/oauth-provider) |
| Account pages | 25 | 2 + SPA routes (via @atproto/oauth-provider) |