Our Personal Data Server from scratch! tranquil.farm
oauth atproto pds rust postgresql objectstorage fun
at main 2212 lines 57 kB view raw
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};