forked from
tranquil.farm/tranquil-pds
Our Personal Data Server from scratch!
1import { err, ok, type Result } from "./types/result.ts";
2import type {
3 AccessToken,
4 Did,
5 EmailAddress,
6 Handle,
7 Nsid,
8 RefreshToken,
9 Rkey,
10 ScopeSet,
11} from "./types/branded.ts";
12import {
13 unsafeAsAccessToken,
14 unsafeAsDid,
15 unsafeAsEmail,
16 unsafeAsHandle,
17 unsafeAsISODate,
18 unsafeAsRefreshToken,
19 unsafeAsScopeSet,
20} from "./types/branded.ts";
21import {
22 createDPoPProofForRequest,
23 getDPoPNonce,
24 setDPoPNonce,
25} from "./oauth.ts";
26import type {
27 AccountInfo,
28 AccountState,
29 ApiErrorCode,
30 AppPassword,
31 CompletePasskeySetupResponse,
32 ConfirmSignupResult,
33 ContactState,
34 CreateAccountParams,
35 CreateAccountResult,
36 CreateBackupResponse,
37 CreatedAppPassword,
38 CreateRecordResponse,
39 DelegationAuditEntry,
40 DelegationControlledAccount,
41 DelegationController,
42 DelegationScopePreset,
43 DidDocument,
44 DidType,
45 EmailUpdateResponse,
46 EnableTotpResponse,
47 FinishPasskeyRegistrationResponse,
48 GetInviteCodesResponse,
49 InviteCodeInfo,
50 LegacyLoginPreference,
51 ListBackupsResponse,
52 ListPasskeysResponse,
53 ListRecordsResponse,
54 ListReposResponse,
55 ListSessionsResponse,
56 ListTrustedDevicesResponse,
57 NotificationHistoryResponse,
58 NotificationPrefs,
59 PasskeyAccountCreateResponse,
60 PasswordStatus,
61 ReauthPasskeyStartResponse,
62 ReauthResponse,
63 ReauthStatus,
64 RecommendedDidCredentials,
65 RecordResponse,
66 RegenerateBackupCodesResponse,
67 RepoDescription,
68 ResendMigrationVerificationResponse,
69 ReserveSigningKeyResponse,
70 SearchAccountsResponse,
71 ServerConfig,
72 ServerDescription,
73 ServerStats,
74 Session,
75 SetBackupEnabledResponse,
76 SsoLinkedAccount,
77 StartPasskeyRegistrationResponse,
78 SuccessResponse,
79 TotpSecret,
80 TotpStatus,
81 UpdateLegacyLoginResponse,
82 UpdateLocaleResponse,
83 UploadBlobResponse,
84 VerificationChannel,
85 VerifyMigrationEmailResponse,
86 VerifyTokenResponse,
87} from "./types/api.ts";
88
89const API_BASE = "/xrpc";
90
91export class ApiError extends Error {
92 public did?: Did;
93 public reauthMethods?: string[];
94 constructor(
95 public status: number,
96 public error: ApiErrorCode,
97 message: string,
98 did?: string,
99 reauthMethods?: string[],
100 ) {
101 super(message);
102 this.name = "ApiError";
103 this.did = did ? unsafeAsDid(did) : undefined;
104 this.reauthMethods = reauthMethods;
105 }
106}
107
108let tokenRefreshCallback: (() => Promise<AccessToken | null>) | null = null;
109
110export function setTokenRefreshCallback(
111 callback: () => Promise<AccessToken | null>,
112) {
113 tokenRefreshCallback = callback;
114}
115
116interface AuthenticatedFetchOptions {
117 method?: "GET" | "POST";
118 token: AccessToken | RefreshToken;
119 headers?: Record<string, string>;
120 body?: BodyInit;
121}
122
123async function authenticatedFetch(
124 url: string,
125 options: AuthenticatedFetchOptions,
126): Promise<Response> {
127 const { method = "GET", token, headers = {}, body } = options;
128 const fullUrl = url.startsWith("http")
129 ? url
130 : `${globalThis.location.origin}${url}`;
131 const dpopProof = await createDPoPProofForRequest(method, fullUrl, token);
132 const res = await fetch(url, {
133 method,
134 headers: {
135 ...headers,
136 Authorization: `DPoP ${token}`,
137 DPoP: dpopProof,
138 },
139 body,
140 });
141 const dpopNonce = res.headers.get("DPoP-Nonce");
142 if (dpopNonce) {
143 setDPoPNonce(dpopNonce);
144 }
145 return res;
146}
147
148interface XrpcOptions {
149 method?: "GET" | "POST";
150 params?: Record<string, string>;
151 body?: unknown;
152 token?: AccessToken | RefreshToken;
153 skipRetry?: boolean;
154 skipDpopRetry?: boolean;
155}
156
157async function xrpc<T>(method: string, options?: XrpcOptions): Promise<T> {
158 const {
159 method: httpMethod = "GET",
160 params,
161 body,
162 token,
163 skipRetry,
164 skipDpopRetry,
165 } = options ?? {};
166 let url = `${API_BASE}/${method}`;
167 if (params) {
168 const searchParams = new URLSearchParams(params);
169 url += `?${searchParams}`;
170 }
171 const headers: Record<string, string> = {};
172 if (body) {
173 headers["Content-Type"] = "application/json";
174 }
175 const res = token
176 ? await authenticatedFetch(url, {
177 method: httpMethod,
178 token,
179 headers,
180 body: body ? JSON.stringify(body) : undefined,
181 })
182 : await fetch(url, {
183 method: httpMethod,
184 headers,
185 body: body ? JSON.stringify(body) : undefined,
186 });
187 if (!res.ok) {
188 const errData = await res.json().catch(() => ({
189 error: "Unknown",
190 message: res.statusText,
191 }));
192 if (
193 res.status === 401 &&
194 errData.error === "use_dpop_nonce" &&
195 token &&
196 !skipDpopRetry &&
197 getDPoPNonce()
198 ) {
199 return xrpc(method, { ...options, skipDpopRetry: true });
200 }
201 if (
202 res.status === 401 &&
203 (errData.error === "AuthenticationFailed" ||
204 errData.error === "ExpiredToken" ||
205 errData.error === "OAuthExpiredToken") &&
206 token &&
207 tokenRefreshCallback &&
208 !skipRetry
209 ) {
210 const newToken = await tokenRefreshCallback();
211 if (newToken && newToken !== token) {
212 return xrpc(method, { ...options, token: newToken, skipRetry: true });
213 }
214 }
215 const message = res.status === 429
216 ? (errData.message || "Too many requests. Please try again later.")
217 : errData.message;
218 throw new ApiError(
219 res.status,
220 errData.error as ApiErrorCode,
221 message,
222 errData.did,
223 errData.reauthMethods,
224 );
225 }
226 return res.json();
227}
228
229async function xrpcResult<T>(
230 method: string,
231 options?: XrpcOptions,
232): Promise<Result<T, ApiError>> {
233 try {
234 const value = await xrpc<T>(method, options);
235 return ok(value);
236 } catch (e) {
237 if (e instanceof ApiError) {
238 return err(e);
239 }
240 return err(
241 new ApiError(0, "Unknown", e instanceof Error ? e.message : String(e)),
242 );
243 }
244}
245
246export interface VerificationMethod {
247 id: string;
248 type: string;
249 publicKeyMultibase: string;
250}
251
252export type { AppPassword, DidDocument, InviteCodeInfo as InviteCode, Session };
253export type { DidType, VerificationChannel };
254
255function buildContactState(s: Record<string, unknown>): ContactState {
256 const preferredChannel = s.preferredChannel as
257 | VerificationChannel
258 | undefined;
259 const email = s.email ? unsafeAsEmail(s.email as string) : undefined;
260
261 if (preferredChannel) {
262 return {
263 contactKind: "channel",
264 preferredChannel,
265 preferredChannelVerified: Boolean(s.preferredChannelVerified),
266 email,
267 };
268 }
269
270 if (email) {
271 return {
272 contactKind: "email",
273 email,
274 emailConfirmed: Boolean(s.emailConfirmed),
275 };
276 }
277
278 return { contactKind: "none" };
279}
280
281function buildAccountState(s: Record<string, unknown>): AccountState {
282 const status = s.status as string | undefined;
283 const isAdmin = Boolean(s.isAdmin);
284 const active = s.active as boolean | undefined;
285
286 if (status === "migrated") {
287 return {
288 accountKind: "migrated",
289 migratedToPds: (s.migratedToPds as string) || "",
290 migratedAt: s.migratedAt
291 ? unsafeAsISODate(s.migratedAt as string)
292 : unsafeAsISODate(new Date().toISOString()),
293 isAdmin,
294 };
295 }
296
297 if (status === "deactivated" || active === false) {
298 return { accountKind: "deactivated", isAdmin };
299 }
300
301 if (status === "suspended") {
302 return { accountKind: "suspended", isAdmin };
303 }
304
305 return { accountKind: "active", isAdmin };
306}
307
308export function castSession(raw: unknown): Session {
309 const s = raw as Record<string, unknown>;
310 const contact = buildContactState(s);
311 const account = buildAccountState(s);
312
313 return {
314 did: unsafeAsDid(s.did as string),
315 handle: unsafeAsHandle(s.handle as string),
316 accessJwt: unsafeAsAccessToken(s.accessJwt as string),
317 refreshJwt: unsafeAsRefreshToken(s.refreshJwt as string),
318 preferredLocale: s.preferredLocale as string | null | undefined,
319 ...contact,
320 ...account,
321 };
322}
323
324function _castDelegationController(raw: unknown): DelegationController {
325 const c = raw as Record<string, unknown>;
326 return {
327 did: unsafeAsDid(c.did as string),
328 granted_scopes: unsafeAsScopeSet(c.granted_scopes as string),
329 added_at: unsafeAsISODate(c.added_at as string),
330 };
331}
332
333function _castDelegationControlledAccount(
334 raw: unknown,
335): DelegationControlledAccount {
336 const a = raw as Record<string, unknown>;
337 return {
338 did: unsafeAsDid(a.did as string),
339 handle: unsafeAsHandle(a.handle as string),
340 granted_scopes: unsafeAsScopeSet(a.granted_scopes as string),
341 };
342}
343
344function _castDelegationAuditEntry(raw: unknown): DelegationAuditEntry {
345 const e = raw as Record<string, unknown>;
346 return {
347 id: e.id as string,
348 action: e.action as string,
349 actor_did: unsafeAsDid(e.actor_did as string),
350 target_did: e.target_did ? unsafeAsDid(e.target_did as string) : undefined,
351 details: e.details as string | undefined,
352 created_at: unsafeAsISODate(e.created_at as string),
353 };
354}
355
356function _castSsoLinkedAccount(raw: unknown): SsoLinkedAccount {
357 const a = raw as Record<string, unknown>;
358 return {
359 id: a.id as string,
360 provider: a.provider as string,
361 provider_name: a.provider_name as string,
362 provider_username: a.provider_username as string,
363 provider_email: a.provider_email as string | undefined,
364 created_at: unsafeAsISODate(a.created_at as string),
365 last_login_at: a.last_login_at
366 ? unsafeAsISODate(a.last_login_at as string)
367 : undefined,
368 };
369}
370
371export const api = {
372 async createAccount(
373 params: CreateAccountParams,
374 byodToken?: string,
375 ): Promise<CreateAccountResult> {
376 const url = `${API_BASE}/com.atproto.server.createAccount`;
377 const headers: Record<string, string> = {
378 "Content-Type": "application/json",
379 };
380 if (byodToken) {
381 headers["Authorization"] = `Bearer ${byodToken}`;
382 }
383 const response = await fetch(url, {
384 method: "POST",
385 headers,
386 body: JSON.stringify({
387 handle: params.handle,
388 email: params.email,
389 password: params.password,
390 inviteCode: params.inviteCode,
391 didType: params.didType,
392 did: params.did,
393 signingKey: params.signingKey,
394 verificationChannel: params.verificationChannel,
395 discordId: params.discordId,
396 telegramUsername: params.telegramUsername,
397 signalNumber: params.signalNumber,
398 }),
399 });
400 const data = await response.json();
401 if (!response.ok) {
402 throw new ApiError(response.status, data.error, data.message);
403 }
404 return data;
405 },
406
407 async createAccountWithServiceAuth(
408 serviceAuthToken: string,
409 params: {
410 did: Did;
411 handle: Handle;
412 email: EmailAddress;
413 password: string;
414 inviteCode?: string;
415 },
416 ): Promise<Session> {
417 const url = `${API_BASE}/com.atproto.server.createAccount`;
418 const response = await fetch(url, {
419 method: "POST",
420 headers: {
421 "Content-Type": "application/json",
422 "Authorization": `Bearer ${serviceAuthToken}`,
423 },
424 body: JSON.stringify({
425 did: params.did,
426 handle: params.handle,
427 email: params.email,
428 password: params.password,
429 inviteCode: params.inviteCode,
430 }),
431 });
432 const data = await response.json();
433 if (!response.ok) {
434 throw new ApiError(response.status, data.error, data.message);
435 }
436 return castSession(data);
437 },
438
439 confirmSignup(
440 did: Did,
441 verificationCode: string,
442 ): Promise<ConfirmSignupResult> {
443 return xrpc("com.atproto.server.confirmSignup", {
444 method: "POST",
445 body: { did, verificationCode },
446 });
447 },
448
449 resendVerification(did: Did): Promise<{ success: boolean }> {
450 return xrpc("com.atproto.server.resendVerification", {
451 method: "POST",
452 body: { did },
453 });
454 },
455
456 async createSession(identifier: string, password: string): Promise<Session> {
457 const raw = await xrpc<unknown>("com.atproto.server.createSession", {
458 method: "POST",
459 body: { identifier, password },
460 });
461 return castSession(raw);
462 },
463
464 checkEmailVerified(identifier: string): Promise<{ verified: boolean }> {
465 return xrpc("_checkEmailVerified", {
466 method: "POST",
467 body: { identifier },
468 });
469 },
470
471 checkEmailInUse(email: string): Promise<{ inUse: boolean }> {
472 return xrpc("_account.checkEmailInUse", {
473 method: "POST",
474 body: { email },
475 });
476 },
477
478 checkCommsChannelInUse(
479 channel: "email" | "discord" | "telegram" | "signal",
480 identifier: string,
481 ): Promise<{ inUse: boolean }> {
482 return xrpc("_account.checkCommsChannelInUse", {
483 method: "POST",
484 body: { channel, identifier },
485 });
486 },
487
488 async getSession(token: AccessToken): Promise<Session> {
489 const raw = await xrpc<unknown>("com.atproto.server.getSession", { token });
490 return castSession(raw);
491 },
492
493 async refreshSession(refreshJwt: RefreshToken): Promise<Session> {
494 const raw = await xrpc<unknown>("com.atproto.server.refreshSession", {
495 method: "POST",
496 token: refreshJwt,
497 });
498 return castSession(raw);
499 },
500
501 async deleteSession(token: AccessToken): Promise<void> {
502 await xrpc("com.atproto.server.deleteSession", {
503 method: "POST",
504 token,
505 });
506 },
507
508 listAppPasswords(token: AccessToken): Promise<{ passwords: AppPassword[] }> {
509 return xrpc("com.atproto.server.listAppPasswords", { token });
510 },
511
512 createAppPassword(
513 token: AccessToken,
514 name: string,
515 scopes?: string,
516 ): Promise<CreatedAppPassword> {
517 return xrpc("com.atproto.server.createAppPassword", {
518 method: "POST",
519 token,
520 body: { name, scopes },
521 });
522 },
523
524 async revokeAppPassword(token: AccessToken, name: string): Promise<void> {
525 await xrpc("com.atproto.server.revokeAppPassword", {
526 method: "POST",
527 token,
528 body: { name },
529 });
530 },
531
532 getAccountInviteCodes(
533 token: AccessToken,
534 ): Promise<{ codes: InviteCodeInfo[] }> {
535 return xrpc("com.atproto.server.getAccountInviteCodes", { token });
536 },
537
538 createInviteCode(
539 token: AccessToken,
540 useCount: number = 1,
541 ): Promise<{ code: string }> {
542 return xrpc("com.atproto.server.createInviteCode", {
543 method: "POST",
544 token,
545 body: { useCount },
546 });
547 },
548
549 async requestPasswordReset(email: EmailAddress): Promise<void> {
550 await xrpc("com.atproto.server.requestPasswordReset", {
551 method: "POST",
552 body: { email },
553 });
554 },
555
556 async resetPassword(token: string, password: string): Promise<void> {
557 await xrpc("com.atproto.server.resetPassword", {
558 method: "POST",
559 body: { token, password },
560 });
561 },
562
563 requestEmailUpdate(
564 token: AccessToken,
565 newEmail?: string,
566 ): Promise<EmailUpdateResponse> {
567 return xrpc("com.atproto.server.requestEmailUpdate", {
568 method: "POST",
569 token,
570 body: newEmail ? { newEmail } : undefined,
571 });
572 },
573
574 async updateEmail(
575 token: AccessToken,
576 email: string,
577 emailToken?: string,
578 ): Promise<void> {
579 await xrpc("com.atproto.server.updateEmail", {
580 method: "POST",
581 token,
582 body: { email, token: emailToken },
583 });
584 },
585
586 checkEmailUpdateStatus(
587 token: AccessToken,
588 ): Promise<{ pending: boolean; authorized: boolean; newEmail?: string }> {
589 return xrpc("_account.checkEmailUpdateStatus", {
590 method: "GET",
591 token,
592 });
593 },
594
595 async updateHandle(token: AccessToken, handle: Handle): Promise<void> {
596 await xrpc("com.atproto.identity.updateHandle", {
597 method: "POST",
598 token,
599 body: { handle },
600 });
601 },
602
603 async requestAccountDelete(token: AccessToken): Promise<void> {
604 await xrpc("com.atproto.server.requestAccountDelete", {
605 method: "POST",
606 token,
607 });
608 },
609
610 async deleteAccount(
611 did: Did,
612 password: string,
613 deleteToken: string,
614 ): Promise<void> {
615 await xrpc("com.atproto.server.deleteAccount", {
616 method: "POST",
617 body: { did, password, token: deleteToken },
618 });
619 },
620
621 describeServer(): Promise<ServerDescription> {
622 return xrpc("com.atproto.server.describeServer");
623 },
624
625 listRepos(limit?: number): Promise<ListReposResponse> {
626 const params: Record<string, string> = {};
627 if (limit) params.limit = String(limit);
628 return xrpc("com.atproto.sync.listRepos", { params });
629 },
630
631 getNotificationPrefs(token: AccessToken): Promise<NotificationPrefs> {
632 return xrpc("_account.getNotificationPrefs", { token });
633 },
634
635 updateNotificationPrefs(token: AccessToken, prefs: {
636 preferredChannel?: string;
637 discordId?: string;
638 telegramUsername?: string;
639 signalNumber?: string;
640 }): Promise<SuccessResponse> {
641 return xrpc("_account.updateNotificationPrefs", {
642 method: "POST",
643 token,
644 body: prefs,
645 });
646 },
647
648 confirmChannelVerification(
649 token: AccessToken,
650 channel: string,
651 identifier: string,
652 code: string,
653 ): Promise<SuccessResponse> {
654 return xrpc("_account.confirmChannelVerification", {
655 method: "POST",
656 token,
657 body: { channel, identifier, code },
658 });
659 },
660
661 getNotificationHistory(
662 token: AccessToken,
663 ): Promise<NotificationHistoryResponse> {
664 return xrpc("_account.getNotificationHistory", { token });
665 },
666
667 getServerStats(token: AccessToken): Promise<ServerStats> {
668 return xrpc("_admin.getServerStats", { token });
669 },
670
671 getServerConfig(): Promise<ServerConfig> {
672 return xrpc("_server.getConfig");
673 },
674
675 updateServerConfig(
676 token: AccessToken,
677 config: {
678 serverName?: string;
679 primaryColor?: string;
680 primaryColorDark?: string;
681 secondaryColor?: string;
682 secondaryColorDark?: string;
683 logoCid?: string;
684 },
685 ): Promise<SuccessResponse> {
686 return xrpc("_admin.updateServerConfig", {
687 method: "POST",
688 token,
689 body: config,
690 });
691 },
692
693 async uploadBlob(
694 token: AccessToken,
695 file: File,
696 ): Promise<UploadBlobResponse> {
697 const res = await authenticatedFetch("/xrpc/com.atproto.repo.uploadBlob", {
698 method: "POST",
699 token,
700 headers: { "Content-Type": file.type },
701 body: file,
702 });
703 if (!res.ok) {
704 const errData = await res.json().catch(() => ({
705 error: "Unknown",
706 message: res.statusText,
707 }));
708 throw new ApiError(res.status, errData.error, errData.message);
709 }
710 return res.json();
711 },
712
713 async changePassword(
714 token: AccessToken,
715 currentPassword: string,
716 newPassword: string,
717 ): Promise<void> {
718 await xrpc("_account.changePassword", {
719 method: "POST",
720 token,
721 body: { currentPassword, newPassword },
722 });
723 },
724
725 removePassword(token: AccessToken): Promise<SuccessResponse> {
726 return xrpc("_account.removePassword", {
727 method: "POST",
728 token,
729 });
730 },
731
732 setPassword(
733 token: AccessToken,
734 newPassword: string,
735 ): Promise<SuccessResponse> {
736 return xrpc("_account.setPassword", {
737 method: "POST",
738 token,
739 body: { newPassword },
740 });
741 },
742
743 getPasswordStatus(token: AccessToken): Promise<PasswordStatus> {
744 return xrpc("_account.getPasswordStatus", { token });
745 },
746
747 getLegacyLoginPreference(token: AccessToken): Promise<LegacyLoginPreference> {
748 return xrpc("_account.getLegacyLoginPreference", { token });
749 },
750
751 updateLegacyLoginPreference(
752 token: AccessToken,
753 allowLegacyLogin: boolean,
754 ): Promise<UpdateLegacyLoginResponse> {
755 return xrpc("_account.updateLegacyLoginPreference", {
756 method: "POST",
757 token,
758 body: { allowLegacyLogin },
759 });
760 },
761
762 updateLocale(
763 token: AccessToken,
764 preferredLocale: string,
765 ): Promise<UpdateLocaleResponse> {
766 return xrpc("_account.updateLocale", {
767 method: "POST",
768 token,
769 body: { preferredLocale },
770 });
771 },
772
773 listSessions(token: AccessToken): Promise<ListSessionsResponse> {
774 return xrpc("_account.listSessions", { token });
775 },
776
777 async revokeSession(token: AccessToken, sessionId: string): Promise<void> {
778 await xrpc("_account.revokeSession", {
779 method: "POST",
780 token,
781 body: { sessionId },
782 });
783 },
784
785 revokeAllSessions(token: AccessToken): Promise<{ revokedCount: number }> {
786 return xrpc("_account.revokeAllSessions", {
787 method: "POST",
788 token,
789 });
790 },
791
792 searchAccounts(token: AccessToken, options?: {
793 handle?: string;
794 cursor?: string;
795 limit?: number;
796 }): Promise<SearchAccountsResponse> {
797 const params: Record<string, string> = {};
798 if (options?.handle) params.handle = options.handle;
799 if (options?.cursor) params.cursor = options.cursor;
800 if (options?.limit) params.limit = String(options.limit);
801 return xrpc("com.atproto.admin.searchAccounts", { token, params });
802 },
803
804 getInviteCodes(token: AccessToken, options?: {
805 sort?: "recent" | "usage";
806 cursor?: string;
807 limit?: number;
808 }): Promise<GetInviteCodesResponse> {
809 const params: Record<string, string> = {};
810 if (options?.sort) params.sort = options.sort;
811 if (options?.cursor) params.cursor = options.cursor;
812 if (options?.limit) params.limit = String(options.limit);
813 return xrpc("com.atproto.admin.getInviteCodes", { token, params });
814 },
815
816 async disableInviteCodes(
817 token: AccessToken,
818 codes?: string[],
819 accounts?: string[],
820 ): Promise<void> {
821 await xrpc("com.atproto.admin.disableInviteCodes", {
822 method: "POST",
823 token,
824 body: { codes, accounts },
825 });
826 },
827
828 getAccountInfo(token: AccessToken, did: Did): Promise<AccountInfo> {
829 return xrpc("com.atproto.admin.getAccountInfo", { token, params: { did } });
830 },
831
832 async disableAccountInvites(token: AccessToken, account: Did): Promise<void> {
833 await xrpc("com.atproto.admin.disableAccountInvites", {
834 method: "POST",
835 token,
836 body: { account },
837 });
838 },
839
840 async enableAccountInvites(token: AccessToken, account: Did): Promise<void> {
841 await xrpc("com.atproto.admin.enableAccountInvites", {
842 method: "POST",
843 token,
844 body: { account },
845 });
846 },
847
848 async adminDeleteAccount(token: AccessToken, did: Did): Promise<void> {
849 await xrpc("com.atproto.admin.deleteAccount", {
850 method: "POST",
851 token,
852 body: { did },
853 });
854 },
855
856 describeRepo(token: AccessToken, repo: Did): Promise<RepoDescription> {
857 return xrpc("com.atproto.repo.describeRepo", {
858 token,
859 params: { repo },
860 });
861 },
862
863 listRecords(token: AccessToken, repo: Did, collection: Nsid, options?: {
864 limit?: number;
865 cursor?: string;
866 reverse?: boolean;
867 }): Promise<ListRecordsResponse> {
868 const params: Record<string, string> = { repo, collection };
869 if (options?.limit) params.limit = String(options.limit);
870 if (options?.cursor) params.cursor = options.cursor;
871 if (options?.reverse) params.reverse = "true";
872 return xrpc("com.atproto.repo.listRecords", { token, params });
873 },
874
875 getRecord(
876 token: AccessToken,
877 repo: Did,
878 collection: Nsid,
879 rkey: Rkey,
880 ): Promise<RecordResponse> {
881 return xrpc("com.atproto.repo.getRecord", {
882 token,
883 params: { repo, collection, rkey },
884 });
885 },
886
887 createRecord(
888 token: AccessToken,
889 repo: Did,
890 collection: Nsid,
891 record: unknown,
892 rkey?: Rkey,
893 ): Promise<CreateRecordResponse> {
894 return xrpc("com.atproto.repo.createRecord", {
895 method: "POST",
896 token,
897 body: { repo, collection, record, rkey },
898 });
899 },
900
901 putRecord(
902 token: AccessToken,
903 repo: Did,
904 collection: Nsid,
905 rkey: Rkey,
906 record: unknown,
907 ): Promise<CreateRecordResponse> {
908 return xrpc("com.atproto.repo.putRecord", {
909 method: "POST",
910 token,
911 body: { repo, collection, rkey, record },
912 });
913 },
914
915 async deleteRecord(
916 token: AccessToken,
917 repo: Did,
918 collection: Nsid,
919 rkey: Rkey,
920 ): Promise<void> {
921 await xrpc("com.atproto.repo.deleteRecord", {
922 method: "POST",
923 token,
924 body: { repo, collection, rkey },
925 });
926 },
927
928 getTotpStatus(token: AccessToken): Promise<TotpStatus> {
929 return xrpc("com.atproto.server.getTotpStatus", { token });
930 },
931
932 createTotpSecret(token: AccessToken): Promise<TotpSecret> {
933 return xrpc("com.atproto.server.createTotpSecret", {
934 method: "POST",
935 token,
936 });
937 },
938
939 enableTotp(token: AccessToken, code: string): Promise<EnableTotpResponse> {
940 return xrpc("com.atproto.server.enableTotp", {
941 method: "POST",
942 token,
943 body: { code },
944 });
945 },
946
947 disableTotp(
948 token: AccessToken,
949 password: string,
950 code: string,
951 ): Promise<SuccessResponse> {
952 return xrpc("com.atproto.server.disableTotp", {
953 method: "POST",
954 token,
955 body: { password, code },
956 });
957 },
958
959 regenerateBackupCodes(
960 token: AccessToken,
961 password: string,
962 code: string,
963 ): Promise<RegenerateBackupCodesResponse> {
964 return xrpc("com.atproto.server.regenerateBackupCodes", {
965 method: "POST",
966 token,
967 body: { password, code },
968 });
969 },
970
971 startPasskeyRegistration(
972 token: AccessToken,
973 friendlyName?: string,
974 ): Promise<StartPasskeyRegistrationResponse> {
975 return xrpc("com.atproto.server.startPasskeyRegistration", {
976 method: "POST",
977 token,
978 body: { friendlyName },
979 });
980 },
981
982 finishPasskeyRegistration(
983 token: AccessToken,
984 credential: unknown,
985 friendlyName?: string,
986 ): Promise<FinishPasskeyRegistrationResponse> {
987 return xrpc("com.atproto.server.finishPasskeyRegistration", {
988 method: "POST",
989 token,
990 body: { credential, friendlyName },
991 });
992 },
993
994 listPasskeys(token: AccessToken): Promise<ListPasskeysResponse> {
995 return xrpc("com.atproto.server.listPasskeys", { token });
996 },
997
998 async deletePasskey(token: AccessToken, id: string): Promise<void> {
999 await xrpc("com.atproto.server.deletePasskey", {
1000 method: "POST",
1001 token,
1002 body: { id },
1003 });
1004 },
1005
1006 async updatePasskey(
1007 token: AccessToken,
1008 id: string,
1009 friendlyName: string,
1010 ): Promise<void> {
1011 await xrpc("com.atproto.server.updatePasskey", {
1012 method: "POST",
1013 token,
1014 body: { id, friendlyName },
1015 });
1016 },
1017
1018 listTrustedDevices(token: AccessToken): Promise<ListTrustedDevicesResponse> {
1019 return xrpc("_account.listTrustedDevices", { token });
1020 },
1021
1022 revokeTrustedDevice(
1023 token: AccessToken,
1024 deviceId: string,
1025 ): Promise<SuccessResponse> {
1026 return xrpc("_account.revokeTrustedDevice", {
1027 method: "POST",
1028 token,
1029 body: { deviceId },
1030 });
1031 },
1032
1033 updateTrustedDevice(
1034 token: AccessToken,
1035 deviceId: string,
1036 friendlyName: string,
1037 ): Promise<SuccessResponse> {
1038 return xrpc("_account.updateTrustedDevice", {
1039 method: "POST",
1040 token,
1041 body: { deviceId, friendlyName },
1042 });
1043 },
1044
1045 getReauthStatus(token: AccessToken): Promise<ReauthStatus> {
1046 return xrpc("_account.getReauthStatus", { token });
1047 },
1048
1049 reauthPassword(
1050 token: AccessToken,
1051 password: string,
1052 ): Promise<ReauthResponse> {
1053 return xrpc("_account.reauthPassword", {
1054 method: "POST",
1055 token,
1056 body: { password },
1057 });
1058 },
1059
1060 reauthTotp(token: AccessToken, code: string): Promise<ReauthResponse> {
1061 return xrpc("_account.reauthTotp", {
1062 method: "POST",
1063 token,
1064 body: { code },
1065 });
1066 },
1067
1068 reauthPasskeyStart(token: AccessToken): Promise<ReauthPasskeyStartResponse> {
1069 return xrpc("_account.reauthPasskeyStart", {
1070 method: "POST",
1071 token,
1072 });
1073 },
1074
1075 reauthPasskeyFinish(
1076 token: AccessToken,
1077 credential: unknown,
1078 ): Promise<ReauthResponse> {
1079 return xrpc("_account.reauthPasskeyFinish", {
1080 method: "POST",
1081 token,
1082 body: { credential },
1083 });
1084 },
1085
1086 reserveSigningKey(did?: Did): Promise<ReserveSigningKeyResponse> {
1087 return xrpc("com.atproto.server.reserveSigningKey", {
1088 method: "POST",
1089 body: { did },
1090 });
1091 },
1092
1093 getRecommendedDidCredentials(
1094 token: AccessToken,
1095 ): Promise<RecommendedDidCredentials> {
1096 return xrpc("com.atproto.identity.getRecommendedDidCredentials", { token });
1097 },
1098
1099 async activateAccount(token: AccessToken): Promise<void> {
1100 await xrpc("com.atproto.server.activateAccount", {
1101 method: "POST",
1102 token,
1103 });
1104 },
1105
1106 async createPasskeyAccount(params: {
1107 handle: Handle;
1108 email?: EmailAddress;
1109 inviteCode?: string;
1110 didType?: DidType;
1111 did?: Did;
1112 signingKey?: string;
1113 verificationChannel?: VerificationChannel;
1114 discordId?: string;
1115 telegramUsername?: string;
1116 signalNumber?: string;
1117 }, byodToken?: string): Promise<PasskeyAccountCreateResponse> {
1118 const url = `${API_BASE}/_account.createPasskeyAccount`;
1119 const headers: Record<string, string> = {
1120 "Content-Type": "application/json",
1121 };
1122 if (byodToken) {
1123 headers["Authorization"] = `Bearer ${byodToken}`;
1124 }
1125 const res = await fetch(url, {
1126 method: "POST",
1127 headers,
1128 body: JSON.stringify(params),
1129 });
1130 if (!res.ok) {
1131 const errData = await res.json().catch(() => ({
1132 error: "Unknown",
1133 message: res.statusText,
1134 }));
1135 throw new ApiError(res.status, errData.error, errData.message);
1136 }
1137 return res.json();
1138 },
1139
1140 startPasskeyRegistrationForSetup(
1141 did: Did,
1142 setupToken: string,
1143 friendlyName?: string,
1144 ): Promise<StartPasskeyRegistrationResponse> {
1145 return xrpc("_account.startPasskeyRegistrationForSetup", {
1146 method: "POST",
1147 body: { did, setupToken, friendlyName },
1148 });
1149 },
1150
1151 completePasskeySetup(
1152 did: Did,
1153 setupToken: string,
1154 passkeyCredential: unknown,
1155 passkeyFriendlyName?: string,
1156 ): Promise<CompletePasskeySetupResponse> {
1157 return xrpc("_account.completePasskeySetup", {
1158 method: "POST",
1159 body: { did, setupToken, passkeyCredential, passkeyFriendlyName },
1160 });
1161 },
1162
1163 requestPasskeyRecovery(email: EmailAddress): Promise<SuccessResponse> {
1164 return xrpc("_account.requestPasskeyRecovery", {
1165 method: "POST",
1166 body: { email },
1167 });
1168 },
1169
1170 recoverPasskeyAccount(
1171 did: Did,
1172 recoveryToken: string,
1173 newPassword: string,
1174 ): Promise<SuccessResponse> {
1175 return xrpc("_account.recoverPasskeyAccount", {
1176 method: "POST",
1177 body: { did, recoveryToken, newPassword },
1178 });
1179 },
1180
1181 verifyMigrationEmail(
1182 token: string,
1183 email: EmailAddress,
1184 ): Promise<VerifyMigrationEmailResponse> {
1185 return xrpc("com.atproto.server.verifyMigrationEmail", {
1186 method: "POST",
1187 body: { token, email },
1188 });
1189 },
1190
1191 resendMigrationVerification(
1192 email: EmailAddress,
1193 ): Promise<ResendMigrationVerificationResponse> {
1194 return xrpc("com.atproto.server.resendMigrationVerification", {
1195 method: "POST",
1196 body: { email },
1197 });
1198 },
1199
1200 verifyToken(
1201 token: string,
1202 identifier: string,
1203 accessToken?: AccessToken,
1204 ): Promise<VerifyTokenResponse> {
1205 return xrpc("_account.verifyToken", {
1206 method: "POST",
1207 body: { token, identifier },
1208 token: accessToken,
1209 });
1210 },
1211
1212 getDidDocument(token: AccessToken): Promise<DidDocument> {
1213 return xrpc("_account.getDidDocument", { token });
1214 },
1215
1216 updateDidDocument(
1217 token: AccessToken,
1218 params: {
1219 verificationMethods?: VerificationMethod[];
1220 alsoKnownAs?: string[];
1221 serviceEndpoint?: string;
1222 },
1223 ): Promise<SuccessResponse> {
1224 return xrpc("_account.updateDidDocument", {
1225 method: "POST",
1226 token,
1227 body: params,
1228 });
1229 },
1230
1231 async deactivateAccount(
1232 token: AccessToken,
1233 deleteAfter?: string,
1234 ): Promise<void> {
1235 await xrpc("com.atproto.server.deactivateAccount", {
1236 method: "POST",
1237 token,
1238 body: { deleteAfter },
1239 });
1240 },
1241
1242 async getRepo(token: AccessToken, did: Did): Promise<ArrayBuffer> {
1243 const url = `${API_BASE}/com.atproto.sync.getRepo?did=${
1244 encodeURIComponent(did)
1245 }`;
1246 const res = await authenticatedFetch(url, { token });
1247 if (!res.ok) {
1248 const errData = await res.json().catch(() => ({
1249 error: "Unknown",
1250 message: res.statusText,
1251 }));
1252 throw new ApiError(res.status, errData.error, errData.message);
1253 }
1254 return res.arrayBuffer();
1255 },
1256
1257 listBackups(token: AccessToken): Promise<ListBackupsResponse> {
1258 return xrpc("_backup.listBackups", { token });
1259 },
1260
1261 async getBackup(token: AccessToken, id: string): Promise<Blob> {
1262 const url = `${API_BASE}/_backup.getBackup?id=${encodeURIComponent(id)}`;
1263 const res = await authenticatedFetch(url, { token });
1264 if (!res.ok) {
1265 const errData = await res.json().catch(() => ({
1266 error: "Unknown",
1267 message: res.statusText,
1268 }));
1269 throw new ApiError(res.status, errData.error, errData.message);
1270 }
1271 return res.blob();
1272 },
1273
1274 createBackup(token: AccessToken): Promise<CreateBackupResponse> {
1275 return xrpc("_backup.createBackup", {
1276 method: "POST",
1277 token,
1278 });
1279 },
1280
1281 async deleteBackup(token: AccessToken, id: string): Promise<void> {
1282 await xrpc("_backup.deleteBackup", {
1283 method: "POST",
1284 token,
1285 params: { id },
1286 });
1287 },
1288
1289 setBackupEnabled(
1290 token: AccessToken,
1291 enabled: boolean,
1292 ): Promise<SetBackupEnabledResponse> {
1293 return xrpc("_backup.setEnabled", {
1294 method: "POST",
1295 token,
1296 body: { enabled },
1297 });
1298 },
1299
1300 async importRepo(token: AccessToken, car: Uint8Array): Promise<void> {
1301 const res = await authenticatedFetch(
1302 `${API_BASE}/com.atproto.repo.importRepo`,
1303 {
1304 method: "POST",
1305 token,
1306 headers: { "Content-Type": "application/vnd.ipld.car" },
1307 body: car as unknown as BodyInit,
1308 },
1309 );
1310 if (!res.ok) {
1311 const errData = await res.json().catch(() => ({
1312 error: "Unknown",
1313 message: res.statusText,
1314 }));
1315 throw new ApiError(res.status, errData.error, errData.message);
1316 }
1317 },
1318
1319 async establishOAuthSession(
1320 token: AccessToken,
1321 ): Promise<{ success: boolean; device_id: string }> {
1322 const res = await authenticatedFetch("/oauth/establish-session", {
1323 method: "POST",
1324 token,
1325 headers: { "Content-Type": "application/json" },
1326 });
1327 if (!res.ok) {
1328 const errData = await res.json().catch(() => ({
1329 error: "Unknown",
1330 message: res.statusText,
1331 }));
1332 throw new ApiError(res.status, errData.error, errData.message);
1333 }
1334 return res.json();
1335 },
1336
1337 async getSsoLinkedAccounts(
1338 token: AccessToken,
1339 ): Promise<{ accounts: SsoLinkedAccount[] }> {
1340 const res = await authenticatedFetch("/oauth/sso/linked", { token });
1341 if (!res.ok) {
1342 const errData = await res.json().catch(() => ({
1343 error: "Unknown",
1344 message: res.statusText,
1345 }));
1346 throw new ApiError(res.status, errData.error, errData.message);
1347 }
1348 return res.json();
1349 },
1350
1351 listDelegationControllers(
1352 token: AccessToken,
1353 ): Promise<Result<{ controllers: DelegationController[] }, ApiError>> {
1354 return xrpcResult("_delegation.listControllers", { token });
1355 },
1356
1357 listDelegationControlledAccounts(
1358 token: AccessToken,
1359 ): Promise<Result<{ accounts: DelegationControlledAccount[] }, ApiError>> {
1360 return xrpcResult("_delegation.listControlledAccounts", { token });
1361 },
1362
1363 getDelegationScopePresets(): Promise<
1364 Result<{ presets: DelegationScopePreset[] }, ApiError>
1365 > {
1366 return xrpcResult("_delegation.getScopePresets");
1367 },
1368
1369 addDelegationController(
1370 token: AccessToken,
1371 controllerDid: Did,
1372 grantedScopes: ScopeSet,
1373 ): Promise<Result<{ success: boolean }, ApiError>> {
1374 return xrpcResult("_delegation.addController", {
1375 method: "POST",
1376 token,
1377 body: { controller_did: controllerDid, granted_scopes: grantedScopes },
1378 });
1379 },
1380
1381 removeDelegationController(
1382 token: AccessToken,
1383 controllerDid: Did,
1384 ): Promise<Result<{ success: boolean }, ApiError>> {
1385 return xrpcResult("_delegation.removeController", {
1386 method: "POST",
1387 token,
1388 body: { controller_did: controllerDid },
1389 });
1390 },
1391
1392 createDelegatedAccount(
1393 token: AccessToken,
1394 handle: Handle,
1395 email?: EmailAddress,
1396 controllerScopes?: ScopeSet,
1397 ): Promise<Result<{ did: Did; handle: Handle }, ApiError>> {
1398 return xrpcResult("_delegation.createDelegatedAccount", {
1399 method: "POST",
1400 token,
1401 body: { handle, email, controllerScopes },
1402 });
1403 },
1404
1405 getDelegationAuditLog(
1406 token: AccessToken,
1407 limit: number,
1408 offset: number,
1409 ): Promise<
1410 Result<{ entries: DelegationAuditEntry[]; total: number }, ApiError>
1411 > {
1412 return xrpcResult("_delegation.getAuditLog", {
1413 token,
1414 params: { limit: String(limit), offset: String(offset) },
1415 });
1416 },
1417
1418 async exportBlobs(token: AccessToken): Promise<Blob> {
1419 const res = await authenticatedFetch(`${API_BASE}/_backup.exportBlobs`, {
1420 token,
1421 });
1422 if (!res.ok) {
1423 const errData = await res.json().catch(() => ({
1424 error: "Unknown",
1425 message: res.statusText,
1426 }));
1427 throw new ApiError(res.status, errData.error, errData.message);
1428 }
1429 return res.blob();
1430 },
1431};
1432
1433export const typedApi = {
1434 createSession(
1435 identifier: string,
1436 password: string,
1437 ): Promise<Result<Session, ApiError>> {
1438 return xrpcResult<Session>("com.atproto.server.createSession", {
1439 method: "POST",
1440 body: { identifier, password },
1441 }).then((r) => r.ok ? ok(castSession(r.value)) : r);
1442 },
1443
1444 getSession(token: AccessToken): Promise<Result<Session, ApiError>> {
1445 return xrpcResult<Session>("com.atproto.server.getSession", { token })
1446 .then((r) => r.ok ? ok(castSession(r.value)) : r);
1447 },
1448
1449 refreshSession(refreshJwt: RefreshToken): Promise<Result<Session, ApiError>> {
1450 return xrpcResult<Session>("com.atproto.server.refreshSession", {
1451 method: "POST",
1452 token: refreshJwt,
1453 }).then((r) => r.ok ? ok(castSession(r.value)) : r);
1454 },
1455
1456 describeServer(): Promise<Result<ServerDescription, ApiError>> {
1457 return xrpcResult("com.atproto.server.describeServer");
1458 },
1459
1460 listAppPasswords(
1461 token: AccessToken,
1462 ): Promise<Result<{ passwords: AppPassword[] }, ApiError>> {
1463 return xrpcResult("com.atproto.server.listAppPasswords", { token });
1464 },
1465
1466 createAppPassword(
1467 token: AccessToken,
1468 name: string,
1469 scopes?: string,
1470 ): Promise<Result<CreatedAppPassword, ApiError>> {
1471 return xrpcResult("com.atproto.server.createAppPassword", {
1472 method: "POST",
1473 token,
1474 body: { name, scopes },
1475 });
1476 },
1477
1478 revokeAppPassword(
1479 token: AccessToken,
1480 name: string,
1481 ): Promise<Result<void, ApiError>> {
1482 return xrpcResult<void>("com.atproto.server.revokeAppPassword", {
1483 method: "POST",
1484 token,
1485 body: { name },
1486 });
1487 },
1488
1489 listSessions(
1490 token: AccessToken,
1491 ): Promise<Result<ListSessionsResponse, ApiError>> {
1492 return xrpcResult("_account.listSessions", { token });
1493 },
1494
1495 revokeSession(
1496 token: AccessToken,
1497 sessionId: string,
1498 ): Promise<Result<void, ApiError>> {
1499 return xrpcResult<void>("_account.revokeSession", {
1500 method: "POST",
1501 token,
1502 body: { sessionId },
1503 });
1504 },
1505
1506 getTotpStatus(token: AccessToken): Promise<Result<TotpStatus, ApiError>> {
1507 return xrpcResult("com.atproto.server.getTotpStatus", { token });
1508 },
1509
1510 createTotpSecret(token: AccessToken): Promise<Result<TotpSecret, ApiError>> {
1511 return xrpcResult("com.atproto.server.createTotpSecret", {
1512 method: "POST",
1513 token,
1514 });
1515 },
1516
1517 enableTotp(
1518 token: AccessToken,
1519 code: string,
1520 ): Promise<Result<EnableTotpResponse, ApiError>> {
1521 return xrpcResult("com.atproto.server.enableTotp", {
1522 method: "POST",
1523 token,
1524 body: { code },
1525 });
1526 },
1527
1528 disableTotp(
1529 token: AccessToken,
1530 password: string,
1531 code: string,
1532 ): Promise<Result<SuccessResponse, ApiError>> {
1533 return xrpcResult("com.atproto.server.disableTotp", {
1534 method: "POST",
1535 token,
1536 body: { password, code },
1537 });
1538 },
1539
1540 listPasskeys(
1541 token: AccessToken,
1542 ): Promise<Result<ListPasskeysResponse, ApiError>> {
1543 return xrpcResult("com.atproto.server.listPasskeys", { token });
1544 },
1545
1546 deletePasskey(
1547 token: AccessToken,
1548 id: string,
1549 ): Promise<Result<void, ApiError>> {
1550 return xrpcResult<void>("com.atproto.server.deletePasskey", {
1551 method: "POST",
1552 token,
1553 body: { id },
1554 });
1555 },
1556
1557 listTrustedDevices(
1558 token: AccessToken,
1559 ): Promise<Result<ListTrustedDevicesResponse, ApiError>> {
1560 return xrpcResult("_account.listTrustedDevices", { token });
1561 },
1562
1563 getReauthStatus(token: AccessToken): Promise<Result<ReauthStatus, ApiError>> {
1564 return xrpcResult("_account.getReauthStatus", { token });
1565 },
1566
1567 getNotificationPrefs(
1568 token: AccessToken,
1569 ): Promise<Result<NotificationPrefs, ApiError>> {
1570 return xrpcResult("_account.getNotificationPrefs", { token });
1571 },
1572
1573 updateHandle(
1574 token: AccessToken,
1575 handle: Handle,
1576 ): Promise<Result<void, ApiError>> {
1577 return xrpcResult<void>("com.atproto.identity.updateHandle", {
1578 method: "POST",
1579 token,
1580 body: { handle },
1581 });
1582 },
1583
1584 describeRepo(
1585 token: AccessToken,
1586 repo: Did,
1587 ): Promise<Result<RepoDescription, ApiError>> {
1588 return xrpcResult("com.atproto.repo.describeRepo", {
1589 token,
1590 params: { repo },
1591 });
1592 },
1593
1594 listRecords(
1595 token: AccessToken,
1596 repo: Did,
1597 collection: Nsid,
1598 options?: { limit?: number; cursor?: string; reverse?: boolean },
1599 ): Promise<Result<ListRecordsResponse, ApiError>> {
1600 const params: Record<string, string> = { repo, collection };
1601 if (options?.limit) params.limit = String(options.limit);
1602 if (options?.cursor) params.cursor = options.cursor;
1603 if (options?.reverse) params.reverse = "true";
1604 return xrpcResult("com.atproto.repo.listRecords", { token, params });
1605 },
1606
1607 getRecord(
1608 token: AccessToken,
1609 repo: Did,
1610 collection: Nsid,
1611 rkey: Rkey,
1612 ): Promise<Result<RecordResponse, ApiError>> {
1613 return xrpcResult("com.atproto.repo.getRecord", {
1614 token,
1615 params: { repo, collection, rkey },
1616 });
1617 },
1618
1619 deleteRecord(
1620 token: AccessToken,
1621 repo: Did,
1622 collection: Nsid,
1623 rkey: Rkey,
1624 ): Promise<Result<void, ApiError>> {
1625 return xrpcResult<void>("com.atproto.repo.deleteRecord", {
1626 method: "POST",
1627 token,
1628 body: { repo, collection, rkey },
1629 });
1630 },
1631
1632 searchAccounts(
1633 token: AccessToken,
1634 options?: { handle?: string; cursor?: string; limit?: number },
1635 ): Promise<Result<SearchAccountsResponse, ApiError>> {
1636 const params: Record<string, string> = {};
1637 if (options?.handle) params.handle = options.handle;
1638 if (options?.cursor) params.cursor = options.cursor;
1639 if (options?.limit) params.limit = String(options.limit);
1640 return xrpcResult("com.atproto.admin.searchAccounts", { token, params });
1641 },
1642
1643 getAccountInfo(
1644 token: AccessToken,
1645 did: Did,
1646 ): Promise<Result<AccountInfo, ApiError>> {
1647 return xrpcResult("com.atproto.admin.getAccountInfo", {
1648 token,
1649 params: { did },
1650 });
1651 },
1652
1653 getServerStats(token: AccessToken): Promise<Result<ServerStats, ApiError>> {
1654 return xrpcResult("_admin.getServerStats", { token });
1655 },
1656
1657 listBackups(
1658 token: AccessToken,
1659 ): Promise<Result<ListBackupsResponse, ApiError>> {
1660 return xrpcResult("_backup.listBackups", { token });
1661 },
1662
1663 createBackup(
1664 token: AccessToken,
1665 ): Promise<Result<CreateBackupResponse, ApiError>> {
1666 return xrpcResult("_backup.createBackup", {
1667 method: "POST",
1668 token,
1669 });
1670 },
1671
1672 getDidDocument(token: AccessToken): Promise<Result<DidDocument, ApiError>> {
1673 return xrpcResult("_account.getDidDocument", { token });
1674 },
1675
1676 deleteSession(token: AccessToken): Promise<Result<void, ApiError>> {
1677 return xrpcResult<void>("com.atproto.server.deleteSession", {
1678 method: "POST",
1679 token,
1680 });
1681 },
1682
1683 revokeAllSessions(
1684 token: AccessToken,
1685 ): Promise<Result<{ revokedCount: number }, ApiError>> {
1686 return xrpcResult("_account.revokeAllSessions", {
1687 method: "POST",
1688 token,
1689 });
1690 },
1691
1692 getAccountInviteCodes(
1693 token: AccessToken,
1694 ): Promise<Result<{ codes: InviteCodeInfo[] }, ApiError>> {
1695 return xrpcResult("com.atproto.server.getAccountInviteCodes", { token });
1696 },
1697
1698 createInviteCode(
1699 token: AccessToken,
1700 useCount: number = 1,
1701 ): Promise<Result<{ code: string }, ApiError>> {
1702 return xrpcResult("com.atproto.server.createInviteCode", {
1703 method: "POST",
1704 token,
1705 body: { useCount },
1706 });
1707 },
1708
1709 changePassword(
1710 token: AccessToken,
1711 currentPassword: string,
1712 newPassword: string,
1713 ): Promise<Result<void, ApiError>> {
1714 return xrpcResult<void>("_account.changePassword", {
1715 method: "POST",
1716 token,
1717 body: { currentPassword, newPassword },
1718 });
1719 },
1720
1721 getPasswordStatus(
1722 token: AccessToken,
1723 ): Promise<Result<PasswordStatus, ApiError>> {
1724 return xrpcResult("_account.getPasswordStatus", { token });
1725 },
1726
1727 getServerConfig(): Promise<Result<ServerConfig, ApiError>> {
1728 return xrpcResult("_server.getConfig");
1729 },
1730
1731 getLegacyLoginPreference(
1732 token: AccessToken,
1733 ): Promise<Result<LegacyLoginPreference, ApiError>> {
1734 return xrpcResult("_account.getLegacyLoginPreference", { token });
1735 },
1736
1737 updateLegacyLoginPreference(
1738 token: AccessToken,
1739 allowLegacyLogin: boolean,
1740 ): Promise<Result<UpdateLegacyLoginResponse, ApiError>> {
1741 return xrpcResult("_account.updateLegacyLoginPreference", {
1742 method: "POST",
1743 token,
1744 body: { allowLegacyLogin },
1745 });
1746 },
1747
1748 getNotificationHistory(
1749 token: AccessToken,
1750 ): Promise<Result<NotificationHistoryResponse, ApiError>> {
1751 return xrpcResult("_account.getNotificationHistory", { token });
1752 },
1753
1754 updateNotificationPrefs(
1755 token: AccessToken,
1756 prefs: {
1757 preferredChannel?: string;
1758 discordId?: string;
1759 telegramUsername?: string;
1760 signalNumber?: string;
1761 },
1762 ): Promise<Result<SuccessResponse, ApiError>> {
1763 return xrpcResult("_account.updateNotificationPrefs", {
1764 method: "POST",
1765 token,
1766 body: prefs,
1767 });
1768 },
1769
1770 revokeTrustedDevice(
1771 token: AccessToken,
1772 deviceId: string,
1773 ): Promise<Result<SuccessResponse, ApiError>> {
1774 return xrpcResult("_account.revokeTrustedDevice", {
1775 method: "POST",
1776 token,
1777 body: { deviceId },
1778 });
1779 },
1780
1781 updateTrustedDevice(
1782 token: AccessToken,
1783 deviceId: string,
1784 friendlyName: string,
1785 ): Promise<Result<SuccessResponse, ApiError>> {
1786 return xrpcResult("_account.updateTrustedDevice", {
1787 method: "POST",
1788 token,
1789 body: { deviceId, friendlyName },
1790 });
1791 },
1792
1793 reauthPassword(
1794 token: AccessToken,
1795 password: string,
1796 ): Promise<Result<ReauthResponse, ApiError>> {
1797 return xrpcResult("_account.reauthPassword", {
1798 method: "POST",
1799 token,
1800 body: { password },
1801 });
1802 },
1803
1804 reauthTotp(
1805 token: AccessToken,
1806 code: string,
1807 ): Promise<Result<ReauthResponse, ApiError>> {
1808 return xrpcResult("_account.reauthTotp", {
1809 method: "POST",
1810 token,
1811 body: { code },
1812 });
1813 },
1814
1815 reauthPasskeyStart(
1816 token: AccessToken,
1817 ): Promise<Result<ReauthPasskeyStartResponse, ApiError>> {
1818 return xrpcResult("_account.reauthPasskeyStart", {
1819 method: "POST",
1820 token,
1821 });
1822 },
1823
1824 reauthPasskeyFinish(
1825 token: AccessToken,
1826 credential: unknown,
1827 ): Promise<Result<ReauthResponse, ApiError>> {
1828 return xrpcResult("_account.reauthPasskeyFinish", {
1829 method: "POST",
1830 token,
1831 body: { credential },
1832 });
1833 },
1834
1835 confirmSignup(
1836 did: Did,
1837 verificationCode: string,
1838 ): Promise<Result<ConfirmSignupResult, ApiError>> {
1839 return xrpcResult("com.atproto.server.confirmSignup", {
1840 method: "POST",
1841 body: { did, verificationCode },
1842 });
1843 },
1844
1845 resendVerification(
1846 did: Did,
1847 ): Promise<Result<{ success: boolean }, ApiError>> {
1848 return xrpcResult("com.atproto.server.resendVerification", {
1849 method: "POST",
1850 body: { did },
1851 });
1852 },
1853
1854 requestEmailUpdate(
1855 token: AccessToken,
1856 ): Promise<Result<EmailUpdateResponse, ApiError>> {
1857 return xrpcResult("com.atproto.server.requestEmailUpdate", {
1858 method: "POST",
1859 token,
1860 });
1861 },
1862
1863 updateEmail(
1864 token: AccessToken,
1865 email: string,
1866 emailToken?: string,
1867 ): Promise<Result<void, ApiError>> {
1868 return xrpcResult<void>("com.atproto.server.updateEmail", {
1869 method: "POST",
1870 token,
1871 body: { email, token: emailToken },
1872 });
1873 },
1874
1875 requestAccountDelete(token: AccessToken): Promise<Result<void, ApiError>> {
1876 return xrpcResult<void>("com.atproto.server.requestAccountDelete", {
1877 method: "POST",
1878 token,
1879 });
1880 },
1881
1882 deleteAccount(
1883 did: Did,
1884 password: string,
1885 deleteToken: string,
1886 ): Promise<Result<void, ApiError>> {
1887 return xrpcResult<void>("com.atproto.server.deleteAccount", {
1888 method: "POST",
1889 body: { did, password, token: deleteToken },
1890 });
1891 },
1892
1893 updateDidDocument(
1894 token: AccessToken,
1895 params: {
1896 verificationMethods?: VerificationMethod[];
1897 alsoKnownAs?: string[];
1898 serviceEndpoint?: string;
1899 },
1900 ): Promise<Result<SuccessResponse, ApiError>> {
1901 return xrpcResult("_account.updateDidDocument", {
1902 method: "POST",
1903 token,
1904 body: params,
1905 });
1906 },
1907
1908 deactivateAccount(
1909 token: AccessToken,
1910 deleteAfter?: string,
1911 ): Promise<Result<void, ApiError>> {
1912 return xrpcResult<void>("com.atproto.server.deactivateAccount", {
1913 method: "POST",
1914 token,
1915 body: { deleteAfter },
1916 });
1917 },
1918
1919 activateAccount(token: AccessToken): Promise<Result<void, ApiError>> {
1920 return xrpcResult<void>("com.atproto.server.activateAccount", {
1921 method: "POST",
1922 token,
1923 });
1924 },
1925
1926 setBackupEnabled(
1927 token: AccessToken,
1928 enabled: boolean,
1929 ): Promise<Result<SetBackupEnabledResponse, ApiError>> {
1930 return xrpcResult("_backup.setEnabled", {
1931 method: "POST",
1932 token,
1933 body: { enabled },
1934 });
1935 },
1936
1937 deleteBackup(
1938 token: AccessToken,
1939 id: string,
1940 ): Promise<Result<void, ApiError>> {
1941 return xrpcResult<void>("_backup.deleteBackup", {
1942 method: "POST",
1943 token,
1944 params: { id },
1945 });
1946 },
1947
1948 createRecord(
1949 token: AccessToken,
1950 repo: Did,
1951 collection: Nsid,
1952 record: unknown,
1953 rkey?: Rkey,
1954 ): Promise<Result<CreateRecordResponse, ApiError>> {
1955 return xrpcResult("com.atproto.repo.createRecord", {
1956 method: "POST",
1957 token,
1958 body: { repo, collection, record, rkey },
1959 });
1960 },
1961
1962 putRecord(
1963 token: AccessToken,
1964 repo: Did,
1965 collection: Nsid,
1966 rkey: Rkey,
1967 record: unknown,
1968 ): Promise<Result<CreateRecordResponse, ApiError>> {
1969 return xrpcResult("com.atproto.repo.putRecord", {
1970 method: "POST",
1971 token,
1972 body: { repo, collection, rkey, record },
1973 });
1974 },
1975
1976 getInviteCodes(
1977 token: AccessToken,
1978 options?: { sort?: "recent" | "usage"; cursor?: string; limit?: number },
1979 ): Promise<Result<GetInviteCodesResponse, ApiError>> {
1980 const params: Record<string, string> = {};
1981 if (options?.sort) params.sort = options.sort;
1982 if (options?.cursor) params.cursor = options.cursor;
1983 if (options?.limit) params.limit = String(options.limit);
1984 return xrpcResult("com.atproto.admin.getInviteCodes", { token, params });
1985 },
1986
1987 disableAccountInvites(
1988 token: AccessToken,
1989 account: Did,
1990 ): Promise<Result<void, ApiError>> {
1991 return xrpcResult<void>("com.atproto.admin.disableAccountInvites", {
1992 method: "POST",
1993 token,
1994 body: { account },
1995 });
1996 },
1997
1998 enableAccountInvites(
1999 token: AccessToken,
2000 account: Did,
2001 ): Promise<Result<void, ApiError>> {
2002 return xrpcResult<void>("com.atproto.admin.enableAccountInvites", {
2003 method: "POST",
2004 token,
2005 body: { account },
2006 });
2007 },
2008
2009 adminDeleteAccount(
2010 token: AccessToken,
2011 did: Did,
2012 ): Promise<Result<void, ApiError>> {
2013 return xrpcResult<void>("com.atproto.admin.deleteAccount", {
2014 method: "POST",
2015 token,
2016 body: { did },
2017 });
2018 },
2019
2020 startPasskeyRegistration(
2021 token: AccessToken,
2022 friendlyName?: string,
2023 ): Promise<Result<StartPasskeyRegistrationResponse, ApiError>> {
2024 return xrpcResult("com.atproto.server.startPasskeyRegistration", {
2025 method: "POST",
2026 token,
2027 body: { friendlyName },
2028 });
2029 },
2030
2031 finishPasskeyRegistration(
2032 token: AccessToken,
2033 credential: unknown,
2034 friendlyName?: string,
2035 ): Promise<Result<FinishPasskeyRegistrationResponse, ApiError>> {
2036 return xrpcResult("com.atproto.server.finishPasskeyRegistration", {
2037 method: "POST",
2038 token,
2039 body: { credential, friendlyName },
2040 });
2041 },
2042
2043 updatePasskey(
2044 token: AccessToken,
2045 id: string,
2046 friendlyName: string,
2047 ): Promise<Result<void, ApiError>> {
2048 return xrpcResult<void>("com.atproto.server.updatePasskey", {
2049 method: "POST",
2050 token,
2051 body: { id, friendlyName },
2052 });
2053 },
2054
2055 regenerateBackupCodes(
2056 token: AccessToken,
2057 password: string,
2058 code: string,
2059 ): Promise<Result<RegenerateBackupCodesResponse, ApiError>> {
2060 return xrpcResult("com.atproto.server.regenerateBackupCodes", {
2061 method: "POST",
2062 token,
2063 body: { password, code },
2064 });
2065 },
2066
2067 updateLocale(
2068 token: AccessToken,
2069 preferredLocale: string,
2070 ): Promise<Result<UpdateLocaleResponse, ApiError>> {
2071 return xrpcResult("_account.updateLocale", {
2072 method: "POST",
2073 token,
2074 body: { preferredLocale },
2075 });
2076 },
2077
2078 confirmChannelVerification(
2079 token: AccessToken,
2080 channel: string,
2081 identifier: string,
2082 code: string,
2083 ): Promise<Result<SuccessResponse, ApiError>> {
2084 return xrpcResult("_account.confirmChannelVerification", {
2085 method: "POST",
2086 token,
2087 body: { channel, identifier, code },
2088 });
2089 },
2090
2091 removePassword(
2092 token: AccessToken,
2093 ): Promise<Result<SuccessResponse, ApiError>> {
2094 return xrpcResult("_account.removePassword", {
2095 method: "POST",
2096 token,
2097 });
2098 },
2099};