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