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