zds can only run as a standalone PDS. bsky.social runs as an entryway: a front door that owns sessions, OAuth, email, invites and PLC rotation, and places each account on one of many PDS hosts behind it. If zds is ever to be one of those hosts, it needs the reference PDS's entryway mode. This issue scopes the minimum.
Reference: bluesky-social/atproto packages/pds at 413f04f (2026-11-12). Tranquil (v0.8.0) has no entryway mode, so there is no other implementation to compare against. Bluesky's entryway is not open source; the reference tests run against a mock (tests/entryway-mock.ts), and zds would have to do the same.
how the reference does it #
PDS_ENTRYWAY_URL switches the mode on and requires the entryway's DID, its secp256k1 token key, and its PLC rotation key (config.ts:128-144). About 27 handlers then branch on it.
MVP #
- config: entryway URL, DID, K256 verify key (hex), PLC rotation key (did:key), all-or-nothing. Hosted handle domains set to the entryway's.
- entryway-issued tokens, replacing zds' own HS256 sessions in this mode:
- access: ES256K against the configured key,
typ: at+jwt,aud= this PDS's DID, nolxm/cnf - refresh:
typ: refresh+jwt,aud= the entryway DID,jtirequired (auth-verifier.ts:266-298) - OAuth: resource server only. Advertise the entryway as the authorization server in
/.well-known/oauth-protected-resource, run no local authorize/token endpoints, verify DPoP-bound ES256K tokens with issuer = entryway URL, and dereference scope references through the entryway'scom.atproto.temp.dereferenceScope(cached)
- access: ES256K against the configured key,
- account creation from the entryway:
reserveSigningKey, thencreateAccountwithdid+plcOp. The op'srotationKeysmust include the entryway key and the op must be signed by it; the DID document must match the handle, this PDS's URL and the reserved signing key. No email, password or invite. The PDS submits the op to PLC and never tombstones (createAccount.ts:165-223)submitPlcOperation,getRecommendedDidCredentialsand DID-doc validation use the entryway's rotation key instead of zds' own
- callbacks from the entryway:
admin.updateAccountHandle(no PLC op; the DID document must already show the handle),admin.updateSubjectStatus,admin.getSubjectStatus,admin.deleteAccount. Authenticate them with the entryway service JWT from #5844 / #5847 / #5866 (ES256K,typ: JWT,iss= entryway DID,aud= this PDS,lxm= the route, ≤ 5 min, checked against the configured key with no DID resolution). Skip the shared admin password the reference still accepts; the reference is retiring it, so zds can start at "require service auth" (auth-verifier.ts:187-232, 646-714) - forwarding to the entryway, about 25 routes:
- with the caller's own auth header:
createSession,deleteSession,refreshSession(after a local check),resetPassword,requestPasswordReset(when the email isn't local),activateAccount,deleteAccount,admin.updateAccountPassword,admin.updateAccountEmail,temp.checkSignupQueue - with a service JWT signed by the user's repo key,
aud= entryway DID (a DPoP token can't be forwarded):getSession(strip email withoutemail:read),deactivateAccount, email confirm/update/request, app passwords,requestAccountDelete,getAccountInviteCodes,requestPlcOperationSignature,signPlcOperation,identity.updateHandle,admin.sendEmail - send
x-forwarded-forwith the client IP
- with the caller's own auth header:
- turned off: invite creation and management ("managed by the entryway service"),
/.well-known/atproto-did(404), and resolving handles from local accounts inresolveHandle
not in the MVP #
- relay tokens that carry the client IP for rate limiting, and the rate-limit bypass for entryway calls (#5845, #5850)
- a separate private API URL for calls to the entryway (#5846)
- the admin-password fallback on callbacks
done when #
A mock entryway in the zds test suite, built from the reference mock's contract, drives account creation, login, refresh, OAuth access, a handle change, deactivation and deletion through a zds instance in entryway mode, and a zds instance without entryway config behaves exactly as it does today.
This waits on someone with an entryway who wants to run zds. The contract moved weekly in November 2026, so re-read the reference before starting.