atproto pds in zig
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380const std = @import("std");const httpz = @import("httpz");const xrpc = @import("../core/xrpc.zig");const config = @import("../core/config.zig");
pub const Route = enum { cors_preflight, root, api_docs, api_openapi, stats_page, account_page, spaces_redirect, favicon, og_image, health, did_json, oauth_protected_resource, oauth_authorization_server, oauth_jwks, oauth_par, oauth_authorize, oauth_token, oauth_introspect, oauth_revoke, oauth_passkey_options, oauth_passkey_finish, passkeys_redirect, security_redirect, admin_sessions_page, zds_list_account_sessions, zds_list_admin_sessions, atproto_did, describe_server, reserve_signing_key, create_account, create_invite_code, create_invite_codes, get_account_invite_codes, admin_update_subject_status, list_app_passwords, create_app_password, revoke_app_password, start_passkey_registration, finish_passkey_registration, list_passkeys, delete_passkey, update_passkey, create_session, refresh_session, get_session, get_service_auth, activate_account, deactivate_account, request_email_confirmation, confirm_email, request_email_update, update_email, check_account_status, app_preferences_get, app_preferences_put, repo_create_record, repo_put_record, repo_describe_repo, repo_get_record, repo_list_records, repo_delete_record, repo_apply_writes, repo_import_repo, repo_upload_blob, repo_list_missing_blobs, sync_get_blob, sync_get_repo, sync_get_latest_commit, sync_list_repos, sync_list_blobs, sync_subscribe_repos, sync_get_repo_status, sync_notify_of_update, sync_request_crawl, identity_get_recommended_did_credentials, identity_request_plc_operation_signature, identity_sign_plc_operation, identity_submit_plc_operation, identity_resolve_handle, identity_update_handle, permissioned_data, devtools, proxy_xrpc, not_found,};
pub const Endpoint = struct { route: Route, method: []const u8, path: []const u8, group: []const u8, auth: []const u8, summary: []const u8, params: []const []const u8 = &.{}, body: []const []const u8 = &.{}, response: []const []const u8 = &.{}, notes: []const u8 = "",
pub fn nsid(endpoint: Endpoint) ?[]const u8 { const prefix = "/xrpc/"; if (!std.mem.startsWith(u8, endpoint.path, prefix)) return null; return endpoint.path[prefix.len..]; }};
const permissioned_data_note = "Experimental prototype gated by ZDS_PERMISSIONED_DATA; shape may change with upstream permissioned-data drafts.";const dev_tools_note = "Development tool gated by ZDS_DEV_TOOLS; never enable on a production deployment.";
pub const endpoints = [_]Endpoint{ .{ .route = .api_docs, .method = "GET", .path = "/api", .group = "zds", .auth = "public", .summary = "Interactive endpoint inventory for this ZDS instance." }, .{ .route = .api_docs, .method = "HEAD", .path = "/api", .group = "zds", .auth = "public", .summary = "Endpoint inventory probe without a response body." }, .{ .route = .api_openapi, .method = "GET", .path = "/api/openapi.json", .group = "zds", .auth = "public", .summary = "OpenAPI 3.1 export generated from the same endpoint inventory." }, .{ .route = .api_openapi, .method = "HEAD", .path = "/api/openapi.json", .group = "zds", .auth = "public", .summary = "OpenAPI export probe without a response body." }, .{ .route = .stats_page, .method = "GET", .path = "/stats", .group = "zds", .auth = "public", .summary = "Operational health and route latency page." }, .{ .route = .stats_page, .method = "HEAD", .path = "/stats", .group = "zds", .auth = "public", .summary = "Stats page probe without a response body." }, .{ .route = .account_page, .method = "GET", .path = "/account", .group = "account", .auth = "resident", .summary = "Resident account control center." }, .{ .route = .account_page, .method = "GET", .path = "/account/security", .group = "account", .auth = "resident", .summary = "Resident passkey and app-password management." }, .{ .route = .account_page, .method = "GET", .path = "/account/sessions", .group = "account", .auth = "resident", .summary = "Resident app access and direct API token view." }, .{ .route = .account_page, .method = "GET", .path = "/account/apps", .group = "account", .auth = "resident", .summary = "Compatibility view for app access." }, .{ .route = .account_page, .method = "GET", .path = "/account/spaces", .group = "account", .auth = "resident", .summary = "Resident view for permissioned-space records." }, .{ .route = .account_page, .method = "GET", .path = "/account/manage", .group = "account", .auth = "resident", .summary = "Resident email and account status management." }, .{ .route = .account_page, .method = "GET", .path = "/account/about", .group = "account", .auth = "resident", .summary = "Resident account hosting explainer." }, .{ .route = .spaces_redirect, .method = "GET", .path = "/spaces", .group = "account", .auth = "resident", .summary = "Compatibility redirect to /account/spaces." }, .{ .route = .spaces_redirect, .method = "HEAD", .path = "/spaces", .group = "account", .auth = "resident", .summary = "Compatibility redirect to /account/spaces." }, .{ .route = .root, .method = "GET", .path = "/", .group = "zds", .auth = "public", .summary = "Public landing page for the PDS." }, .{ .route = .root, .method = "HEAD", .path = "/", .group = "zds", .auth = "public", .summary = "Landing page probe." }, .{ .route = .favicon, .method = "GET", .path = "/favicon.svg", .group = "zds", .auth = "public", .summary = "SVG favicon." }, .{ .route = .favicon, .method = "HEAD", .path = "/favicon.svg", .group = "zds", .auth = "public", .summary = "SVG favicon probe." }, .{ .route = .og_image, .method = "GET", .path = "/og-image", .group = "zds", .auth = "public", .summary = "Social preview image." }, .{ .route = .og_image, .method = "HEAD", .path = "/og-image", .group = "zds", .auth = "public", .summary = "Social preview image probe." }, .{ .route = .health, .method = "GET", .path = "/xrpc/_health", .group = "status", .auth = "public", .summary = "PDS health probe.", .response = &.{ "version", "status" } }, .{ .route = .health, .method = "HEAD", .path = "/xrpc/_health", .group = "status", .auth = "public", .summary = "PDS health probe without a response body." }, .{ .route = .did_json, .method = "GET", .path = "/.well-known/did.json", .group = "discovery", .auth = "public", .summary = "DID document for the server DID." }, .{ .route = .atproto_did, .method = "GET", .path = "/.well-known/atproto-did", .group = "discovery", .auth = "public", .summary = "atproto DID discovery document." }, .{ .route = .oauth_protected_resource, .method = "GET", .path = "/.well-known/oauth-protected-resource", .group = "oauth", .auth = "public", .summary = "OAuth protected resource metadata." }, .{ .route = .oauth_authorization_server, .method = "GET", .path = "/.well-known/oauth-authorization-server", .group = "oauth", .auth = "public", .summary = "OAuth authorization server metadata." }, .{ .route = .oauth_jwks, .method = "GET", .path = "/oauth/jwks", .group = "oauth", .auth = "public", .summary = "OAuth signing keys." }, .{ .route = .oauth_par, .method = "POST", .path = "/oauth/par", .group = "oauth", .auth = "client", .summary = "Pushed authorization request endpoint." }, .{ .route = .oauth_authorize, .method = "GET", .path = "/oauth/authorize", .group = "oauth", .auth = "browser", .summary = "Authorization consent page.", .params = &.{"request_uri"} }, .{ .route = .oauth_authorize, .method = "POST", .path = "/oauth/authorize", .group = "oauth", .auth = "browser", .summary = "Authorization consent submission." }, .{ .route = .oauth_token, .method = "POST", .path = "/oauth/token", .group = "oauth", .auth = "client", .summary = "OAuth token endpoint." }, .{ .route = .oauth_introspect, .method = "POST", .path = "/oauth/introspect", .group = "oauth", .auth = "client", .summary = "OAuth token introspection endpoint." }, .{ .route = .oauth_revoke, .method = "POST", .path = "/oauth/revoke", .group = "oauth", .auth = "client", .summary = "OAuth token revocation endpoint." }, .{ .route = .oauth_passkey_options, .method = "POST", .path = "/oauth/passkey/options", .group = "oauth", .auth = "browser", .summary = "Passkey login challenge for OAuth flows." }, .{ .route = .oauth_passkey_finish, .method = "POST", .path = "/oauth/passkey/finish", .group = "oauth", .auth = "browser", .summary = "Passkey login completion for OAuth flows." }, .{ .route = .security_redirect, .method = "GET", .path = "/security", .group = "account", .auth = "resident", .summary = "Compatibility redirect to /account/security." }, .{ .route = .passkeys_redirect, .method = "GET", .path = "/passkeys", .group = "account", .auth = "resident", .summary = "Compatibility redirect to /account/security." }, .{ .route = .admin_sessions_page, .method = "GET", .path = "/admin/sessions", .group = "zds", .auth = "admin", .summary = "Operator session inventory page." }, .{ .route = .zds_list_account_sessions, .method = "GET", .path = "/xrpc/dev.zat.account.listSessions", .group = "zds", .auth = "bearer", .summary = "List active account sessions and OAuth grants for the signed-in account.", .params = &.{ "active", "limit" } }, .{ .route = .zds_list_admin_sessions, .method = "GET", .path = "/xrpc/dev.zat.admin.listSessions", .group = "zds", .auth = "admin", .summary = "List active sessions and OAuth grants across accounts on this PDS.", .params = &.{ "active", "limit" } },
.{ .route = .describe_server, .method = "GET", .path = "/xrpc/com.atproto.server.describeServer", .group = "server", .auth = "public", .summary = "Describe account creation capabilities for this PDS." }, .{ .route = .reserve_signing_key, .method = "POST", .path = "/xrpc/com.atproto.server.reserveSigningKey", .group = "server", .auth = "public", .summary = "Reserve a signing key for account creation." }, .{ .route = .create_account, .method = "POST", .path = "/xrpc/com.atproto.server.createAccount", .group = "server", .auth = "invite", .summary = "Create a new account on this PDS.", .body = &.{ "handle", "email", "password", "inviteCode", "signingKey" } }, .{ .route = .create_invite_code, .method = "POST", .path = "/xrpc/com.atproto.server.createInviteCode", .group = "server", .auth = "admin", .summary = "Create one invite code.", .body = &.{ "useCount", "forAccount" } }, .{ .route = .create_invite_codes, .method = "POST", .path = "/xrpc/com.atproto.server.createInviteCodes", .group = "server", .auth = "admin", .summary = "Create invite codes in bulk.", .body = &.{ "codeCount", "useCount", "forAccounts" } }, .{ .route = .get_account_invite_codes, .method = "GET", .path = "/xrpc/com.atproto.server.getAccountInviteCodes", .group = "server", .auth = "bearer", .summary = "List invite codes associated with the signed-in account." }, .{ .route = .admin_update_subject_status, .method = "POST", .path = "/xrpc/com.atproto.admin.updateSubjectStatus", .group = "admin", .auth = "admin", .summary = "Update account takedown or deactivation status.", .body = &.{ "subject", "takedown", "deactivated" } }, .{ .route = .list_app_passwords, .method = "GET", .path = "/xrpc/com.atproto.server.listAppPasswords", .group = "server", .auth = "bearer", .summary = "List app passwords for the signed-in account." }, .{ .route = .create_app_password, .method = "POST", .path = "/xrpc/com.atproto.server.createAppPassword", .group = "server", .auth = "bearer", .summary = "Create an app password.", .body = &.{ "name", "privileged" } }, .{ .route = .revoke_app_password, .method = "POST", .path = "/xrpc/com.atproto.server.revokeAppPassword", .group = "server", .auth = "bearer", .summary = "Revoke an app password.", .body = &.{"name"} }, .{ .route = .start_passkey_registration, .method = "POST", .path = "/xrpc/com.atproto.server.startPasskeyRegistration", .group = "server", .auth = "bearer", .summary = "Begin passkey registration." }, .{ .route = .finish_passkey_registration, .method = "POST", .path = "/xrpc/com.atproto.server.finishPasskeyRegistration", .group = "server", .auth = "bearer", .summary = "Finish passkey registration." }, .{ .route = .list_passkeys, .method = "GET", .path = "/xrpc/com.atproto.server.listPasskeys", .group = "server", .auth = "bearer", .summary = "List passkeys for the signed-in account." }, .{ .route = .delete_passkey, .method = "POST", .path = "/xrpc/com.atproto.server.deletePasskey", .group = "server", .auth = "bearer", .summary = "Delete a passkey.", .body = &.{"id"} }, .{ .route = .update_passkey, .method = "POST", .path = "/xrpc/com.atproto.server.updatePasskey", .group = "server", .auth = "bearer", .summary = "Rename or update a passkey.", .body = &.{ "id", "name" } }, .{ .route = .create_session, .method = "POST", .path = "/xrpc/com.atproto.server.createSession", .group = "server", .auth = "password", .summary = "Create an app password session.", .body = &.{ "identifier", "password" } }, .{ .route = .refresh_session, .method = "POST", .path = "/xrpc/com.atproto.server.refreshSession", .group = "server", .auth = "refresh bearer", .summary = "Refresh an app password session." }, .{ .route = .get_session, .method = "GET", .path = "/xrpc/com.atproto.server.getSession", .group = "server", .auth = "bearer", .summary = "Describe the current app password session." }, .{ .route = .get_service_auth, .method = "GET", .path = "/xrpc/com.atproto.server.getServiceAuth", .group = "server", .auth = "bearer", .summary = "Mint a service auth token.", .params = &.{ "aud", "exp", "lxm" } }, .{ .route = .activate_account, .method = "POST", .path = "/xrpc/com.atproto.server.activateAccount", .group = "server", .auth = "bearer", .summary = "Reactivate the signed-in account." }, .{ .route = .deactivate_account, .method = "POST", .path = "/xrpc/com.atproto.server.deactivateAccount", .group = "server", .auth = "bearer", .summary = "Deactivate the signed-in account." }, .{ .route = .request_email_confirmation, .method = "POST", .path = "/xrpc/com.atproto.server.requestEmailConfirmation", .group = "server", .auth = "bearer", .summary = "Request an email confirmation token." }, .{ .route = .confirm_email, .method = "POST", .path = "/xrpc/com.atproto.server.confirmEmail", .group = "server", .auth = "bearer", .summary = "Confirm an account email address.", .body = &.{ "email", "token" } }, .{ .route = .request_email_update, .method = "POST", .path = "/xrpc/com.atproto.server.requestEmailUpdate", .group = "server", .auth = "bearer", .summary = "Request an email update token." }, .{ .route = .update_email, .method = "POST", .path = "/xrpc/com.atproto.server.updateEmail", .group = "server", .auth = "bearer", .summary = "Update an account email address.", .body = &.{ "email", "token" } }, .{ .route = .check_account_status, .method = "GET", .path = "/xrpc/com.atproto.server.checkAccountStatus", .group = "server", .auth = "bearer", .summary = "Check account status for the signed-in account." },
.{ .route = .app_preferences_get, .method = "GET", .path = "/xrpc/app.bsky.actor.getPreferences", .group = "preferences", .auth = "bearer", .summary = "Read Bluesky actor preferences." }, .{ .route = .app_preferences_put, .method = "POST", .path = "/xrpc/app.bsky.actor.putPreferences", .group = "preferences", .auth = "bearer", .summary = "Replace Bluesky actor preferences.", .body = &.{"preferences"} },
.{ .route = .repo_create_record, .method = "POST", .path = "/xrpc/com.atproto.repo.createRecord", .group = "repo", .auth = "bearer", .summary = "Create a record in a repository collection.", .body = &.{ "repo", "collection", "rkey", "validate", "record", "swapCommit" } }, .{ .route = .repo_put_record, .method = "POST", .path = "/xrpc/com.atproto.repo.putRecord", .group = "repo", .auth = "bearer", .summary = "Create or replace a record by rkey.", .body = &.{ "repo", "collection", "rkey", "validate", "record", "swapRecord", "swapCommit" } }, .{ .route = .repo_describe_repo, .method = "GET", .path = "/xrpc/com.atproto.repo.describeRepo", .group = "repo", .auth = "public", .summary = "Describe a repository.", .params = &.{"repo"} }, .{ .route = .repo_get_record, .method = "GET", .path = "/xrpc/com.atproto.repo.getRecord", .group = "repo", .auth = "public", .summary = "Read a record.", .params = &.{ "repo", "collection", "rkey", "cid" } }, .{ .route = .repo_list_records, .method = "GET", .path = "/xrpc/com.atproto.repo.listRecords", .group = "repo", .auth = "public", .summary = "List records in a collection.", .params = &.{ "repo", "collection", "limit", "cursor", "reverse" } }, .{ .route = .repo_delete_record, .method = "POST", .path = "/xrpc/com.atproto.repo.deleteRecord", .group = "repo", .auth = "bearer", .summary = "Delete a record.", .body = &.{ "repo", "collection", "rkey", "swapRecord", "swapCommit" } }, .{ .route = .repo_apply_writes, .method = "POST", .path = "/xrpc/com.atproto.repo.applyWrites", .group = "repo", .auth = "bearer", .summary = "Apply multiple repo writes in one commit.", .body = &.{ "repo", "validate", "writes", "swapCommit" } }, .{ .route = .repo_import_repo, .method = "POST", .path = "/xrpc/com.atproto.repo.importRepo", .group = "repo", .auth = "bearer", .summary = "Import a repository CAR." }, .{ .route = .repo_upload_blob, .method = "POST", .path = "/xrpc/com.atproto.repo.uploadBlob", .group = "repo", .auth = "bearer or service", .summary = "Upload a blob for the signed-in repository." }, .{ .route = .repo_list_missing_blobs, .method = "GET", .path = "/xrpc/com.atproto.repo.listMissingBlobs", .group = "repo", .auth = "bearer", .summary = "List blob CIDs referenced by records but missing from storage.", .params = &.{ "cursor", "limit" } },
.{ .route = .sync_get_blob, .method = "GET", .path = "/xrpc/com.atproto.sync.getBlob", .group = "sync", .auth = "public", .summary = "Fetch a repository blob.", .params = &.{ "did", "cid" } }, .{ .route = .sync_get_blob, .method = "HEAD", .path = "/xrpc/com.atproto.sync.getBlob", .group = "sync", .auth = "public", .summary = "Probe a repository blob without a response body.", .params = &.{ "did", "cid" } }, .{ .route = .sync_get_repo, .method = "GET", .path = "/xrpc/com.atproto.sync.getRepo", .group = "sync", .auth = "public", .summary = "Fetch a repository CAR.", .params = &.{ "did", "since" } }, .{ .route = .sync_get_repo, .method = "HEAD", .path = "/xrpc/com.atproto.sync.getRepo", .group = "sync", .auth = "public", .summary = "Probe a repository CAR without a response body.", .params = &.{ "did", "since" } }, .{ .route = .sync_get_latest_commit, .method = "GET", .path = "/xrpc/com.atproto.sync.getLatestCommit", .group = "sync", .auth = "public", .summary = "Fetch the latest commit CID and revision.", .params = &.{"did"} }, .{ .route = .sync_get_latest_commit, .method = "HEAD", .path = "/xrpc/com.atproto.sync.getLatestCommit", .group = "sync", .auth = "public", .summary = "Probe latest commit metadata without a response body.", .params = &.{"did"} }, .{ .route = .sync_list_repos, .method = "GET", .path = "/xrpc/com.atproto.sync.listRepos", .group = "sync", .auth = "public", .summary = "List hosted repositories.", .params = &.{ "limit", "cursor" } }, .{ .route = .sync_list_repos, .method = "HEAD", .path = "/xrpc/com.atproto.sync.listRepos", .group = "sync", .auth = "public", .summary = "Probe hosted repositories without a response body.", .params = &.{ "limit", "cursor" } }, .{ .route = .sync_list_blobs, .method = "GET", .path = "/xrpc/com.atproto.sync.listBlobs", .group = "sync", .auth = "public", .summary = "List repository blob CIDs.", .params = &.{ "did", "since", "limit", "cursor" } }, .{ .route = .sync_list_blobs, .method = "HEAD", .path = "/xrpc/com.atproto.sync.listBlobs", .group = "sync", .auth = "public", .summary = "Probe repository blob CIDs without a response body.", .params = &.{ "did", "since", "limit", "cursor" } }, .{ .route = .sync_subscribe_repos, .method = "GET", .path = "/xrpc/com.atproto.sync.subscribeRepos", .group = "sync", .auth = "websocket", .summary = "Subscribe to repository commit events.", .params = &.{"cursor"}, .notes = "Upgrades to a websocket connection." }, .{ .route = .sync_get_repo_status, .method = "GET", .path = "/xrpc/com.atproto.sync.getRepoStatus", .group = "sync", .auth = "public", .summary = "Read repository availability status.", .params = &.{"did"} }, .{ .route = .sync_notify_of_update, .method = "POST", .path = "/xrpc/com.atproto.sync.notifyOfUpdate", .group = "sync", .auth = "public", .summary = "Notify this PDS that another repo has updated.", .body = &.{"hostname"} }, .{ .route = .sync_request_crawl, .method = "POST", .path = "/xrpc/com.atproto.sync.requestCrawl", .group = "sync", .auth = "public", .summary = "Ask a relay to crawl this PDS.", .body = &.{"hostname"} },
.{ .route = .identity_get_recommended_did_credentials, .method = "GET", .path = "/xrpc/com.atproto.identity.getRecommendedDidCredentials", .group = "identity", .auth = "bearer", .summary = "Get recommended DID credentials for the signed-in account." }, .{ .route = .identity_request_plc_operation_signature, .method = "POST", .path = "/xrpc/com.atproto.identity.requestPlcOperationSignature", .group = "identity", .auth = "bearer", .summary = "Request a PLC operation signature." }, .{ .route = .identity_sign_plc_operation, .method = "POST", .path = "/xrpc/com.atproto.identity.signPlcOperation", .group = "identity", .auth = "bearer", .summary = "Sign a PLC operation." }, .{ .route = .identity_submit_plc_operation, .method = "POST", .path = "/xrpc/com.atproto.identity.submitPlcOperation", .group = "identity", .auth = "bearer", .summary = "Submit a PLC operation." }, .{ .route = .identity_resolve_handle, .method = "GET", .path = "/xrpc/com.atproto.identity.resolveHandle", .group = "identity", .auth = "public", .summary = "Resolve a handle to a DID.", .params = &.{"handle"} }, .{ .route = .identity_update_handle, .method = "POST", .path = "/xrpc/com.atproto.identity.updateHandle", .group = "identity", .auth = "bearer", .summary = "Update the signed-in account's handle.", .body = &.{"handle"} },
.{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.getSpace", .group = "space", .auth = "experimental bearer", .summary = "Read space configuration from its authority host.", .params = &.{"space"}, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.listSpaces", .group = "space", .auth = "experimental bearer", .summary = "List permissioned repos held by the authenticated user, grouped by space.", .params = &.{ "did", "type", "limit", "cursor" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.listRepos", .group = "space", .auth = "experimental space credential", .summary = "List the authority's known writer repos and commit digests.", .params = &.{ "space", "limit", "cursor" }, .notes = permissioned_data_note },
.{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.getDelegationToken", .group = "space", .auth = "experimental OAuth", .summary = "Create a delegation token for exchange with a space authority.", .params = &.{"space"}, .notes = permissioned_data_note },
.{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.space.createRecord", .group = "space", .auth = "experimental bearer", .summary = "Create a record inside a permissioned data space.", .body = &.{ "space", "repo", "collection", "rkey", "validate", "record" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.space.putRecord", .group = "space", .auth = "experimental bearer", .summary = "Create or update a record inside a permissioned data space.", .body = &.{ "space", "repo", "collection", "rkey", "validate", "record" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.space.deleteRecord", .group = "space", .auth = "experimental bearer", .summary = "Delete a record inside a permissioned data space.", .body = &.{ "space", "repo", "collection", "rkey" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.space.applyWrites", .group = "space", .auth = "experimental bearer", .summary = "Apply a batch of writes inside a permissioned data space.", .body = &.{ "space", "repo", "validate", "writes" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.getRecord", .group = "space", .auth = "experimental bearer or space credential", .summary = "Read a record from a permissioned data space.", .params = &.{ "space", "repo", "collection", "rkey" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.listRecords", .group = "space", .auth = "experimental bearer or space credential", .summary = "List records in a permissioned data space. Values are included unless excludeValues=true.", .params = &.{ "space", "repo", "collection", "limit", "cursor", "reverse", "excludeValues" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.getBlob", .group = "space", .auth = "experimental bearer or space credential", .summary = "Read a blob referenced from a permissioned data record.", .params = &.{ "space", "repo", "cid" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.getLatestCommit", .group = "space", .auth = "experimental bearer or space credential", .summary = "Read the current signed commit for a writer repo in a space.", .params = &.{ "space", "repo" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.getRepo", .group = "space", .auth = "experimental bearer or space credential", .summary = "Download a full permissioned repo CAR for recovery.", .params = &.{ "space", "repo" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.space.listRepoOps", .group = "space", .auth = "experimental bearer or space credential", .summary = "Read incremental record operations. Values are included unless excludeValues=true.", .params = &.{ "space", "repo", "since", "limit", "excludeValues" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.space.registerNotify", .group = "space", .auth = "experimental space credential", .summary = "Register an expiring write-notification endpoint for a space or repo.", .body = &.{ "space", "repo", "endpoint" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.space.notifyWrite", .group = "space", .auth = "experimental service", .summary = "Notify a space authority or syncing service of a permissioned data write.", .body = &.{ "space", "repo", "rev", "hash" }, .notes = permissioned_data_note },
.{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.space.getSpaceCredential", .group = "space", .auth = "experimental delegation token", .summary = "Exchange a delegation token for a space credential.", .body = &.{ "space", "clientAttestation" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.space.notifySpaceDeleted", .group = "space", .auth = "experimental service", .summary = "Notify a repo host or syncing service that a space was deleted.", .body = &.{"space"}, .notes = permissioned_data_note },
.{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.simplespace.createSpace", .group = "simplespace", .auth = "experimental bearer", .summary = "Create or materialize a baseline PDS-managed permissioned data space.", .body = &.{ "did", "type", "skey", "config" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.simplespace.updateSpace", .group = "simplespace", .auth = "experimental bearer", .summary = "Update baseline PDS-managed space configuration.", .body = &.{ "space", "managingApp", "policy", "appAccess" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.simplespace.deleteSpace", .group = "simplespace", .auth = "experimental bearer", .summary = "Delete a baseline PDS-managed space.", .body = &.{"space"}, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.simplespace.addMember", .group = "simplespace", .auth = "experimental bearer", .summary = "Add a DID to a simplespace member-list policy.", .body = &.{ "space", "did" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.simplespace.join", .group = "simplespace", .auth = "experimental bearer", .summary = "Redeem an at.linji.invite record to join a baseline PDS-managed space.", .body = &.{ "space", "invite" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.simplespace.inviteInfo", .group = "simplespace", .auth = "experimental bearer", .summary = "Preview an invite's validity for a space without being a member.", .body = &.{ "space", "invite" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "POST", .path = "/xrpc/com.atproto.simplespace.removeMember", .group = "simplespace", .auth = "experimental bearer", .summary = "Remove a DID from a simplespace member-list policy.", .body = &.{ "space", "did" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.simplespace.listMembers", .group = "simplespace", .auth = "experimental bearer", .summary = "List DIDs in a simplespace member-list policy.", .params = &.{ "space", "limit", "cursor" }, .notes = permissioned_data_note }, .{ .route = .permissioned_data, .method = "GET", .path = "/xrpc/com.atproto.simplespace.checkUserAccess", .group = "simplespace", .auth = "experimental service", .summary = "Ask a managing app whether a user may access a space. Generic PDS handling denies by default.", .params = &.{ "space", "user", "clientId" }, .notes = permissioned_data_note },
.{ .route = .devtools, .method = "GET", .path = "/xrpc/dev.zat.debug.getClock", .group = "dev", .auth = "public", .summary = "Read the server clock and dev clock offset.", .response = &.{ "now", "offsetSeconds" }, .notes = dev_tools_note }, .{ .route = .devtools, .method = "POST", .path = "/xrpc/dev.zat.debug.setClock", .group = "dev", .auth = "public", .summary = "Set the dev clock offset in seconds; shifts every server wall-clock read.", .body = &.{"offsetSeconds"}, .response = &.{ "ok", "offsetSeconds" }, .notes = dev_tools_note }, .{ .route = .devtools, .method = "POST", .path = "/xrpc/dev.zat.debug.expireTokens", .group = "dev", .auth = "public", .summary = "Force-expire all OAuth access tokens (refresh tokens untouched) for client refresh-flow testing.", .response = &.{"ok"}, .notes = dev_tools_note },};
pub fn route(method: httpz.Method, target: []const u8) Route { if (method == .OPTIONS) return .cors_preflight;
const path = stripQuery(target); for (endpoints) |endpoint| { if (endpoint.route == .devtools and !config.devTools()) continue; if (methodMatches(endpoint.method, method) and std.mem.eql(u8, path, endpoint.path)) return endpoint.route; }
return .not_found;}
fn methodMatches(expected: []const u8, actual: httpz.Method) bool { return switch (actual) { .GET => std.mem.eql(u8, expected, "GET"), .HEAD => std.mem.eql(u8, expected, "HEAD"), .POST => std.mem.eql(u8, expected, "POST"), else => false, };}
fn stripQuery(target: []const u8) []const u8 { const end = std.mem.indexOfScalar(u8, target, '?') orelse target.len; return target[0..end];}
test "routes pds probes" { try std.testing.expectEqual(Route.cors_preflight, route(.OPTIONS, "/anything")); try std.testing.expectEqual(Route.root, route(.GET, "/")); try std.testing.expectEqual(Route.root, route(.HEAD, "/")); try std.testing.expectEqual(Route.api_docs, route(.GET, "/api")); try std.testing.expectEqual(Route.api_docs, route(.HEAD, "/api")); try std.testing.expectEqual(Route.api_docs, route(.GET, "/api?group=repo")); try std.testing.expectEqual(Route.api_openapi, route(.GET, "/api/openapi.json")); try std.testing.expectEqual(Route.api_openapi, route(.HEAD, "/api/openapi.json")); try std.testing.expectEqual(Route.stats_page, route(.GET, "/stats")); try std.testing.expectEqual(Route.stats_page, route(.HEAD, "/stats")); try std.testing.expectEqual(Route.account_page, route(.GET, "/account")); try std.testing.expectEqual(Route.account_page, route(.GET, "/account/security")); try std.testing.expectEqual(Route.account_page, route(.GET, "/account/sessions")); try std.testing.expectEqual(Route.account_page, route(.GET, "/account/apps")); try std.testing.expectEqual(Route.account_page, route(.GET, "/account/spaces")); try std.testing.expectEqual(Route.account_page, route(.GET, "/account/manage")); try std.testing.expectEqual(Route.account_page, route(.GET, "/account/about")); try std.testing.expectEqual(Route.spaces_redirect, route(.GET, "/spaces")); try std.testing.expectEqual(Route.spaces_redirect, route(.HEAD, "/spaces")); try std.testing.expectEqual(Route.favicon, route(.GET, "/favicon.svg")); try std.testing.expectEqual(Route.favicon, route(.HEAD, "/favicon.svg")); try std.testing.expectEqual(Route.og_image, route(.GET, "/og-image")); try std.testing.expectEqual(Route.og_image, route(.HEAD, "/og-image")); try std.testing.expectEqual(Route.health, route(.GET, "/xrpc/_health")); try std.testing.expectEqual(Route.health, route(.HEAD, "/xrpc/_health")); try std.testing.expectEqual(Route.health, route(.GET, "/xrpc/_health?x=1")); try std.testing.expectEqual(Route.did_json, route(.GET, "/.well-known/did.json")); try std.testing.expectEqual(Route.oauth_protected_resource, route(.GET, "/.well-known/oauth-protected-resource")); try std.testing.expectEqual(Route.oauth_authorization_server, route(.GET, "/.well-known/oauth-authorization-server")); try std.testing.expectEqual(Route.oauth_par, route(.POST, "/oauth/par")); try std.testing.expectEqual(Route.oauth_authorize, route(.GET, "/oauth/authorize?request_uri=x")); try std.testing.expectEqual(Route.oauth_token, route(.POST, "/oauth/token")); try std.testing.expectEqual(Route.oauth_passkey_options, route(.POST, "/oauth/passkey/options")); try std.testing.expectEqual(Route.oauth_passkey_finish, route(.POST, "/oauth/passkey/finish")); try std.testing.expectEqual(Route.passkeys_redirect, route(.GET, "/passkeys")); try std.testing.expectEqual(Route.security_redirect, route(.GET, "/security")); try std.testing.expectEqual(Route.admin_sessions_page, route(.GET, "/admin/sessions")); try std.testing.expectEqual(Route.zds_list_account_sessions, route(.GET, "/xrpc/dev.zat.account.listSessions")); try std.testing.expectEqual(Route.zds_list_admin_sessions, route(.GET, "/xrpc/dev.zat.admin.listSessions")); try std.testing.expectEqual(Route.atproto_did, route(.GET, "/.well-known/atproto-did")); try std.testing.expectEqual(Route.describe_server, route(.GET, "/xrpc/com.atproto.server.describeServer")); try std.testing.expectEqual(Route.reserve_signing_key, route(.POST, "/xrpc/com.atproto.server.reserveSigningKey")); try std.testing.expectEqual(Route.create_account, route(.POST, "/xrpc/com.atproto.server.createAccount")); try std.testing.expectEqual(Route.create_invite_code, route(.POST, "/xrpc/com.atproto.server.createInviteCode")); try std.testing.expectEqual(Route.create_invite_codes, route(.POST, "/xrpc/com.atproto.server.createInviteCodes")); try std.testing.expectEqual(Route.get_account_invite_codes, route(.GET, "/xrpc/com.atproto.server.getAccountInviteCodes")); try std.testing.expectEqual(Route.list_app_passwords, route(.GET, "/xrpc/com.atproto.server.listAppPasswords")); try std.testing.expectEqual(Route.create_app_password, route(.POST, "/xrpc/com.atproto.server.createAppPassword")); try std.testing.expectEqual(Route.revoke_app_password, route(.POST, "/xrpc/com.atproto.server.revokeAppPassword")); try std.testing.expectEqual(Route.start_passkey_registration, route(.POST, "/xrpc/com.atproto.server.startPasskeyRegistration")); try std.testing.expectEqual(Route.finish_passkey_registration, route(.POST, "/xrpc/com.atproto.server.finishPasskeyRegistration")); try std.testing.expectEqual(Route.list_passkeys, route(.GET, "/xrpc/com.atproto.server.listPasskeys")); try std.testing.expectEqual(Route.delete_passkey, route(.POST, "/xrpc/com.atproto.server.deletePasskey")); try std.testing.expectEqual(Route.update_passkey, route(.POST, "/xrpc/com.atproto.server.updatePasskey")); try std.testing.expectEqual(Route.create_session, route(.POST, "/xrpc/com.atproto.server.createSession")); try std.testing.expectEqual(Route.refresh_session, route(.POST, "/xrpc/com.atproto.server.refreshSession")); try std.testing.expectEqual(Route.get_session, route(.GET, "/xrpc/com.atproto.server.getSession")); try std.testing.expectEqual(Route.get_service_auth, route(.GET, "/xrpc/com.atproto.server.getServiceAuth?aud=did%3Aplc%3Aservice")); try std.testing.expectEqual(Route.admin_update_subject_status, route(.POST, "/xrpc/com.atproto.admin.updateSubjectStatus")); try std.testing.expectEqual(Route.app_preferences_get, route(.GET, "/xrpc/app.bsky.actor.getPreferences")); try std.testing.expectEqual(Route.app_preferences_put, route(.POST, "/xrpc/app.bsky.actor.putPreferences")); try std.testing.expectEqual(Route.repo_list_records, route(.GET, "/xrpc/com.atproto.repo.listRecords?repo=alice.test&collection=app.bsky.feed.post")); try std.testing.expectEqual(Route.repo_describe_repo, route(.GET, "/xrpc/com.atproto.repo.describeRepo?repo=alice.test")); try std.testing.expectEqual(Route.repo_create_record, route(.POST, "/xrpc/com.atproto.repo.createRecord")); try std.testing.expectEqual(Route.repo_upload_blob, route(.POST, "/xrpc/com.atproto.repo.uploadBlob")); try std.testing.expectEqual(Route.sync_list_repos, route(.GET, "/xrpc/com.atproto.sync.listRepos?limit=10")); try std.testing.expectEqual(Route.sync_list_repos, route(.HEAD, "/xrpc/com.atproto.sync.listRepos?limit=10")); try std.testing.expectEqual(Route.sync_get_blob, route(.HEAD, "/xrpc/com.atproto.sync.getBlob?did=did%3Aplc%3Aabc&cid=bafk")); try std.testing.expectEqual(Route.sync_get_repo, route(.HEAD, "/xrpc/com.atproto.sync.getRepo?did=did%3Aplc%3Aabc")); try std.testing.expectEqual(Route.sync_get_latest_commit, route(.GET, "/xrpc/com.atproto.sync.getLatestCommit?did=did%3Aplc%3Aabc")); try std.testing.expectEqual(Route.sync_subscribe_repos, route(.GET, "/xrpc/com.atproto.sync.subscribeRepos?cursor=0")); try std.testing.expectEqual(Route.sync_request_crawl, route(.POST, "/xrpc/com.atproto.sync.requestCrawl")); try std.testing.expectEqual(Route.permissioned_data, route(.POST, "/xrpc/com.atproto.simplespace.createSpace")); try std.testing.expectEqual(Route.permissioned_data, route(.GET, "/xrpc/com.atproto.space.listRecords?space=at%3A%2F%2Fdid%3Aplc%3Aabc%2Fspace%2Fkey")); try std.testing.expectEqual(Route.not_found, route(.GET, "/xrpc/app.bsky.ageassurance.getState?countryCode=US")); try std.testing.expectEqual(Route.identity_resolve_handle, route(.GET, "/xrpc/com.atproto.identity.resolveHandle?handle=alice.test")); try std.testing.expectEqual(Route.identity_update_handle, route(.POST, "/xrpc/com.atproto.identity.updateHandle"));}
test "gates dev clock routes behind ZDS_DEV_TOOLS" { try std.testing.expectEqual(Route.not_found, route(.GET, "/xrpc/dev.zat.debug.getClock")); try std.testing.expectEqual(Route.not_found, route(.POST, "/xrpc/dev.zat.debug.setClock")); config.setDevTools(true); defer config.setDevTools(false); try std.testing.expectEqual(Route.devtools, route(.GET, "/xrpc/dev.zat.debug.getClock")); try std.testing.expectEqual(Route.devtools, route(.POST, "/xrpc/dev.zat.debug.setClock"));}
test "does not route wrong methods" { try std.testing.expectEqual(Route.not_found, route(.POST, "/xrpc/_health")); try std.testing.expectEqual(Route.not_found, route(.GET, "/xrpc/com.atproto.server.createSession")); try std.testing.expectEqual(Route.not_found, route(.POST, "/xrpc/dev.zat.account.listSessions")); try std.testing.expectEqual(Route.not_found, route(.GET, "/xrpc/com.atproto.space.createSpace")); try std.testing.expectEqual(Route.not_found, route(.POST, "/xrpc/com.atproto.space.createSpace"));}