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