this repo has no description
1const API_BASE = "/xrpc";
2
3export class ApiError extends Error {
4 public did?: string;
5 public reauthMethods?: string[];
6 constructor(
7 public status: number,
8 public error: string,
9 message: string,
10 did?: string,
11 reauthMethods?: string[],
12 ) {
13 super(message);
14 this.name = "ApiError";
15 this.did = did;
16 this.reauthMethods = reauthMethods;
17 }
18}
19
20let tokenRefreshCallback: (() => Promise<string | null>) | null = null;
21
22export function setTokenRefreshCallback(
23 callback: () => Promise<string | null>,
24) {
25 tokenRefreshCallback = callback;
26}
27
28async function xrpc<T>(method: string, options?: {
29 method?: "GET" | "POST";
30 params?: Record<string, string>;
31 body?: unknown;
32 token?: string;
33 skipRetry?: boolean;
34}): Promise<T> {
35 const { method: httpMethod = "GET", params, body, token, skipRetry } =
36 options ?? {};
37 let url = `${API_BASE}/${method}`;
38 if (params) {
39 const searchParams = new URLSearchParams(params);
40 url += `?${searchParams}`;
41 }
42 const headers: Record<string, string> = {};
43 if (token) {
44 headers["Authorization"] = `Bearer ${token}`;
45 }
46 if (body) {
47 headers["Content-Type"] = "application/json";
48 }
49 const res = await fetch(url, {
50 method: httpMethod,
51 headers,
52 body: body ? JSON.stringify(body) : undefined,
53 });
54 if (!res.ok) {
55 const err = await res.json().catch(() => ({
56 error: "Unknown",
57 message: res.statusText,
58 }));
59 if (
60 res.status === 401 &&
61 (err.error === "AuthenticationFailed" || err.error === "ExpiredToken") &&
62 token && tokenRefreshCallback && !skipRetry
63 ) {
64 const newToken = await tokenRefreshCallback();
65 if (newToken && newToken !== token) {
66 return xrpc(method, { ...options, token: newToken, skipRetry: true });
67 }
68 }
69 throw new ApiError(
70 res.status,
71 err.error,
72 err.message,
73 err.did,
74 err.reauthMethods,
75 );
76 }
77 return res.json();
78}
79
80export interface Session {
81 did: string;
82 handle: string;
83 email?: string;
84 emailConfirmed?: boolean;
85 preferredChannel?: string;
86 preferredChannelVerified?: boolean;
87 isAdmin?: boolean;
88 active?: boolean;
89 status?: "active" | "deactivated" | "migrated";
90 migratedToPds?: string;
91 migratedAt?: string;
92 accessJwt: string;
93 refreshJwt: string;
94}
95
96export interface VerificationMethod {
97 id: string;
98 type: string;
99 publicKeyMultibase: string;
100}
101
102export interface DidDocument {
103 "@context": string[];
104 id: string;
105 alsoKnownAs: string[];
106 verificationMethod: Array<{
107 id: string;
108 type: string;
109 controller: string;
110 publicKeyMultibase: string;
111 }>;
112 service: Array<{
113 id: string;
114 type: string;
115 serviceEndpoint: string;
116 }>;
117}
118
119export interface AppPassword {
120 name: string;
121 createdAt: string;
122 scopes?: string;
123 createdByController?: string;
124}
125
126export interface InviteCode {
127 code: string;
128 available: number;
129 disabled: boolean;
130 forAccount: string;
131 createdBy: string;
132 createdAt: string;
133 uses: { usedBy: string; usedByHandle?: string; usedAt: string }[];
134}
135
136export type VerificationChannel = "email" | "discord" | "telegram" | "signal";
137
138export type DidType = "plc" | "web" | "web-external";
139
140export interface CreateAccountParams {
141 handle: string;
142 email: string;
143 password: string;
144 inviteCode?: string;
145 didType?: DidType;
146 did?: string;
147 signingKey?: string;
148 verificationChannel?: VerificationChannel;
149 discordId?: string;
150 telegramUsername?: string;
151 signalNumber?: string;
152}
153
154export interface CreateAccountResult {
155 handle: string;
156 did: string;
157 verificationRequired: boolean;
158 verificationChannel: string;
159}
160
161export interface ConfirmSignupResult {
162 accessJwt: string;
163 refreshJwt: string;
164 handle: string;
165 did: string;
166 email?: string;
167 emailConfirmed?: boolean;
168 preferredChannel?: string;
169 preferredChannelVerified?: boolean;
170}
171
172export const api = {
173 async createAccount(
174 params: CreateAccountParams,
175 byodToken?: string,
176 ): Promise<CreateAccountResult> {
177 const url = `${API_BASE}/com.atproto.server.createAccount`;
178 const headers: Record<string, string> = {
179 "Content-Type": "application/json",
180 };
181 if (byodToken) {
182 headers["Authorization"] = `Bearer ${byodToken}`;
183 }
184 const response = await fetch(url, {
185 method: "POST",
186 headers,
187 body: JSON.stringify({
188 handle: params.handle,
189 email: params.email,
190 password: params.password,
191 inviteCode: params.inviteCode,
192 didType: params.didType,
193 did: params.did,
194 signingKey: params.signingKey,
195 verificationChannel: params.verificationChannel,
196 discordId: params.discordId,
197 telegramUsername: params.telegramUsername,
198 signalNumber: params.signalNumber,
199 }),
200 });
201 const data = await response.json();
202 if (!response.ok) {
203 throw new ApiError(response.status, data.error, data.message);
204 }
205 return data;
206 },
207
208 async createAccountWithServiceAuth(
209 serviceAuthToken: string,
210 params: {
211 did: string;
212 handle: string;
213 email: string;
214 password: string;
215 inviteCode?: string;
216 },
217 ): Promise<Session> {
218 const url = `${API_BASE}/com.atproto.server.createAccount`;
219 const response = await fetch(url, {
220 method: "POST",
221 headers: {
222 "Content-Type": "application/json",
223 "Authorization": `Bearer ${serviceAuthToken}`,
224 },
225 body: JSON.stringify({
226 did: params.did,
227 handle: params.handle,
228 email: params.email,
229 password: params.password,
230 inviteCode: params.inviteCode,
231 }),
232 });
233 const data = await response.json();
234 if (!response.ok) {
235 throw new ApiError(response.status, data.error, data.message);
236 }
237 return data;
238 },
239
240 confirmSignup(
241 did: string,
242 verificationCode: string,
243 ): Promise<ConfirmSignupResult> {
244 return xrpc("com.atproto.server.confirmSignup", {
245 method: "POST",
246 body: { did, verificationCode },
247 });
248 },
249
250 resendVerification(did: string): Promise<{ success: boolean }> {
251 return xrpc("com.atproto.server.resendVerification", {
252 method: "POST",
253 body: { did },
254 });
255 },
256
257 createSession(identifier: string, password: string): Promise<Session> {
258 return xrpc("com.atproto.server.createSession", {
259 method: "POST",
260 body: { identifier, password },
261 });
262 },
263
264 checkEmailVerified(identifier: string): Promise<{ verified: boolean }> {
265 return xrpc("_checkEmailVerified", {
266 method: "POST",
267 body: { identifier },
268 });
269 },
270
271 getSession(token: string): Promise<Session> {
272 return xrpc("com.atproto.server.getSession", { token });
273 },
274
275 refreshSession(refreshJwt: string): Promise<Session> {
276 return xrpc("com.atproto.server.refreshSession", {
277 method: "POST",
278 token: refreshJwt,
279 });
280 },
281
282 async deleteSession(token: string): Promise<void> {
283 await xrpc("com.atproto.server.deleteSession", {
284 method: "POST",
285 token,
286 });
287 },
288
289 listAppPasswords(token: string): Promise<{ passwords: AppPassword[] }> {
290 return xrpc("com.atproto.server.listAppPasswords", { token });
291 },
292
293 createAppPassword(
294 token: string,
295 name: string,
296 scopes?: string,
297 ): Promise<
298 { name: string; password: string; createdAt: string; scopes?: string }
299 > {
300 return xrpc("com.atproto.server.createAppPassword", {
301 method: "POST",
302 token,
303 body: { name, scopes },
304 });
305 },
306
307 async revokeAppPassword(token: string, name: string): Promise<void> {
308 await xrpc("com.atproto.server.revokeAppPassword", {
309 method: "POST",
310 token,
311 body: { name },
312 });
313 },
314
315 getAccountInviteCodes(token: string): Promise<{ codes: InviteCode[] }> {
316 return xrpc("com.atproto.server.getAccountInviteCodes", { token });
317 },
318
319 createInviteCode(
320 token: string,
321 useCount: number = 1,
322 ): Promise<{ code: string }> {
323 return xrpc("com.atproto.server.createInviteCode", {
324 method: "POST",
325 token,
326 body: { useCount },
327 });
328 },
329
330 async requestPasswordReset(email: string): Promise<void> {
331 await xrpc("com.atproto.server.requestPasswordReset", {
332 method: "POST",
333 body: { email },
334 });
335 },
336
337 async resetPassword(token: string, password: string): Promise<void> {
338 await xrpc("com.atproto.server.resetPassword", {
339 method: "POST",
340 body: { token, password },
341 });
342 },
343
344 requestEmailUpdate(
345 token: string,
346 ): Promise<{ tokenRequired: boolean }> {
347 return xrpc("com.atproto.server.requestEmailUpdate", {
348 method: "POST",
349 token,
350 });
351 },
352
353 async updateEmail(
354 token: string,
355 email: string,
356 emailToken?: string,
357 ): Promise<void> {
358 await xrpc("com.atproto.server.updateEmail", {
359 method: "POST",
360 token,
361 body: { email, token: emailToken },
362 });
363 },
364
365 async updateHandle(token: string, handle: string): Promise<void> {
366 await xrpc("com.atproto.identity.updateHandle", {
367 method: "POST",
368 token,
369 body: { handle },
370 });
371 },
372
373 async requestAccountDelete(token: string): Promise<void> {
374 await xrpc("com.atproto.server.requestAccountDelete", {
375 method: "POST",
376 token,
377 });
378 },
379
380 async deleteAccount(
381 did: string,
382 password: string,
383 deleteToken: string,
384 ): Promise<void> {
385 await xrpc("com.atproto.server.deleteAccount", {
386 method: "POST",
387 body: { did, password, token: deleteToken },
388 });
389 },
390
391 describeServer(): Promise<{
392 availableUserDomains: string[];
393 inviteCodeRequired: boolean;
394 links?: { privacyPolicy?: string; termsOfService?: string };
395 version?: string;
396 availableCommsChannels?: string[];
397 selfHostedDidWebEnabled?: boolean;
398 }> {
399 return xrpc("com.atproto.server.describeServer");
400 },
401
402 listRepos(limit?: number): Promise<{
403 repos: Array<{ did: string; head: string; rev: string }>;
404 cursor?: string;
405 }> {
406 const params: Record<string, string> = {};
407 if (limit) params.limit = String(limit);
408 return xrpc("com.atproto.sync.listRepos", { params });
409 },
410
411 getNotificationPrefs(token: string): Promise<{
412 preferredChannel: string;
413 email: string;
414 discordId: string | null;
415 discordVerified: boolean;
416 telegramUsername: string | null;
417 telegramVerified: boolean;
418 signalNumber: string | null;
419 signalVerified: boolean;
420 }> {
421 return xrpc("_account.getNotificationPrefs", { token });
422 },
423
424 updateNotificationPrefs(token: string, prefs: {
425 preferredChannel?: string;
426 discordId?: string;
427 telegramUsername?: string;
428 signalNumber?: string;
429 }): Promise<{ success: boolean }> {
430 return xrpc("_account.updateNotificationPrefs", {
431 method: "POST",
432 token,
433 body: prefs,
434 });
435 },
436
437 confirmChannelVerification(
438 token: string,
439 channel: string,
440 identifier: string,
441 code: string,
442 ): Promise<{ success: boolean }> {
443 return xrpc("_account.confirmChannelVerification", {
444 method: "POST",
445 token,
446 body: { channel, identifier, code },
447 });
448 },
449
450 getNotificationHistory(token: string): Promise<{
451 notifications: Array<{
452 createdAt: string;
453 channel: string;
454 notificationType: string;
455 status: string;
456 subject: string | null;
457 body: string;
458 }>;
459 }> {
460 return xrpc("_account.getNotificationHistory", { token });
461 },
462
463 getServerStats(token: string): Promise<{
464 userCount: number;
465 repoCount: number;
466 recordCount: number;
467 blobStorageBytes: number;
468 }> {
469 return xrpc("_admin.getServerStats", { token });
470 },
471
472 getServerConfig(): Promise<{
473 serverName: string;
474 primaryColor: string | null;
475 primaryColorDark: string | null;
476 secondaryColor: string | null;
477 secondaryColorDark: string | null;
478 logoCid: string | null;
479 }> {
480 return xrpc("_server.getConfig");
481 },
482
483 updateServerConfig(
484 token: string,
485 config: {
486 serverName?: string;
487 primaryColor?: string;
488 primaryColorDark?: string;
489 secondaryColor?: string;
490 secondaryColorDark?: string;
491 logoCid?: string;
492 },
493 ): Promise<{ success: boolean }> {
494 return xrpc("_admin.updateServerConfig", {
495 method: "POST",
496 token,
497 body: config,
498 });
499 },
500
501 async uploadBlob(
502 token: string,
503 file: File,
504 ): Promise<
505 {
506 blob: {
507 $type: string;
508 ref: { $link: string };
509 mimeType: string;
510 size: number;
511 };
512 }
513 > {
514 const res = await fetch("/xrpc/com.atproto.repo.uploadBlob", {
515 method: "POST",
516 headers: {
517 "Authorization": `Bearer ${token}`,
518 "Content-Type": file.type,
519 },
520 body: file,
521 });
522 if (!res.ok) {
523 const err = await res.json().catch(() => ({
524 error: "Unknown",
525 message: res.statusText,
526 }));
527 throw new ApiError(res.status, err.error, err.message);
528 }
529 return res.json();
530 },
531
532 async changePassword(
533 token: string,
534 currentPassword: string,
535 newPassword: string,
536 ): Promise<void> {
537 await xrpc("_account.changePassword", {
538 method: "POST",
539 token,
540 body: { currentPassword, newPassword },
541 });
542 },
543
544 removePassword(token: string): Promise<{ success: boolean }> {
545 return xrpc("_account.removePassword", {
546 method: "POST",
547 token,
548 });
549 },
550
551 getPasswordStatus(token: string): Promise<{ hasPassword: boolean }> {
552 return xrpc("_account.getPasswordStatus", { token });
553 },
554
555 getLegacyLoginPreference(
556 token: string,
557 ): Promise<{ allowLegacyLogin: boolean; hasMfa: boolean }> {
558 return xrpc("_account.getLegacyLoginPreference", { token });
559 },
560
561 updateLegacyLoginPreference(
562 token: string,
563 allowLegacyLogin: boolean,
564 ): Promise<{ allowLegacyLogin: boolean }> {
565 return xrpc("_account.updateLegacyLoginPreference", {
566 method: "POST",
567 token,
568 body: { allowLegacyLogin },
569 });
570 },
571
572 updateLocale(
573 token: string,
574 preferredLocale: string,
575 ): Promise<{ preferredLocale: string }> {
576 return xrpc("_account.updateLocale", {
577 method: "POST",
578 token,
579 body: { preferredLocale },
580 });
581 },
582
583 listSessions(token: string): Promise<{
584 sessions: Array<{
585 id: string;
586 sessionType: string;
587 clientName: string | null;
588 createdAt: string;
589 expiresAt: string;
590 isCurrent: boolean;
591 }>;
592 }> {
593 return xrpc("_account.listSessions", { token });
594 },
595
596 async revokeSession(token: string, sessionId: string): Promise<void> {
597 await xrpc("_account.revokeSession", {
598 method: "POST",
599 token,
600 body: { sessionId },
601 });
602 },
603
604 revokeAllSessions(token: string): Promise<{ revokedCount: number }> {
605 return xrpc("_account.revokeAllSessions", {
606 method: "POST",
607 token,
608 });
609 },
610
611 searchAccounts(token: string, options?: {
612 handle?: string;
613 cursor?: string;
614 limit?: number;
615 }): Promise<{
616 cursor?: string;
617 accounts: Array<{
618 did: string;
619 handle: string;
620 email?: string;
621 indexedAt: string;
622 emailConfirmedAt?: string;
623 deactivatedAt?: string;
624 }>;
625 }> {
626 const params: Record<string, string> = {};
627 if (options?.handle) params.handle = options.handle;
628 if (options?.cursor) params.cursor = options.cursor;
629 if (options?.limit) params.limit = String(options.limit);
630 return xrpc("com.atproto.admin.searchAccounts", { token, params });
631 },
632
633 getInviteCodes(token: string, options?: {
634 sort?: "recent" | "usage";
635 cursor?: string;
636 limit?: number;
637 }): Promise<{
638 cursor?: string;
639 codes: Array<{
640 code: string;
641 available: number;
642 disabled: boolean;
643 forAccount: string;
644 createdBy: string;
645 createdAt: string;
646 uses: Array<{ usedBy: string; usedAt: string }>;
647 }>;
648 }> {
649 const params: Record<string, string> = {};
650 if (options?.sort) params.sort = options.sort;
651 if (options?.cursor) params.cursor = options.cursor;
652 if (options?.limit) params.limit = String(options.limit);
653 return xrpc("com.atproto.admin.getInviteCodes", { token, params });
654 },
655
656 async disableInviteCodes(
657 token: string,
658 codes?: string[],
659 accounts?: string[],
660 ): Promise<void> {
661 await xrpc("com.atproto.admin.disableInviteCodes", {
662 method: "POST",
663 token,
664 body: { codes, accounts },
665 });
666 },
667
668 getAccountInfo(token: string, did: string): Promise<{
669 did: string;
670 handle: string;
671 email?: string;
672 indexedAt: string;
673 emailConfirmedAt?: string;
674 invitesDisabled?: boolean;
675 deactivatedAt?: string;
676 }> {
677 return xrpc("com.atproto.admin.getAccountInfo", { token, params: { did } });
678 },
679
680 async disableAccountInvites(token: string, account: string): Promise<void> {
681 await xrpc("com.atproto.admin.disableAccountInvites", {
682 method: "POST",
683 token,
684 body: { account },
685 });
686 },
687
688 async enableAccountInvites(token: string, account: string): Promise<void> {
689 await xrpc("com.atproto.admin.enableAccountInvites", {
690 method: "POST",
691 token,
692 body: { account },
693 });
694 },
695
696 async adminDeleteAccount(token: string, did: string): Promise<void> {
697 await xrpc("com.atproto.admin.deleteAccount", {
698 method: "POST",
699 token,
700 body: { did },
701 });
702 },
703
704 describeRepo(token: string, repo: string): Promise<{
705 handle: string;
706 did: string;
707 didDoc: unknown;
708 collections: string[];
709 handleIsCorrect: boolean;
710 }> {
711 return xrpc("com.atproto.repo.describeRepo", {
712 token,
713 params: { repo },
714 });
715 },
716
717 listRecords(token: string, repo: string, collection: string, options?: {
718 limit?: number;
719 cursor?: string;
720 reverse?: boolean;
721 }): Promise<{
722 records: Array<{ uri: string; cid: string; value: unknown }>;
723 cursor?: string;
724 }> {
725 const params: Record<string, string> = { repo, collection };
726 if (options?.limit) params.limit = String(options.limit);
727 if (options?.cursor) params.cursor = options.cursor;
728 if (options?.reverse) params.reverse = "true";
729 return xrpc("com.atproto.repo.listRecords", { token, params });
730 },
731
732 getRecord(
733 token: string,
734 repo: string,
735 collection: string,
736 rkey: string,
737 ): Promise<{
738 uri: string;
739 cid: string;
740 value: unknown;
741 }> {
742 return xrpc("com.atproto.repo.getRecord", {
743 token,
744 params: { repo, collection, rkey },
745 });
746 },
747
748 createRecord(
749 token: string,
750 repo: string,
751 collection: string,
752 record: unknown,
753 rkey?: string,
754 ): Promise<{
755 uri: string;
756 cid: string;
757 }> {
758 return xrpc("com.atproto.repo.createRecord", {
759 method: "POST",
760 token,
761 body: { repo, collection, record, rkey },
762 });
763 },
764
765 putRecord(
766 token: string,
767 repo: string,
768 collection: string,
769 rkey: string,
770 record: unknown,
771 ): Promise<{
772 uri: string;
773 cid: string;
774 }> {
775 return xrpc("com.atproto.repo.putRecord", {
776 method: "POST",
777 token,
778 body: { repo, collection, rkey, record },
779 });
780 },
781
782 async deleteRecord(
783 token: string,
784 repo: string,
785 collection: string,
786 rkey: string,
787 ): Promise<void> {
788 await xrpc("com.atproto.repo.deleteRecord", {
789 method: "POST",
790 token,
791 body: { repo, collection, rkey },
792 });
793 },
794
795 getTotpStatus(
796 token: string,
797 ): Promise<{ enabled: boolean; hasBackupCodes: boolean }> {
798 return xrpc("com.atproto.server.getTotpStatus", { token });
799 },
800
801 createTotpSecret(
802 token: string,
803 ): Promise<{ uri: string; qrBase64: string }> {
804 return xrpc("com.atproto.server.createTotpSecret", {
805 method: "POST",
806 token,
807 });
808 },
809
810 enableTotp(
811 token: string,
812 code: string,
813 ): Promise<{ success: boolean; backupCodes: string[] }> {
814 return xrpc("com.atproto.server.enableTotp", {
815 method: "POST",
816 token,
817 body: { code },
818 });
819 },
820
821 disableTotp(
822 token: string,
823 password: string,
824 code: string,
825 ): Promise<{ success: boolean }> {
826 return xrpc("com.atproto.server.disableTotp", {
827 method: "POST",
828 token,
829 body: { password, code },
830 });
831 },
832
833 regenerateBackupCodes(
834 token: string,
835 password: string,
836 code: string,
837 ): Promise<{ backupCodes: string[] }> {
838 return xrpc("com.atproto.server.regenerateBackupCodes", {
839 method: "POST",
840 token,
841 body: { password, code },
842 });
843 },
844
845 startPasskeyRegistration(
846 token: string,
847 friendlyName?: string,
848 ): Promise<{ options: unknown }> {
849 return xrpc("com.atproto.server.startPasskeyRegistration", {
850 method: "POST",
851 token,
852 body: { friendlyName },
853 });
854 },
855
856 finishPasskeyRegistration(
857 token: string,
858 credential: unknown,
859 friendlyName?: string,
860 ): Promise<{ id: string; credentialId: string }> {
861 return xrpc("com.atproto.server.finishPasskeyRegistration", {
862 method: "POST",
863 token,
864 body: { credential, friendlyName },
865 });
866 },
867
868 listPasskeys(token: string): Promise<{
869 passkeys: Array<{
870 id: string;
871 credentialId: string;
872 friendlyName: string | null;
873 createdAt: string;
874 lastUsed: string | null;
875 }>;
876 }> {
877 return xrpc("com.atproto.server.listPasskeys", { token });
878 },
879
880 async deletePasskey(token: string, id: string): Promise<void> {
881 await xrpc("com.atproto.server.deletePasskey", {
882 method: "POST",
883 token,
884 body: { id },
885 });
886 },
887
888 async updatePasskey(
889 token: string,
890 id: string,
891 friendlyName: string,
892 ): Promise<void> {
893 await xrpc("com.atproto.server.updatePasskey", {
894 method: "POST",
895 token,
896 body: { id, friendlyName },
897 });
898 },
899
900 listTrustedDevices(token: string): Promise<{
901 devices: Array<{
902 id: string;
903 userAgent: string | null;
904 friendlyName: string | null;
905 trustedAt: string | null;
906 trustedUntil: string | null;
907 lastSeenAt: string;
908 }>;
909 }> {
910 return xrpc("_account.listTrustedDevices", { token });
911 },
912
913 revokeTrustedDevice(
914 token: string,
915 deviceId: string,
916 ): Promise<{ success: boolean }> {
917 return xrpc("_account.revokeTrustedDevice", {
918 method: "POST",
919 token,
920 body: { deviceId },
921 });
922 },
923
924 updateTrustedDevice(
925 token: string,
926 deviceId: string,
927 friendlyName: string,
928 ): Promise<{ success: boolean }> {
929 return xrpc("_account.updateTrustedDevice", {
930 method: "POST",
931 token,
932 body: { deviceId, friendlyName },
933 });
934 },
935
936 getReauthStatus(token: string): Promise<{
937 requiresReauth: boolean;
938 lastReauthAt: string | null;
939 availableMethods: string[];
940 }> {
941 return xrpc("_account.getReauthStatus", { token });
942 },
943
944 reauthPassword(
945 token: string,
946 password: string,
947 ): Promise<{ success: boolean; reauthAt: string }> {
948 return xrpc("_account.reauthPassword", {
949 method: "POST",
950 token,
951 body: { password },
952 });
953 },
954
955 reauthTotp(
956 token: string,
957 code: string,
958 ): Promise<{ success: boolean; reauthAt: string }> {
959 return xrpc("_account.reauthTotp", {
960 method: "POST",
961 token,
962 body: { code },
963 });
964 },
965
966 reauthPasskeyStart(token: string): Promise<{ options: unknown }> {
967 return xrpc("_account.reauthPasskeyStart", {
968 method: "POST",
969 token,
970 });
971 },
972
973 reauthPasskeyFinish(
974 token: string,
975 credential: unknown,
976 ): Promise<{ success: boolean; reauthAt: string }> {
977 return xrpc("_account.reauthPasskeyFinish", {
978 method: "POST",
979 token,
980 body: { credential },
981 });
982 },
983
984 reserveSigningKey(did?: string): Promise<{ signingKey: string }> {
985 return xrpc("com.atproto.server.reserveSigningKey", {
986 method: "POST",
987 body: { did },
988 });
989 },
990
991 getRecommendedDidCredentials(token: string): Promise<{
992 rotationKeys?: string[];
993 alsoKnownAs?: string[];
994 verificationMethods?: { atproto?: string };
995 services?: { atproto_pds?: { type: string; endpoint: string } };
996 }> {
997 return xrpc("com.atproto.identity.getRecommendedDidCredentials", { token });
998 },
999
1000 async activateAccount(token: string): Promise<void> {
1001 await xrpc("com.atproto.server.activateAccount", {
1002 method: "POST",
1003 token,
1004 });
1005 },
1006
1007 async createPasskeyAccount(params: {
1008 handle: string;
1009 email?: string;
1010 inviteCode?: string;
1011 didType?: DidType;
1012 did?: string;
1013 signingKey?: string;
1014 verificationChannel?: VerificationChannel;
1015 discordId?: string;
1016 telegramUsername?: string;
1017 signalNumber?: string;
1018 }, byodToken?: string): Promise<{
1019 did: string;
1020 handle: string;
1021 setupToken: string;
1022 setupExpiresAt: string;
1023 }> {
1024 const url = `${API_BASE}/_account.createPasskeyAccount`;
1025 const headers: Record<string, string> = {
1026 "Content-Type": "application/json",
1027 };
1028 if (byodToken) {
1029 headers["Authorization"] = `Bearer ${byodToken}`;
1030 }
1031 const res = await fetch(url, {
1032 method: "POST",
1033 headers,
1034 body: JSON.stringify(params),
1035 });
1036 if (!res.ok) {
1037 const err = await res.json().catch(() => ({
1038 error: "Unknown",
1039 message: res.statusText,
1040 }));
1041 throw new ApiError(res.status, err.error, err.message);
1042 }
1043 return res.json();
1044 },
1045
1046 startPasskeyRegistrationForSetup(
1047 did: string,
1048 setupToken: string,
1049 friendlyName?: string,
1050 ): Promise<{ options: unknown }> {
1051 return xrpc("_account.startPasskeyRegistrationForSetup", {
1052 method: "POST",
1053 body: { did, setupToken, friendlyName },
1054 });
1055 },
1056
1057 completePasskeySetup(
1058 did: string,
1059 setupToken: string,
1060 passkeyCredential: unknown,
1061 passkeyFriendlyName?: string,
1062 ): Promise<{
1063 did: string;
1064 handle: string;
1065 appPassword: string;
1066 appPasswordName: string;
1067 }> {
1068 return xrpc("_account.completePasskeySetup", {
1069 method: "POST",
1070 body: { did, setupToken, passkeyCredential, passkeyFriendlyName },
1071 });
1072 },
1073
1074 requestPasskeyRecovery(email: string): Promise<{ success: boolean }> {
1075 return xrpc("_account.requestPasskeyRecovery", {
1076 method: "POST",
1077 body: { email },
1078 });
1079 },
1080
1081 recoverPasskeyAccount(
1082 did: string,
1083 recoveryToken: string,
1084 newPassword: string,
1085 ): Promise<{ success: boolean }> {
1086 return xrpc("_account.recoverPasskeyAccount", {
1087 method: "POST",
1088 body: { did, recoveryToken, newPassword },
1089 });
1090 },
1091
1092 verifyMigrationEmail(
1093 token: string,
1094 email: string,
1095 ): Promise<{ success: boolean; did: string }> {
1096 return xrpc("com.atproto.server.verifyMigrationEmail", {
1097 method: "POST",
1098 body: { token, email },
1099 });
1100 },
1101
1102 resendMigrationVerification(email: string): Promise<{ sent: boolean }> {
1103 return xrpc("com.atproto.server.resendMigrationVerification", {
1104 method: "POST",
1105 body: { email },
1106 });
1107 },
1108
1109 verifyToken(
1110 token: string,
1111 identifier: string,
1112 accessToken?: string,
1113 ): Promise<{
1114 success: boolean;
1115 did: string;
1116 purpose: string;
1117 channel: string;
1118 }> {
1119 return xrpc("_account.verifyToken", {
1120 method: "POST",
1121 body: { token, identifier },
1122 token: accessToken,
1123 });
1124 },
1125
1126 getDidDocument(token: string): Promise<DidDocument> {
1127 return xrpc("_account.getDidDocument", { token });
1128 },
1129
1130 updateDidDocument(
1131 token: string,
1132 params: {
1133 verificationMethods?: VerificationMethod[];
1134 alsoKnownAs?: string[];
1135 serviceEndpoint?: string;
1136 },
1137 ): Promise<{ success: boolean }> {
1138 return xrpc("_account.updateDidDocument", {
1139 method: "POST",
1140 token,
1141 body: params,
1142 });
1143 },
1144
1145 async deactivateAccount(
1146 token: string,
1147 deleteAfter?: string,
1148 ): Promise<void> {
1149 await xrpc("com.atproto.server.deactivateAccount", {
1150 method: "POST",
1151 token,
1152 body: { deleteAfter },
1153 });
1154 },
1155
1156 async getRepo(token: string, did: string): Promise<ArrayBuffer> {
1157 const url = `${API_BASE}/com.atproto.sync.getRepo?did=${
1158 encodeURIComponent(did)
1159 }`;
1160 const res = await fetch(url, {
1161 headers: { Authorization: `Bearer ${token}` },
1162 });
1163 if (!res.ok) {
1164 const err = await res.json().catch(() => ({
1165 error: "Unknown",
1166 message: res.statusText,
1167 }));
1168 throw new ApiError(res.status, err.error, err.message);
1169 }
1170 return res.arrayBuffer();
1171 },
1172
1173 listBackups(token: string): Promise<{
1174 backups: Array<{
1175 id: string;
1176 repoRev: string;
1177 repoRootCid: string;
1178 blockCount: number;
1179 sizeBytes: number;
1180 createdAt: string;
1181 }>;
1182 backupEnabled: boolean;
1183 }> {
1184 return xrpc("_backup.listBackups", { token });
1185 },
1186
1187 async getBackup(token: string, id: string): Promise<Blob> {
1188 const url = `${API_BASE}/_backup.getBackup?id=${encodeURIComponent(id)}`;
1189 const res = await fetch(url, {
1190 headers: { Authorization: `Bearer ${token}` },
1191 });
1192 if (!res.ok) {
1193 const err = await res.json().catch(() => ({
1194 error: "Unknown",
1195 message: res.statusText,
1196 }));
1197 throw new ApiError(res.status, err.error, err.message);
1198 }
1199 return res.blob();
1200 },
1201
1202 createBackup(token: string): Promise<{
1203 id: string;
1204 repoRev: string;
1205 sizeBytes: number;
1206 blockCount: number;
1207 }> {
1208 return xrpc("_backup.createBackup", {
1209 method: "POST",
1210 token,
1211 });
1212 },
1213
1214 async deleteBackup(token: string, id: string): Promise<void> {
1215 await xrpc("_backup.deleteBackup", {
1216 method: "POST",
1217 token,
1218 params: { id },
1219 });
1220 },
1221
1222 setBackupEnabled(
1223 token: string,
1224 enabled: boolean,
1225 ): Promise<{ enabled: boolean }> {
1226 return xrpc("_backup.setEnabled", {
1227 method: "POST",
1228 token,
1229 body: { enabled },
1230 });
1231 },
1232
1233 async importRepo(token: string, car: Uint8Array): Promise<void> {
1234 const url = `${API_BASE}/com.atproto.repo.importRepo`;
1235 const res = await fetch(url, {
1236 method: "POST",
1237 headers: {
1238 Authorization: `Bearer ${token}`,
1239 "Content-Type": "application/vnd.ipld.car",
1240 },
1241 body: car,
1242 });
1243 if (!res.ok) {
1244 const err = await res.json().catch(() => ({
1245 error: "Unknown",
1246 message: res.statusText,
1247 }));
1248 throw new ApiError(res.status, err.error, err.message);
1249 }
1250 },
1251};