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