archastro.platform.auth
1# Copyright (c) 2026 ArchAstro Inc. Licensed under the MIT License. 2# This file is auto-generated by @archastro/sdk-generator. Do not edit. 3# Content hash: 753c8a0332f7 4 5from __future__ import annotations 6 7from dataclasses import dataclass 8 9from .runtime.http_client import HttpClient, SyncHttpClient 10 11 12@dataclass 13class AuthTokens: 14 token_expiry: int | None = None 15 refresh_token: str | None = None 16 access_token: str | None = None 17 18 19class AsyncAuthClient: 20 def __init__(self, http: HttpClient): 21 self._http = http 22 23 async def allowed_auth_methods(self) -> dict: 24 """ 25 List supported auth methods 26 Returns the complete catalogue of authentication methods the platform supports, 27 including each method's stable slug, user-facing name, and description. 28 Use this endpoint to render method labels in sign-in UIs or org settings screens 29 without hardcoding copy or maintaining your own enum list. Results reflect the 30 platform's source-of-truth catalogue and are consistent across all orgs. 31 This endpoint requires only a publishable key and is accessible without an active 32 user session, making it suitable for pre-authentication flows such as login page 33 rendering or onboarding configuration. 34 35 Returns: 36 Successful response 37 """ 38 data = await self._http.request( 39 "/api/v1/auth/allowed_auth_methods", 40 method="GET", 41 ) 42 return data 43 44 async def login(self, email: str, password: str) -> AuthTokens: 45 """ 46 Authenticate with email and password 47 Authenticates a user with an email address and password and returns a short-lived 48 access token, a refresh token, and the authenticated user object. Use the refresh 49 token with the `/auth/refresh` endpoint to obtain new access tokens without 50 re-authenticating. 51 Password login must be enabled for the app; apps that have disabled password 52 authentication return HTTP 403. Requests are rate-limited per IP (10 per minute) 53 and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429. 54 55 Args: 56 email: Email address of the user to authenticate. 57 password: Password for the account associated with the given email. 58 59 Returns: 60 Access token, refresh token, and authenticated user object. 61 """ 62 body: dict[str, object] = {} 63 body["email"] = email 64 body["password"] = password 65 66 data = await self._http.request( 67 "/api/v1/auth/login", 68 method="POST", 69 body=body, 70 ) 71 return AuthTokens( 72 token_expiry=data.get("expires_in"), 73 refresh_token=data.get("refresh_token"), 74 access_token=data.get("token"), 75 ) 76 77 async def request_login_magic_link( 78 self, email: str | None = None, redirect_uri: str | None = None 79 ) -> dict: 80 """ 81 Request a magic link for login 82 Sends a magic link to the given email address so an existing user can sign in 83 without a password. The user clicks the link in their email and is redirected to 84 `redirect_uri` with a token; pass that token to `/auth/verify_link` to obtain 85 session tokens. 86 If no account exists for the email, the endpoint still returns success to prevent 87 email enumeration no link is sent in that case. Both `email` and `redirect_uri` 88 are required. Requests are rate-limited per IP (10 per minute) and per email-IP pair 89 (3 per minute) exceeding either limit returns HTTP 429. Returns HTTP 204 on success. 90 91 Args: 92 email: Email address of the account to send the magic link to. 93 redirect_uri: URL the user is redirected to after clicking the magic link. The token is appended as a query parameter. 94 95 Returns: 96 No content 97 """ 98 body: dict[str, object] = {} 99 if email is not None: 100 body["email"] = email 101 if redirect_uri is not None: 102 body["redirect_uri"] = redirect_uri 103 104 data = await self._http.request( 105 "/api/v1/auth/login/link", 106 method="POST", 107 body=body, 108 ) 109 return data 110 111 async def refresh(self, refresh_token: str) -> AuthTokens: 112 """ 113 Refresh an access token 114 Exchanges a valid refresh token for a new access token and a new refresh token, 115 rotating the refresh token on every call. The response also includes the updated 116 user object. Store the new refresh token and discard the old one. 117 Refresh tokens are single-use submitting an already-consumed token returns HTTP 401. 118 Rate limiting is applied per (user, IP) pair when the token can be verified, and 119 falls back to IP-only when it cannot. The limit is 30 exchanges per minute per 120 bucket; exceeding it returns HTTP 429. 121 122 Args: 123 refresh_token: Refresh token previously issued by a login, registration, or token-refresh response. 124 125 Returns: 126 New access token, new refresh token, and the authenticated user object. 127 """ 128 body: dict[str, object] = {} 129 body["refresh_token"] = refresh_token 130 131 data = await self._http.request( 132 "/api/v1/auth/refresh", 133 method="POST", 134 body=body, 135 ) 136 return AuthTokens( 137 token_expiry=data.get("expires_in"), 138 refresh_token=data.get("refresh_token"), 139 access_token=data.get("token"), 140 ) 141 142 async def register( 143 self, 144 email: str, 145 alias: str | None = None, 146 full_name: str | None = None, 147 invite_code: str | None = None, 148 password: str | None = None, 149 set_org: str | None = None, 150 team_invite: str | None = None, 151 timezone: str | None = None, 152 ) -> AuthTokens: 153 """ 154 Register a new user with email and password 155 Creates a new user account and returns an access token, refresh token, and the new 156 user object. Two registration paths are supported: 157 - **Team registration**: supply `team_invite` with a valid team invite ID. The new 158 user is added to that team immediately upon registration. Returns HTTP 404 if the 159 invite is not found. 160 - **Standard registration**: supply `password`. An `invite_code` may optionally be 161 included for invite-gated apps; an invalid code returns HTTP 404. 162 Exactly one of `team_invite` or `password` must be provided; omitting both returns 163 HTTP 400. Password registration must be enabled for the app; disabled apps return 164 HTTP 403. The response status is HTTP 201 on success. 165 166 Args: 167 email: Email address for the new account. 168 alias: Display alias (handle) for the new account. 169 full_name: Full name for the new account. 170 invite_code: Invite code for invite-gated registration. Applied only in the standard registration path. 171 password: Password for the new account. Required for standard (non-team-invite) registration. 172 set_org: Create or reuse an organization from the work-email domain and stamp the new user into it. 173 team_invite: Team invite ID. When provided, the user is added to the team on registration. 174 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 175 176 Returns: 177 Access token, refresh token, and the newly created user object. 178 """ 179 body: dict[str, object] = {} 180 body["email"] = email 181 if alias is not None: 182 body["alias"] = alias 183 if full_name is not None: 184 body["full_name"] = full_name 185 if invite_code is not None: 186 body["invite_code"] = invite_code 187 if password is not None: 188 body["password"] = password 189 if set_org is not None: 190 body["set_org"] = set_org 191 if team_invite is not None: 192 body["team_invite"] = team_invite 193 if timezone is not None: 194 body["timezone"] = timezone 195 196 data = await self._http.request( 197 "/api/v1/auth/register", 198 method="POST", 199 body=body, 200 ) 201 return AuthTokens( 202 token_expiry=data.get("expires_in"), 203 refresh_token=data.get("refresh_token"), 204 access_token=data.get("token"), 205 ) 206 207 async def request_register_magic_link( 208 self, 209 alias: str | None = None, 210 email: str | None = None, 211 full_name: str | None = None, 212 redirect_uri: str | None = None, 213 set_org: str | None = None, 214 timezone: str | None = None, 215 ) -> dict: 216 """ 217 Request a magic link for registration 218 Starts a passwordless registration flow by sending a verification link to the given 219 email address. The recipient clicks the link and is redirected to `redirect_uri` with 220 a token; pass that token to `/auth/verify_link` to complete registration and obtain 221 session tokens. 222 Profile fields (`full_name`, `alias`, `timezone`) are captured now and applied when 223 the link is verified. Requests are rate-limited per IP (10 per minute) and per 224 email-IP pair (3 per minute) exceeding either limit returns HTTP 429. Returns 225 HTTP 204 on success. 226 227 Args: 228 alias: Display alias (handle) for the new account. 229 email: Email address to send the registration magic link to. 230 full_name: Full name for the new account. 231 redirect_uri: URL the user is redirected to after clicking the registration link. The token is appended as a query parameter. 232 set_org: Create or reuse an organization from the work-email domain during confirmation. 233 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 234 235 Returns: 236 No content 237 """ 238 body: dict[str, object] = {} 239 if alias is not None: 240 body["alias"] = alias 241 if email is not None: 242 body["email"] = email 243 if full_name is not None: 244 body["full_name"] = full_name 245 if redirect_uri is not None: 246 body["redirect_uri"] = redirect_uri 247 if set_org is not None: 248 body["set_org"] = set_org 249 if timezone is not None: 250 body["timezone"] = timezone 251 252 data = await self._http.request( 253 "/api/v1/auth/register/link", 254 method="POST", 255 body=body, 256 ) 257 return data 258 259 async def request_magic_link( 260 self, email: str | None = None, redirect_uri: str | None = None, set_org: str | None = None 261 ) -> dict: 262 """ 263 Request a magic link for login or registration 264 Sends a passwordless magic link to the given email address. If an account with that 265 email already exists, a login link is sent. If no account exists, a registration link 266 is sent and the recipient completes sign-up by clicking through. This unified endpoint 267 lets you implement a single email-entry UI that handles both cases transparently. 268 The `redirect_uri` is validated against the app's registered hosts; an unregistered 269 URI returns HTTP 400. Both `email` and `redirect_uri` are required. Requests are 270 rate-limited per IP (10 per minute) and per email-IP pair (3 per minute). Returns 271 HTTP 204 on success no body. 272 273 Args: 274 email: Email address to send the magic link to. 275 redirect_uri: URL the user is redirected to after clicking the magic link. Must be registered with the app. 276 set_org: For a new user, create or reuse an organization from the work-email domain during confirmation. 277 278 Returns: 279 No content 280 """ 281 body: dict[str, object] = {} 282 if email is not None: 283 body["email"] = email 284 if redirect_uri is not None: 285 body["redirect_uri"] = redirect_uri 286 if set_org is not None: 287 body["set_org"] = set_org 288 289 data = await self._http.request( 290 "/api/v1/auth/request/link", 291 method="POST", 292 body=body, 293 ) 294 return data 295 296 async def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens: 297 """ 298 Exchange a one-time login token for session tokens 299 Consumes a single-use login token delivered via email and returns an access token, 300 refresh token, and the authenticated user object. One-time tokens are issued by the 301 passwordless login flow and expire after a short window; submitting an expired or 302 already-used token returns HTTP 401. 303 If `timezone` is provided and the user's current timezone is still the default 304 (`"America/Los_Angeles"`), the account timezone is updated in the same request. 305 Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429. 306 307 Args: 308 token: Single-use login token extracted from the magic link or email code flow. 309 timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged. 310 311 Returns: 312 Access token, refresh token, and the authenticated user object. 313 """ 314 body: dict[str, object] = {} 315 body["token"] = token 316 if timezone is not None: 317 body["timezone"] = timezone 318 319 data = await self._http.request( 320 "/api/v1/auth/token", 321 method="POST", 322 body=body, 323 ) 324 return AuthTokens( 325 token_expiry=data.get("expires_in"), 326 refresh_token=data.get("refresh_token"), 327 access_token=data.get("token"), 328 ) 329 330 async def verify_magic_link(self, token: str | None = None) -> AuthTokens: 331 """ 332 Verify a magic link token 333 Consumes a single-use token from a magic link URL and returns an access token, 334 refresh token, and the authenticated user object. This endpoint completes both the 335 login flow (initiated by `/auth/request_login_link`) and the registration flow 336 (initiated by `/auth/request_register_link` or `/auth/request_link`). 337 Extract the token from the `token` query parameter of the magic link redirect URI 338 and POST it here. Expired or already-used tokens return HTTP 401 expired links 339 carry the error code `expired_token`, unknown or already-used tokens carry 340 `invalid_or_expired_token`. If the app has disabled passwordless authentication 341 the request returns HTTP 403. Rate-limited to 10 requests per IP per minute 342 exceeding this returns HTTP 429. 343 344 Args: 345 token: Single-use magic link token extracted from the redirect URI query parameter. 346 347 Returns: 348 Access token, refresh token, and the authenticated user object. 349 """ 350 body: dict[str, object] = {} 351 if token is not None: 352 body["token"] = token 353 354 data = await self._http.request( 355 "/api/v1/auth/verify/link", 356 method="POST", 357 body=body, 358 ) 359 return AuthTokens( 360 token_expiry=data.get("expires_in"), 361 refresh_token=data.get("refresh_token"), 362 access_token=data.get("token"), 363 ) 364 365 366class AuthClient: 367 def __init__(self, http: SyncHttpClient): 368 self._http = http 369 370 def allowed_auth_methods(self) -> dict: 371 """ 372 List supported auth methods 373 Returns the complete catalogue of authentication methods the platform supports, 374 including each method's stable slug, user-facing name, and description. 375 Use this endpoint to render method labels in sign-in UIs or org settings screens 376 without hardcoding copy or maintaining your own enum list. Results reflect the 377 platform's source-of-truth catalogue and are consistent across all orgs. 378 This endpoint requires only a publishable key and is accessible without an active 379 user session, making it suitable for pre-authentication flows such as login page 380 rendering or onboarding configuration. 381 382 Returns: 383 Successful response 384 """ 385 data = self._http.request( 386 "/api/v1/auth/allowed_auth_methods", 387 method="GET", 388 ) 389 return data 390 391 def login(self, email: str, password: str) -> AuthTokens: 392 """ 393 Authenticate with email and password 394 Authenticates a user with an email address and password and returns a short-lived 395 access token, a refresh token, and the authenticated user object. Use the refresh 396 token with the `/auth/refresh` endpoint to obtain new access tokens without 397 re-authenticating. 398 Password login must be enabled for the app; apps that have disabled password 399 authentication return HTTP 403. Requests are rate-limited per IP (10 per minute) 400 and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429. 401 402 Args: 403 email: Email address of the user to authenticate. 404 password: Password for the account associated with the given email. 405 406 Returns: 407 Access token, refresh token, and authenticated user object. 408 """ 409 body: dict[str, object] = {} 410 body["email"] = email 411 body["password"] = password 412 413 data = self._http.request( 414 "/api/v1/auth/login", 415 method="POST", 416 body=body, 417 ) 418 return AuthTokens( 419 token_expiry=data.get("expires_in"), 420 refresh_token=data.get("refresh_token"), 421 access_token=data.get("token"), 422 ) 423 424 def request_login_magic_link( 425 self, email: str | None = None, redirect_uri: str | None = None 426 ) -> dict: 427 """ 428 Request a magic link for login 429 Sends a magic link to the given email address so an existing user can sign in 430 without a password. The user clicks the link in their email and is redirected to 431 `redirect_uri` with a token; pass that token to `/auth/verify_link` to obtain 432 session tokens. 433 If no account exists for the email, the endpoint still returns success to prevent 434 email enumeration no link is sent in that case. Both `email` and `redirect_uri` 435 are required. Requests are rate-limited per IP (10 per minute) and per email-IP pair 436 (3 per minute) exceeding either limit returns HTTP 429. Returns HTTP 204 on success. 437 438 Args: 439 email: Email address of the account to send the magic link to. 440 redirect_uri: URL the user is redirected to after clicking the magic link. The token is appended as a query parameter. 441 442 Returns: 443 No content 444 """ 445 body: dict[str, object] = {} 446 if email is not None: 447 body["email"] = email 448 if redirect_uri is not None: 449 body["redirect_uri"] = redirect_uri 450 451 data = self._http.request( 452 "/api/v1/auth/login/link", 453 method="POST", 454 body=body, 455 ) 456 return data 457 458 def refresh(self, refresh_token: str) -> AuthTokens: 459 """ 460 Refresh an access token 461 Exchanges a valid refresh token for a new access token and a new refresh token, 462 rotating the refresh token on every call. The response also includes the updated 463 user object. Store the new refresh token and discard the old one. 464 Refresh tokens are single-use submitting an already-consumed token returns HTTP 401. 465 Rate limiting is applied per (user, IP) pair when the token can be verified, and 466 falls back to IP-only when it cannot. The limit is 30 exchanges per minute per 467 bucket; exceeding it returns HTTP 429. 468 469 Args: 470 refresh_token: Refresh token previously issued by a login, registration, or token-refresh response. 471 472 Returns: 473 New access token, new refresh token, and the authenticated user object. 474 """ 475 body: dict[str, object] = {} 476 body["refresh_token"] = refresh_token 477 478 data = self._http.request( 479 "/api/v1/auth/refresh", 480 method="POST", 481 body=body, 482 ) 483 return AuthTokens( 484 token_expiry=data.get("expires_in"), 485 refresh_token=data.get("refresh_token"), 486 access_token=data.get("token"), 487 ) 488 489 def register( 490 self, 491 email: str, 492 alias: str | None = None, 493 full_name: str | None = None, 494 invite_code: str | None = None, 495 password: str | None = None, 496 set_org: str | None = None, 497 team_invite: str | None = None, 498 timezone: str | None = None, 499 ) -> AuthTokens: 500 """ 501 Register a new user with email and password 502 Creates a new user account and returns an access token, refresh token, and the new 503 user object. Two registration paths are supported: 504 - **Team registration**: supply `team_invite` with a valid team invite ID. The new 505 user is added to that team immediately upon registration. Returns HTTP 404 if the 506 invite is not found. 507 - **Standard registration**: supply `password`. An `invite_code` may optionally be 508 included for invite-gated apps; an invalid code returns HTTP 404. 509 Exactly one of `team_invite` or `password` must be provided; omitting both returns 510 HTTP 400. Password registration must be enabled for the app; disabled apps return 511 HTTP 403. The response status is HTTP 201 on success. 512 513 Args: 514 email: Email address for the new account. 515 alias: Display alias (handle) for the new account. 516 full_name: Full name for the new account. 517 invite_code: Invite code for invite-gated registration. Applied only in the standard registration path. 518 password: Password for the new account. Required for standard (non-team-invite) registration. 519 set_org: Create or reuse an organization from the work-email domain and stamp the new user into it. 520 team_invite: Team invite ID. When provided, the user is added to the team on registration. 521 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 522 523 Returns: 524 Access token, refresh token, and the newly created user object. 525 """ 526 body: dict[str, object] = {} 527 body["email"] = email 528 if alias is not None: 529 body["alias"] = alias 530 if full_name is not None: 531 body["full_name"] = full_name 532 if invite_code is not None: 533 body["invite_code"] = invite_code 534 if password is not None: 535 body["password"] = password 536 if set_org is not None: 537 body["set_org"] = set_org 538 if team_invite is not None: 539 body["team_invite"] = team_invite 540 if timezone is not None: 541 body["timezone"] = timezone 542 543 data = self._http.request( 544 "/api/v1/auth/register", 545 method="POST", 546 body=body, 547 ) 548 return AuthTokens( 549 token_expiry=data.get("expires_in"), 550 refresh_token=data.get("refresh_token"), 551 access_token=data.get("token"), 552 ) 553 554 def request_register_magic_link( 555 self, 556 alias: str | None = None, 557 email: str | None = None, 558 full_name: str | None = None, 559 redirect_uri: str | None = None, 560 set_org: str | None = None, 561 timezone: str | None = None, 562 ) -> dict: 563 """ 564 Request a magic link for registration 565 Starts a passwordless registration flow by sending a verification link to the given 566 email address. The recipient clicks the link and is redirected to `redirect_uri` with 567 a token; pass that token to `/auth/verify_link` to complete registration and obtain 568 session tokens. 569 Profile fields (`full_name`, `alias`, `timezone`) are captured now and applied when 570 the link is verified. Requests are rate-limited per IP (10 per minute) and per 571 email-IP pair (3 per minute) exceeding either limit returns HTTP 429. Returns 572 HTTP 204 on success. 573 574 Args: 575 alias: Display alias (handle) for the new account. 576 email: Email address to send the registration magic link to. 577 full_name: Full name for the new account. 578 redirect_uri: URL the user is redirected to after clicking the registration link. The token is appended as a query parameter. 579 set_org: Create or reuse an organization from the work-email domain during confirmation. 580 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 581 582 Returns: 583 No content 584 """ 585 body: dict[str, object] = {} 586 if alias is not None: 587 body["alias"] = alias 588 if email is not None: 589 body["email"] = email 590 if full_name is not None: 591 body["full_name"] = full_name 592 if redirect_uri is not None: 593 body["redirect_uri"] = redirect_uri 594 if set_org is not None: 595 body["set_org"] = set_org 596 if timezone is not None: 597 body["timezone"] = timezone 598 599 data = self._http.request( 600 "/api/v1/auth/register/link", 601 method="POST", 602 body=body, 603 ) 604 return data 605 606 def request_magic_link( 607 self, email: str | None = None, redirect_uri: str | None = None, set_org: str | None = None 608 ) -> dict: 609 """ 610 Request a magic link for login or registration 611 Sends a passwordless magic link to the given email address. If an account with that 612 email already exists, a login link is sent. If no account exists, a registration link 613 is sent and the recipient completes sign-up by clicking through. This unified endpoint 614 lets you implement a single email-entry UI that handles both cases transparently. 615 The `redirect_uri` is validated against the app's registered hosts; an unregistered 616 URI returns HTTP 400. Both `email` and `redirect_uri` are required. Requests are 617 rate-limited per IP (10 per minute) and per email-IP pair (3 per minute). Returns 618 HTTP 204 on success no body. 619 620 Args: 621 email: Email address to send the magic link to. 622 redirect_uri: URL the user is redirected to after clicking the magic link. Must be registered with the app. 623 set_org: For a new user, create or reuse an organization from the work-email domain during confirmation. 624 625 Returns: 626 No content 627 """ 628 body: dict[str, object] = {} 629 if email is not None: 630 body["email"] = email 631 if redirect_uri is not None: 632 body["redirect_uri"] = redirect_uri 633 if set_org is not None: 634 body["set_org"] = set_org 635 636 data = self._http.request( 637 "/api/v1/auth/request/link", 638 method="POST", 639 body=body, 640 ) 641 return data 642 643 def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens: 644 """ 645 Exchange a one-time login token for session tokens 646 Consumes a single-use login token delivered via email and returns an access token, 647 refresh token, and the authenticated user object. One-time tokens are issued by the 648 passwordless login flow and expire after a short window; submitting an expired or 649 already-used token returns HTTP 401. 650 If `timezone` is provided and the user's current timezone is still the default 651 (`"America/Los_Angeles"`), the account timezone is updated in the same request. 652 Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429. 653 654 Args: 655 token: Single-use login token extracted from the magic link or email code flow. 656 timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged. 657 658 Returns: 659 Access token, refresh token, and the authenticated user object. 660 """ 661 body: dict[str, object] = {} 662 body["token"] = token 663 if timezone is not None: 664 body["timezone"] = timezone 665 666 data = self._http.request( 667 "/api/v1/auth/token", 668 method="POST", 669 body=body, 670 ) 671 return AuthTokens( 672 token_expiry=data.get("expires_in"), 673 refresh_token=data.get("refresh_token"), 674 access_token=data.get("token"), 675 ) 676 677 def verify_magic_link(self, token: str | None = None) -> AuthTokens: 678 """ 679 Verify a magic link token 680 Consumes a single-use token from a magic link URL and returns an access token, 681 refresh token, and the authenticated user object. This endpoint completes both the 682 login flow (initiated by `/auth/request_login_link`) and the registration flow 683 (initiated by `/auth/request_register_link` or `/auth/request_link`). 684 Extract the token from the `token` query parameter of the magic link redirect URI 685 and POST it here. Expired or already-used tokens return HTTP 401 expired links 686 carry the error code `expired_token`, unknown or already-used tokens carry 687 `invalid_or_expired_token`. If the app has disabled passwordless authentication 688 the request returns HTTP 403. Rate-limited to 10 requests per IP per minute 689 exceeding this returns HTTP 429. 690 691 Args: 692 token: Single-use magic link token extracted from the redirect URI query parameter. 693 694 Returns: 695 Access token, refresh token, and the authenticated user object. 696 """ 697 body: dict[str, object] = {} 698 if token is not None: 699 body["token"] = token 700 701 data = self._http.request( 702 "/api/v1/auth/verify/link", 703 method="POST", 704 body=body, 705 ) 706 return AuthTokens( 707 token_expiry=data.get("expires_in"), 708 refresh_token=data.get("refresh_token"), 709 access_token=data.get("token"), 710 )
13@dataclass 14class AuthTokens: 15 token_expiry: int | None = None 16 refresh_token: str | None = None 17 access_token: str | None = None
20class AsyncAuthClient: 21 def __init__(self, http: HttpClient): 22 self._http = http 23 24 async def allowed_auth_methods(self) -> dict: 25 """ 26 List supported auth methods 27 Returns the complete catalogue of authentication methods the platform supports, 28 including each method's stable slug, user-facing name, and description. 29 Use this endpoint to render method labels in sign-in UIs or org settings screens 30 without hardcoding copy or maintaining your own enum list. Results reflect the 31 platform's source-of-truth catalogue and are consistent across all orgs. 32 This endpoint requires only a publishable key and is accessible without an active 33 user session, making it suitable for pre-authentication flows such as login page 34 rendering or onboarding configuration. 35 36 Returns: 37 Successful response 38 """ 39 data = await self._http.request( 40 "/api/v1/auth/allowed_auth_methods", 41 method="GET", 42 ) 43 return data 44 45 async def login(self, email: str, password: str) -> AuthTokens: 46 """ 47 Authenticate with email and password 48 Authenticates a user with an email address and password and returns a short-lived 49 access token, a refresh token, and the authenticated user object. Use the refresh 50 token with the `/auth/refresh` endpoint to obtain new access tokens without 51 re-authenticating. 52 Password login must be enabled for the app; apps that have disabled password 53 authentication return HTTP 403. Requests are rate-limited per IP (10 per minute) 54 and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429. 55 56 Args: 57 email: Email address of the user to authenticate. 58 password: Password for the account associated with the given email. 59 60 Returns: 61 Access token, refresh token, and authenticated user object. 62 """ 63 body: dict[str, object] = {} 64 body["email"] = email 65 body["password"] = password 66 67 data = await self._http.request( 68 "/api/v1/auth/login", 69 method="POST", 70 body=body, 71 ) 72 return AuthTokens( 73 token_expiry=data.get("expires_in"), 74 refresh_token=data.get("refresh_token"), 75 access_token=data.get("token"), 76 ) 77 78 async def request_login_magic_link( 79 self, email: str | None = None, redirect_uri: str | None = None 80 ) -> dict: 81 """ 82 Request a magic link for login 83 Sends a magic link to the given email address so an existing user can sign in 84 without a password. The user clicks the link in their email and is redirected to 85 `redirect_uri` with a token; pass that token to `/auth/verify_link` to obtain 86 session tokens. 87 If no account exists for the email, the endpoint still returns success to prevent 88 email enumeration no link is sent in that case. Both `email` and `redirect_uri` 89 are required. Requests are rate-limited per IP (10 per minute) and per email-IP pair 90 (3 per minute) exceeding either limit returns HTTP 429. Returns HTTP 204 on success. 91 92 Args: 93 email: Email address of the account to send the magic link to. 94 redirect_uri: URL the user is redirected to after clicking the magic link. The token is appended as a query parameter. 95 96 Returns: 97 No content 98 """ 99 body: dict[str, object] = {} 100 if email is not None: 101 body["email"] = email 102 if redirect_uri is not None: 103 body["redirect_uri"] = redirect_uri 104 105 data = await self._http.request( 106 "/api/v1/auth/login/link", 107 method="POST", 108 body=body, 109 ) 110 return data 111 112 async def refresh(self, refresh_token: str) -> AuthTokens: 113 """ 114 Refresh an access token 115 Exchanges a valid refresh token for a new access token and a new refresh token, 116 rotating the refresh token on every call. The response also includes the updated 117 user object. Store the new refresh token and discard the old one. 118 Refresh tokens are single-use submitting an already-consumed token returns HTTP 401. 119 Rate limiting is applied per (user, IP) pair when the token can be verified, and 120 falls back to IP-only when it cannot. The limit is 30 exchanges per minute per 121 bucket; exceeding it returns HTTP 429. 122 123 Args: 124 refresh_token: Refresh token previously issued by a login, registration, or token-refresh response. 125 126 Returns: 127 New access token, new refresh token, and the authenticated user object. 128 """ 129 body: dict[str, object] = {} 130 body["refresh_token"] = refresh_token 131 132 data = await self._http.request( 133 "/api/v1/auth/refresh", 134 method="POST", 135 body=body, 136 ) 137 return AuthTokens( 138 token_expiry=data.get("expires_in"), 139 refresh_token=data.get("refresh_token"), 140 access_token=data.get("token"), 141 ) 142 143 async def register( 144 self, 145 email: str, 146 alias: str | None = None, 147 full_name: str | None = None, 148 invite_code: str | None = None, 149 password: str | None = None, 150 set_org: str | None = None, 151 team_invite: str | None = None, 152 timezone: str | None = None, 153 ) -> AuthTokens: 154 """ 155 Register a new user with email and password 156 Creates a new user account and returns an access token, refresh token, and the new 157 user object. Two registration paths are supported: 158 - **Team registration**: supply `team_invite` with a valid team invite ID. The new 159 user is added to that team immediately upon registration. Returns HTTP 404 if the 160 invite is not found. 161 - **Standard registration**: supply `password`. An `invite_code` may optionally be 162 included for invite-gated apps; an invalid code returns HTTP 404. 163 Exactly one of `team_invite` or `password` must be provided; omitting both returns 164 HTTP 400. Password registration must be enabled for the app; disabled apps return 165 HTTP 403. The response status is HTTP 201 on success. 166 167 Args: 168 email: Email address for the new account. 169 alias: Display alias (handle) for the new account. 170 full_name: Full name for the new account. 171 invite_code: Invite code for invite-gated registration. Applied only in the standard registration path. 172 password: Password for the new account. Required for standard (non-team-invite) registration. 173 set_org: Create or reuse an organization from the work-email domain and stamp the new user into it. 174 team_invite: Team invite ID. When provided, the user is added to the team on registration. 175 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 176 177 Returns: 178 Access token, refresh token, and the newly created user object. 179 """ 180 body: dict[str, object] = {} 181 body["email"] = email 182 if alias is not None: 183 body["alias"] = alias 184 if full_name is not None: 185 body["full_name"] = full_name 186 if invite_code is not None: 187 body["invite_code"] = invite_code 188 if password is not None: 189 body["password"] = password 190 if set_org is not None: 191 body["set_org"] = set_org 192 if team_invite is not None: 193 body["team_invite"] = team_invite 194 if timezone is not None: 195 body["timezone"] = timezone 196 197 data = await self._http.request( 198 "/api/v1/auth/register", 199 method="POST", 200 body=body, 201 ) 202 return AuthTokens( 203 token_expiry=data.get("expires_in"), 204 refresh_token=data.get("refresh_token"), 205 access_token=data.get("token"), 206 ) 207 208 async def request_register_magic_link( 209 self, 210 alias: str | None = None, 211 email: str | None = None, 212 full_name: str | None = None, 213 redirect_uri: str | None = None, 214 set_org: str | None = None, 215 timezone: str | None = None, 216 ) -> dict: 217 """ 218 Request a magic link for registration 219 Starts a passwordless registration flow by sending a verification link to the given 220 email address. The recipient clicks the link and is redirected to `redirect_uri` with 221 a token; pass that token to `/auth/verify_link` to complete registration and obtain 222 session tokens. 223 Profile fields (`full_name`, `alias`, `timezone`) are captured now and applied when 224 the link is verified. Requests are rate-limited per IP (10 per minute) and per 225 email-IP pair (3 per minute) exceeding either limit returns HTTP 429. Returns 226 HTTP 204 on success. 227 228 Args: 229 alias: Display alias (handle) for the new account. 230 email: Email address to send the registration magic link to. 231 full_name: Full name for the new account. 232 redirect_uri: URL the user is redirected to after clicking the registration link. The token is appended as a query parameter. 233 set_org: Create or reuse an organization from the work-email domain during confirmation. 234 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 235 236 Returns: 237 No content 238 """ 239 body: dict[str, object] = {} 240 if alias is not None: 241 body["alias"] = alias 242 if email is not None: 243 body["email"] = email 244 if full_name is not None: 245 body["full_name"] = full_name 246 if redirect_uri is not None: 247 body["redirect_uri"] = redirect_uri 248 if set_org is not None: 249 body["set_org"] = set_org 250 if timezone is not None: 251 body["timezone"] = timezone 252 253 data = await self._http.request( 254 "/api/v1/auth/register/link", 255 method="POST", 256 body=body, 257 ) 258 return data 259 260 async def request_magic_link( 261 self, email: str | None = None, redirect_uri: str | None = None, set_org: str | None = None 262 ) -> dict: 263 """ 264 Request a magic link for login or registration 265 Sends a passwordless magic link to the given email address. If an account with that 266 email already exists, a login link is sent. If no account exists, a registration link 267 is sent and the recipient completes sign-up by clicking through. This unified endpoint 268 lets you implement a single email-entry UI that handles both cases transparently. 269 The `redirect_uri` is validated against the app's registered hosts; an unregistered 270 URI returns HTTP 400. Both `email` and `redirect_uri` are required. Requests are 271 rate-limited per IP (10 per minute) and per email-IP pair (3 per minute). Returns 272 HTTP 204 on success no body. 273 274 Args: 275 email: Email address to send the magic link to. 276 redirect_uri: URL the user is redirected to after clicking the magic link. Must be registered with the app. 277 set_org: For a new user, create or reuse an organization from the work-email domain during confirmation. 278 279 Returns: 280 No content 281 """ 282 body: dict[str, object] = {} 283 if email is not None: 284 body["email"] = email 285 if redirect_uri is not None: 286 body["redirect_uri"] = redirect_uri 287 if set_org is not None: 288 body["set_org"] = set_org 289 290 data = await self._http.request( 291 "/api/v1/auth/request/link", 292 method="POST", 293 body=body, 294 ) 295 return data 296 297 async def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens: 298 """ 299 Exchange a one-time login token for session tokens 300 Consumes a single-use login token delivered via email and returns an access token, 301 refresh token, and the authenticated user object. One-time tokens are issued by the 302 passwordless login flow and expire after a short window; submitting an expired or 303 already-used token returns HTTP 401. 304 If `timezone` is provided and the user's current timezone is still the default 305 (`"America/Los_Angeles"`), the account timezone is updated in the same request. 306 Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429. 307 308 Args: 309 token: Single-use login token extracted from the magic link or email code flow. 310 timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged. 311 312 Returns: 313 Access token, refresh token, and the authenticated user object. 314 """ 315 body: dict[str, object] = {} 316 body["token"] = token 317 if timezone is not None: 318 body["timezone"] = timezone 319 320 data = await self._http.request( 321 "/api/v1/auth/token", 322 method="POST", 323 body=body, 324 ) 325 return AuthTokens( 326 token_expiry=data.get("expires_in"), 327 refresh_token=data.get("refresh_token"), 328 access_token=data.get("token"), 329 ) 330 331 async def verify_magic_link(self, token: str | None = None) -> AuthTokens: 332 """ 333 Verify a magic link token 334 Consumes a single-use token from a magic link URL and returns an access token, 335 refresh token, and the authenticated user object. This endpoint completes both the 336 login flow (initiated by `/auth/request_login_link`) and the registration flow 337 (initiated by `/auth/request_register_link` or `/auth/request_link`). 338 Extract the token from the `token` query parameter of the magic link redirect URI 339 and POST it here. Expired or already-used tokens return HTTP 401 expired links 340 carry the error code `expired_token`, unknown or already-used tokens carry 341 `invalid_or_expired_token`. If the app has disabled passwordless authentication 342 the request returns HTTP 403. Rate-limited to 10 requests per IP per minute 343 exceeding this returns HTTP 429. 344 345 Args: 346 token: Single-use magic link token extracted from the redirect URI query parameter. 347 348 Returns: 349 Access token, refresh token, and the authenticated user object. 350 """ 351 body: dict[str, object] = {} 352 if token is not None: 353 body["token"] = token 354 355 data = await self._http.request( 356 "/api/v1/auth/verify/link", 357 method="POST", 358 body=body, 359 ) 360 return AuthTokens( 361 token_expiry=data.get("expires_in"), 362 refresh_token=data.get("refresh_token"), 363 access_token=data.get("token"), 364 )
24 async def allowed_auth_methods(self) -> dict: 25 """ 26 List supported auth methods 27 Returns the complete catalogue of authentication methods the platform supports, 28 including each method's stable slug, user-facing name, and description. 29 Use this endpoint to render method labels in sign-in UIs or org settings screens 30 without hardcoding copy or maintaining your own enum list. Results reflect the 31 platform's source-of-truth catalogue and are consistent across all orgs. 32 This endpoint requires only a publishable key and is accessible without an active 33 user session, making it suitable for pre-authentication flows such as login page 34 rendering or onboarding configuration. 35 36 Returns: 37 Successful response 38 """ 39 data = await self._http.request( 40 "/api/v1/auth/allowed_auth_methods", 41 method="GET", 42 ) 43 return data
List supported auth methods Returns the complete catalogue of authentication methods the platform supports, including each method's stable slug, user-facing name, and description. Use this endpoint to render method labels in sign-in UIs or org settings screens without hardcoding copy or maintaining your own enum list. Results reflect the platform's source-of-truth catalogue and are consistent across all orgs. This endpoint requires only a publishable key and is accessible without an active user session, making it suitable for pre-authentication flows such as login page rendering or onboarding configuration.
Returns:
Successful response
45 async def login(self, email: str, password: str) -> AuthTokens: 46 """ 47 Authenticate with email and password 48 Authenticates a user with an email address and password and returns a short-lived 49 access token, a refresh token, and the authenticated user object. Use the refresh 50 token with the `/auth/refresh` endpoint to obtain new access tokens without 51 re-authenticating. 52 Password login must be enabled for the app; apps that have disabled password 53 authentication return HTTP 403. Requests are rate-limited per IP (10 per minute) 54 and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429. 55 56 Args: 57 email: Email address of the user to authenticate. 58 password: Password for the account associated with the given email. 59 60 Returns: 61 Access token, refresh token, and authenticated user object. 62 """ 63 body: dict[str, object] = {} 64 body["email"] = email 65 body["password"] = password 66 67 data = await self._http.request( 68 "/api/v1/auth/login", 69 method="POST", 70 body=body, 71 ) 72 return AuthTokens( 73 token_expiry=data.get("expires_in"), 74 refresh_token=data.get("refresh_token"), 75 access_token=data.get("token"), 76 )
Authenticate with email and password
Authenticates a user with an email address and password and returns a short-lived
access token, a refresh token, and the authenticated user object. Use the refresh
token with the /auth/refresh endpoint to obtain new access tokens without
re-authenticating.
Password login must be enabled for the app; apps that have disabled password
authentication return HTTP 403. Requests are rate-limited per IP (10 per minute)
and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429.
Arguments:
- email: Email address of the user to authenticate.
- password: Password for the account associated with the given email.
Returns:
Access token, refresh token, and authenticated user object.
78 async def request_login_magic_link( 79 self, email: str | None = None, redirect_uri: str | None = None 80 ) -> dict: 81 """ 82 Request a magic link for login 83 Sends a magic link to the given email address so an existing user can sign in 84 without a password. The user clicks the link in their email and is redirected to 85 `redirect_uri` with a token; pass that token to `/auth/verify_link` to obtain 86 session tokens. 87 If no account exists for the email, the endpoint still returns success to prevent 88 email enumeration no link is sent in that case. Both `email` and `redirect_uri` 89 are required. Requests are rate-limited per IP (10 per minute) and per email-IP pair 90 (3 per minute) exceeding either limit returns HTTP 429. Returns HTTP 204 on success. 91 92 Args: 93 email: Email address of the account to send the magic link to. 94 redirect_uri: URL the user is redirected to after clicking the magic link. The token is appended as a query parameter. 95 96 Returns: 97 No content 98 """ 99 body: dict[str, object] = {} 100 if email is not None: 101 body["email"] = email 102 if redirect_uri is not None: 103 body["redirect_uri"] = redirect_uri 104 105 data = await self._http.request( 106 "/api/v1/auth/login/link", 107 method="POST", 108 body=body, 109 ) 110 return data
Request a magic link for login
Sends a magic link to the given email address so an existing user can sign in
without a password. The user clicks the link in their email and is redirected to
redirect_uri with a token; pass that token to /auth/verify_link to obtain
session tokens.
If no account exists for the email, the endpoint still returns success to prevent
email enumeration no link is sent in that case. Both email and redirect_uri
are required. Requests are rate-limited per IP (10 per minute) and per email-IP pair
(3 per minute) exceeding either limit returns HTTP 429. Returns HTTP 204 on success.
Arguments:
- email: Email address of the account to send the magic link to.
- redirect_uri: URL the user is redirected to after clicking the magic link. The token is appended as a query parameter.
Returns:
No content
112 async def refresh(self, refresh_token: str) -> AuthTokens: 113 """ 114 Refresh an access token 115 Exchanges a valid refresh token for a new access token and a new refresh token, 116 rotating the refresh token on every call. The response also includes the updated 117 user object. Store the new refresh token and discard the old one. 118 Refresh tokens are single-use submitting an already-consumed token returns HTTP 401. 119 Rate limiting is applied per (user, IP) pair when the token can be verified, and 120 falls back to IP-only when it cannot. The limit is 30 exchanges per minute per 121 bucket; exceeding it returns HTTP 429. 122 123 Args: 124 refresh_token: Refresh token previously issued by a login, registration, or token-refresh response. 125 126 Returns: 127 New access token, new refresh token, and the authenticated user object. 128 """ 129 body: dict[str, object] = {} 130 body["refresh_token"] = refresh_token 131 132 data = await self._http.request( 133 "/api/v1/auth/refresh", 134 method="POST", 135 body=body, 136 ) 137 return AuthTokens( 138 token_expiry=data.get("expires_in"), 139 refresh_token=data.get("refresh_token"), 140 access_token=data.get("token"), 141 )
Refresh an access token Exchanges a valid refresh token for a new access token and a new refresh token, rotating the refresh token on every call. The response also includes the updated user object. Store the new refresh token and discard the old one. Refresh tokens are single-use submitting an already-consumed token returns HTTP 401. Rate limiting is applied per (user, IP) pair when the token can be verified, and falls back to IP-only when it cannot. The limit is 30 exchanges per minute per bucket; exceeding it returns HTTP 429.
Arguments:
- refresh_token: Refresh token previously issued by a login, registration, or token-refresh response.
Returns:
New access token, new refresh token, and the authenticated user object.
143 async def register( 144 self, 145 email: str, 146 alias: str | None = None, 147 full_name: str | None = None, 148 invite_code: str | None = None, 149 password: str | None = None, 150 set_org: str | None = None, 151 team_invite: str | None = None, 152 timezone: str | None = None, 153 ) -> AuthTokens: 154 """ 155 Register a new user with email and password 156 Creates a new user account and returns an access token, refresh token, and the new 157 user object. Two registration paths are supported: 158 - **Team registration**: supply `team_invite` with a valid team invite ID. The new 159 user is added to that team immediately upon registration. Returns HTTP 404 if the 160 invite is not found. 161 - **Standard registration**: supply `password`. An `invite_code` may optionally be 162 included for invite-gated apps; an invalid code returns HTTP 404. 163 Exactly one of `team_invite` or `password` must be provided; omitting both returns 164 HTTP 400. Password registration must be enabled for the app; disabled apps return 165 HTTP 403. The response status is HTTP 201 on success. 166 167 Args: 168 email: Email address for the new account. 169 alias: Display alias (handle) for the new account. 170 full_name: Full name for the new account. 171 invite_code: Invite code for invite-gated registration. Applied only in the standard registration path. 172 password: Password for the new account. Required for standard (non-team-invite) registration. 173 set_org: Create or reuse an organization from the work-email domain and stamp the new user into it. 174 team_invite: Team invite ID. When provided, the user is added to the team on registration. 175 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 176 177 Returns: 178 Access token, refresh token, and the newly created user object. 179 """ 180 body: dict[str, object] = {} 181 body["email"] = email 182 if alias is not None: 183 body["alias"] = alias 184 if full_name is not None: 185 body["full_name"] = full_name 186 if invite_code is not None: 187 body["invite_code"] = invite_code 188 if password is not None: 189 body["password"] = password 190 if set_org is not None: 191 body["set_org"] = set_org 192 if team_invite is not None: 193 body["team_invite"] = team_invite 194 if timezone is not None: 195 body["timezone"] = timezone 196 197 data = await self._http.request( 198 "/api/v1/auth/register", 199 method="POST", 200 body=body, 201 ) 202 return AuthTokens( 203 token_expiry=data.get("expires_in"), 204 refresh_token=data.get("refresh_token"), 205 access_token=data.get("token"), 206 )
Register a new user with email and password Creates a new user account and returns an access token, refresh token, and the new user object. Two registration paths are supported:
- Team registration: supply
team_invitewith a valid team invite ID. The new user is added to that team immediately upon registration. Returns HTTP 404 if the invite is not found. - Standard registration: supply
password. Aninvite_codemay optionally be included for invite-gated apps; an invalid code returns HTTP 404. Exactly one ofteam_inviteorpasswordmust be provided; omitting both returns HTTP 400. Password registration must be enabled for the app; disabled apps return HTTP 403. The response status is HTTP 201 on success.
Arguments:
- email: Email address for the new account.
- alias: Display alias (handle) for the new account.
- full_name: Full name for the new account.
- invite_code: Invite code for invite-gated registration. Applied only in the standard registration path.
- password: Password for the new account. Required for standard (non-team-invite) registration.
- set_org: Create or reuse an organization from the work-email domain and stamp the new user into it.
- team_invite: Team invite ID. When provided, the user is added to the team on registration.
- timezone: IANA timezone name for the new account, e.g.
"America/New_York".
Returns:
Access token, refresh token, and the newly created user object.
208 async def request_register_magic_link( 209 self, 210 alias: str | None = None, 211 email: str | None = None, 212 full_name: str | None = None, 213 redirect_uri: str | None = None, 214 set_org: str | None = None, 215 timezone: str | None = None, 216 ) -> dict: 217 """ 218 Request a magic link for registration 219 Starts a passwordless registration flow by sending a verification link to the given 220 email address. The recipient clicks the link and is redirected to `redirect_uri` with 221 a token; pass that token to `/auth/verify_link` to complete registration and obtain 222 session tokens. 223 Profile fields (`full_name`, `alias`, `timezone`) are captured now and applied when 224 the link is verified. Requests are rate-limited per IP (10 per minute) and per 225 email-IP pair (3 per minute) exceeding either limit returns HTTP 429. Returns 226 HTTP 204 on success. 227 228 Args: 229 alias: Display alias (handle) for the new account. 230 email: Email address to send the registration magic link to. 231 full_name: Full name for the new account. 232 redirect_uri: URL the user is redirected to after clicking the registration link. The token is appended as a query parameter. 233 set_org: Create or reuse an organization from the work-email domain during confirmation. 234 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 235 236 Returns: 237 No content 238 """ 239 body: dict[str, object] = {} 240 if alias is not None: 241 body["alias"] = alias 242 if email is not None: 243 body["email"] = email 244 if full_name is not None: 245 body["full_name"] = full_name 246 if redirect_uri is not None: 247 body["redirect_uri"] = redirect_uri 248 if set_org is not None: 249 body["set_org"] = set_org 250 if timezone is not None: 251 body["timezone"] = timezone 252 253 data = await self._http.request( 254 "/api/v1/auth/register/link", 255 method="POST", 256 body=body, 257 ) 258 return data
Request a magic link for registration
Starts a passwordless registration flow by sending a verification link to the given
email address. The recipient clicks the link and is redirected to redirect_uri with
a token; pass that token to /auth/verify_link to complete registration and obtain
session tokens.
Profile fields (full_name, alias, timezone) are captured now and applied when
the link is verified. Requests are rate-limited per IP (10 per minute) and per
email-IP pair (3 per minute) exceeding either limit returns HTTP 429. Returns
HTTP 204 on success.
Arguments:
- alias: Display alias (handle) for the new account.
- email: Email address to send the registration magic link to.
- full_name: Full name for the new account.
- redirect_uri: URL the user is redirected to after clicking the registration link. The token is appended as a query parameter.
- set_org: Create or reuse an organization from the work-email domain during confirmation.
- timezone: IANA timezone name for the new account, e.g.
"America/New_York".
Returns:
No content
260 async def request_magic_link( 261 self, email: str | None = None, redirect_uri: str | None = None, set_org: str | None = None 262 ) -> dict: 263 """ 264 Request a magic link for login or registration 265 Sends a passwordless magic link to the given email address. If an account with that 266 email already exists, a login link is sent. If no account exists, a registration link 267 is sent and the recipient completes sign-up by clicking through. This unified endpoint 268 lets you implement a single email-entry UI that handles both cases transparently. 269 The `redirect_uri` is validated against the app's registered hosts; an unregistered 270 URI returns HTTP 400. Both `email` and `redirect_uri` are required. Requests are 271 rate-limited per IP (10 per minute) and per email-IP pair (3 per minute). Returns 272 HTTP 204 on success no body. 273 274 Args: 275 email: Email address to send the magic link to. 276 redirect_uri: URL the user is redirected to after clicking the magic link. Must be registered with the app. 277 set_org: For a new user, create or reuse an organization from the work-email domain during confirmation. 278 279 Returns: 280 No content 281 """ 282 body: dict[str, object] = {} 283 if email is not None: 284 body["email"] = email 285 if redirect_uri is not None: 286 body["redirect_uri"] = redirect_uri 287 if set_org is not None: 288 body["set_org"] = set_org 289 290 data = await self._http.request( 291 "/api/v1/auth/request/link", 292 method="POST", 293 body=body, 294 ) 295 return data
Request a magic link for login or registration
Sends a passwordless magic link to the given email address. If an account with that
email already exists, a login link is sent. If no account exists, a registration link
is sent and the recipient completes sign-up by clicking through. This unified endpoint
lets you implement a single email-entry UI that handles both cases transparently.
The redirect_uri is validated against the app's registered hosts; an unregistered
URI returns HTTP 400. Both email and redirect_uri are required. Requests are
rate-limited per IP (10 per minute) and per email-IP pair (3 per minute). Returns
HTTP 204 on success no body.
Arguments:
- email: Email address to send the magic link to.
- redirect_uri: URL the user is redirected to after clicking the magic link. Must be registered with the app.
- set_org: For a new user, create or reuse an organization from the work-email domain during confirmation.
Returns:
No content
297 async def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens: 298 """ 299 Exchange a one-time login token for session tokens 300 Consumes a single-use login token delivered via email and returns an access token, 301 refresh token, and the authenticated user object. One-time tokens are issued by the 302 passwordless login flow and expire after a short window; submitting an expired or 303 already-used token returns HTTP 401. 304 If `timezone` is provided and the user's current timezone is still the default 305 (`"America/Los_Angeles"`), the account timezone is updated in the same request. 306 Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429. 307 308 Args: 309 token: Single-use login token extracted from the magic link or email code flow. 310 timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged. 311 312 Returns: 313 Access token, refresh token, and the authenticated user object. 314 """ 315 body: dict[str, object] = {} 316 body["token"] = token 317 if timezone is not None: 318 body["timezone"] = timezone 319 320 data = await self._http.request( 321 "/api/v1/auth/token", 322 method="POST", 323 body=body, 324 ) 325 return AuthTokens( 326 token_expiry=data.get("expires_in"), 327 refresh_token=data.get("refresh_token"), 328 access_token=data.get("token"), 329 )
Exchange a one-time login token for session tokens
Consumes a single-use login token delivered via email and returns an access token,
refresh token, and the authenticated user object. One-time tokens are issued by the
passwordless login flow and expire after a short window; submitting an expired or
already-used token returns HTTP 401.
If timezone is provided and the user's current timezone is still the default
("America/Los_Angeles"), the account timezone is updated in the same request.
Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429.
Arguments:
- token: Single-use login token extracted from the magic link or email code flow.
- timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g.
"Europe/London". Omit to leave the timezone unchanged.
Returns:
Access token, refresh token, and the authenticated user object.
331 async def verify_magic_link(self, token: str | None = None) -> AuthTokens: 332 """ 333 Verify a magic link token 334 Consumes a single-use token from a magic link URL and returns an access token, 335 refresh token, and the authenticated user object. This endpoint completes both the 336 login flow (initiated by `/auth/request_login_link`) and the registration flow 337 (initiated by `/auth/request_register_link` or `/auth/request_link`). 338 Extract the token from the `token` query parameter of the magic link redirect URI 339 and POST it here. Expired or already-used tokens return HTTP 401 expired links 340 carry the error code `expired_token`, unknown or already-used tokens carry 341 `invalid_or_expired_token`. If the app has disabled passwordless authentication 342 the request returns HTTP 403. Rate-limited to 10 requests per IP per minute 343 exceeding this returns HTTP 429. 344 345 Args: 346 token: Single-use magic link token extracted from the redirect URI query parameter. 347 348 Returns: 349 Access token, refresh token, and the authenticated user object. 350 """ 351 body: dict[str, object] = {} 352 if token is not None: 353 body["token"] = token 354 355 data = await self._http.request( 356 "/api/v1/auth/verify/link", 357 method="POST", 358 body=body, 359 ) 360 return AuthTokens( 361 token_expiry=data.get("expires_in"), 362 refresh_token=data.get("refresh_token"), 363 access_token=data.get("token"), 364 )
Verify a magic link token
Consumes a single-use token from a magic link URL and returns an access token,
refresh token, and the authenticated user object. This endpoint completes both the
login flow (initiated by /auth/request_login_link) and the registration flow
(initiated by /auth/request_register_link or /auth/request_link).
Extract the token from the token query parameter of the magic link redirect URI
and POST it here. Expired or already-used tokens return HTTP 401 expired links
carry the error code expired_token, unknown or already-used tokens carry
invalid_or_expired_token. If the app has disabled passwordless authentication
the request returns HTTP 403. Rate-limited to 10 requests per IP per minute
exceeding this returns HTTP 429.
Arguments:
- token: Single-use magic link token extracted from the redirect URI query parameter.
Returns:
Access token, refresh token, and the authenticated user object.
367class AuthClient: 368 def __init__(self, http: SyncHttpClient): 369 self._http = http 370 371 def allowed_auth_methods(self) -> dict: 372 """ 373 List supported auth methods 374 Returns the complete catalogue of authentication methods the platform supports, 375 including each method's stable slug, user-facing name, and description. 376 Use this endpoint to render method labels in sign-in UIs or org settings screens 377 without hardcoding copy or maintaining your own enum list. Results reflect the 378 platform's source-of-truth catalogue and are consistent across all orgs. 379 This endpoint requires only a publishable key and is accessible without an active 380 user session, making it suitable for pre-authentication flows such as login page 381 rendering or onboarding configuration. 382 383 Returns: 384 Successful response 385 """ 386 data = self._http.request( 387 "/api/v1/auth/allowed_auth_methods", 388 method="GET", 389 ) 390 return data 391 392 def login(self, email: str, password: str) -> AuthTokens: 393 """ 394 Authenticate with email and password 395 Authenticates a user with an email address and password and returns a short-lived 396 access token, a refresh token, and the authenticated user object. Use the refresh 397 token with the `/auth/refresh` endpoint to obtain new access tokens without 398 re-authenticating. 399 Password login must be enabled for the app; apps that have disabled password 400 authentication return HTTP 403. Requests are rate-limited per IP (10 per minute) 401 and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429. 402 403 Args: 404 email: Email address of the user to authenticate. 405 password: Password for the account associated with the given email. 406 407 Returns: 408 Access token, refresh token, and authenticated user object. 409 """ 410 body: dict[str, object] = {} 411 body["email"] = email 412 body["password"] = password 413 414 data = self._http.request( 415 "/api/v1/auth/login", 416 method="POST", 417 body=body, 418 ) 419 return AuthTokens( 420 token_expiry=data.get("expires_in"), 421 refresh_token=data.get("refresh_token"), 422 access_token=data.get("token"), 423 ) 424 425 def request_login_magic_link( 426 self, email: str | None = None, redirect_uri: str | None = None 427 ) -> dict: 428 """ 429 Request a magic link for login 430 Sends a magic link to the given email address so an existing user can sign in 431 without a password. The user clicks the link in their email and is redirected to 432 `redirect_uri` with a token; pass that token to `/auth/verify_link` to obtain 433 session tokens. 434 If no account exists for the email, the endpoint still returns success to prevent 435 email enumeration no link is sent in that case. Both `email` and `redirect_uri` 436 are required. Requests are rate-limited per IP (10 per minute) and per email-IP pair 437 (3 per minute) exceeding either limit returns HTTP 429. Returns HTTP 204 on success. 438 439 Args: 440 email: Email address of the account to send the magic link to. 441 redirect_uri: URL the user is redirected to after clicking the magic link. The token is appended as a query parameter. 442 443 Returns: 444 No content 445 """ 446 body: dict[str, object] = {} 447 if email is not None: 448 body["email"] = email 449 if redirect_uri is not None: 450 body["redirect_uri"] = redirect_uri 451 452 data = self._http.request( 453 "/api/v1/auth/login/link", 454 method="POST", 455 body=body, 456 ) 457 return data 458 459 def refresh(self, refresh_token: str) -> AuthTokens: 460 """ 461 Refresh an access token 462 Exchanges a valid refresh token for a new access token and a new refresh token, 463 rotating the refresh token on every call. The response also includes the updated 464 user object. Store the new refresh token and discard the old one. 465 Refresh tokens are single-use submitting an already-consumed token returns HTTP 401. 466 Rate limiting is applied per (user, IP) pair when the token can be verified, and 467 falls back to IP-only when it cannot. The limit is 30 exchanges per minute per 468 bucket; exceeding it returns HTTP 429. 469 470 Args: 471 refresh_token: Refresh token previously issued by a login, registration, or token-refresh response. 472 473 Returns: 474 New access token, new refresh token, and the authenticated user object. 475 """ 476 body: dict[str, object] = {} 477 body["refresh_token"] = refresh_token 478 479 data = self._http.request( 480 "/api/v1/auth/refresh", 481 method="POST", 482 body=body, 483 ) 484 return AuthTokens( 485 token_expiry=data.get("expires_in"), 486 refresh_token=data.get("refresh_token"), 487 access_token=data.get("token"), 488 ) 489 490 def register( 491 self, 492 email: str, 493 alias: str | None = None, 494 full_name: str | None = None, 495 invite_code: str | None = None, 496 password: str | None = None, 497 set_org: str | None = None, 498 team_invite: str | None = None, 499 timezone: str | None = None, 500 ) -> AuthTokens: 501 """ 502 Register a new user with email and password 503 Creates a new user account and returns an access token, refresh token, and the new 504 user object. Two registration paths are supported: 505 - **Team registration**: supply `team_invite` with a valid team invite ID. The new 506 user is added to that team immediately upon registration. Returns HTTP 404 if the 507 invite is not found. 508 - **Standard registration**: supply `password`. An `invite_code` may optionally be 509 included for invite-gated apps; an invalid code returns HTTP 404. 510 Exactly one of `team_invite` or `password` must be provided; omitting both returns 511 HTTP 400. Password registration must be enabled for the app; disabled apps return 512 HTTP 403. The response status is HTTP 201 on success. 513 514 Args: 515 email: Email address for the new account. 516 alias: Display alias (handle) for the new account. 517 full_name: Full name for the new account. 518 invite_code: Invite code for invite-gated registration. Applied only in the standard registration path. 519 password: Password for the new account. Required for standard (non-team-invite) registration. 520 set_org: Create or reuse an organization from the work-email domain and stamp the new user into it. 521 team_invite: Team invite ID. When provided, the user is added to the team on registration. 522 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 523 524 Returns: 525 Access token, refresh token, and the newly created user object. 526 """ 527 body: dict[str, object] = {} 528 body["email"] = email 529 if alias is not None: 530 body["alias"] = alias 531 if full_name is not None: 532 body["full_name"] = full_name 533 if invite_code is not None: 534 body["invite_code"] = invite_code 535 if password is not None: 536 body["password"] = password 537 if set_org is not None: 538 body["set_org"] = set_org 539 if team_invite is not None: 540 body["team_invite"] = team_invite 541 if timezone is not None: 542 body["timezone"] = timezone 543 544 data = self._http.request( 545 "/api/v1/auth/register", 546 method="POST", 547 body=body, 548 ) 549 return AuthTokens( 550 token_expiry=data.get("expires_in"), 551 refresh_token=data.get("refresh_token"), 552 access_token=data.get("token"), 553 ) 554 555 def request_register_magic_link( 556 self, 557 alias: str | None = None, 558 email: str | None = None, 559 full_name: str | None = None, 560 redirect_uri: str | None = None, 561 set_org: str | None = None, 562 timezone: str | None = None, 563 ) -> dict: 564 """ 565 Request a magic link for registration 566 Starts a passwordless registration flow by sending a verification link to the given 567 email address. The recipient clicks the link and is redirected to `redirect_uri` with 568 a token; pass that token to `/auth/verify_link` to complete registration and obtain 569 session tokens. 570 Profile fields (`full_name`, `alias`, `timezone`) are captured now and applied when 571 the link is verified. Requests are rate-limited per IP (10 per minute) and per 572 email-IP pair (3 per minute) exceeding either limit returns HTTP 429. Returns 573 HTTP 204 on success. 574 575 Args: 576 alias: Display alias (handle) for the new account. 577 email: Email address to send the registration magic link to. 578 full_name: Full name for the new account. 579 redirect_uri: URL the user is redirected to after clicking the registration link. The token is appended as a query parameter. 580 set_org: Create or reuse an organization from the work-email domain during confirmation. 581 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 582 583 Returns: 584 No content 585 """ 586 body: dict[str, object] = {} 587 if alias is not None: 588 body["alias"] = alias 589 if email is not None: 590 body["email"] = email 591 if full_name is not None: 592 body["full_name"] = full_name 593 if redirect_uri is not None: 594 body["redirect_uri"] = redirect_uri 595 if set_org is not None: 596 body["set_org"] = set_org 597 if timezone is not None: 598 body["timezone"] = timezone 599 600 data = self._http.request( 601 "/api/v1/auth/register/link", 602 method="POST", 603 body=body, 604 ) 605 return data 606 607 def request_magic_link( 608 self, email: str | None = None, redirect_uri: str | None = None, set_org: str | None = None 609 ) -> dict: 610 """ 611 Request a magic link for login or registration 612 Sends a passwordless magic link to the given email address. If an account with that 613 email already exists, a login link is sent. If no account exists, a registration link 614 is sent and the recipient completes sign-up by clicking through. This unified endpoint 615 lets you implement a single email-entry UI that handles both cases transparently. 616 The `redirect_uri` is validated against the app's registered hosts; an unregistered 617 URI returns HTTP 400. Both `email` and `redirect_uri` are required. Requests are 618 rate-limited per IP (10 per minute) and per email-IP pair (3 per minute). Returns 619 HTTP 204 on success no body. 620 621 Args: 622 email: Email address to send the magic link to. 623 redirect_uri: URL the user is redirected to after clicking the magic link. Must be registered with the app. 624 set_org: For a new user, create or reuse an organization from the work-email domain during confirmation. 625 626 Returns: 627 No content 628 """ 629 body: dict[str, object] = {} 630 if email is not None: 631 body["email"] = email 632 if redirect_uri is not None: 633 body["redirect_uri"] = redirect_uri 634 if set_org is not None: 635 body["set_org"] = set_org 636 637 data = self._http.request( 638 "/api/v1/auth/request/link", 639 method="POST", 640 body=body, 641 ) 642 return data 643 644 def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens: 645 """ 646 Exchange a one-time login token for session tokens 647 Consumes a single-use login token delivered via email and returns an access token, 648 refresh token, and the authenticated user object. One-time tokens are issued by the 649 passwordless login flow and expire after a short window; submitting an expired or 650 already-used token returns HTTP 401. 651 If `timezone` is provided and the user's current timezone is still the default 652 (`"America/Los_Angeles"`), the account timezone is updated in the same request. 653 Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429. 654 655 Args: 656 token: Single-use login token extracted from the magic link or email code flow. 657 timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged. 658 659 Returns: 660 Access token, refresh token, and the authenticated user object. 661 """ 662 body: dict[str, object] = {} 663 body["token"] = token 664 if timezone is not None: 665 body["timezone"] = timezone 666 667 data = self._http.request( 668 "/api/v1/auth/token", 669 method="POST", 670 body=body, 671 ) 672 return AuthTokens( 673 token_expiry=data.get("expires_in"), 674 refresh_token=data.get("refresh_token"), 675 access_token=data.get("token"), 676 ) 677 678 def verify_magic_link(self, token: str | None = None) -> AuthTokens: 679 """ 680 Verify a magic link token 681 Consumes a single-use token from a magic link URL and returns an access token, 682 refresh token, and the authenticated user object. This endpoint completes both the 683 login flow (initiated by `/auth/request_login_link`) and the registration flow 684 (initiated by `/auth/request_register_link` or `/auth/request_link`). 685 Extract the token from the `token` query parameter of the magic link redirect URI 686 and POST it here. Expired or already-used tokens return HTTP 401 expired links 687 carry the error code `expired_token`, unknown or already-used tokens carry 688 `invalid_or_expired_token`. If the app has disabled passwordless authentication 689 the request returns HTTP 403. Rate-limited to 10 requests per IP per minute 690 exceeding this returns HTTP 429. 691 692 Args: 693 token: Single-use magic link token extracted from the redirect URI query parameter. 694 695 Returns: 696 Access token, refresh token, and the authenticated user object. 697 """ 698 body: dict[str, object] = {} 699 if token is not None: 700 body["token"] = token 701 702 data = self._http.request( 703 "/api/v1/auth/verify/link", 704 method="POST", 705 body=body, 706 ) 707 return AuthTokens( 708 token_expiry=data.get("expires_in"), 709 refresh_token=data.get("refresh_token"), 710 access_token=data.get("token"), 711 )
371 def allowed_auth_methods(self) -> dict: 372 """ 373 List supported auth methods 374 Returns the complete catalogue of authentication methods the platform supports, 375 including each method's stable slug, user-facing name, and description. 376 Use this endpoint to render method labels in sign-in UIs or org settings screens 377 without hardcoding copy or maintaining your own enum list. Results reflect the 378 platform's source-of-truth catalogue and are consistent across all orgs. 379 This endpoint requires only a publishable key and is accessible without an active 380 user session, making it suitable for pre-authentication flows such as login page 381 rendering or onboarding configuration. 382 383 Returns: 384 Successful response 385 """ 386 data = self._http.request( 387 "/api/v1/auth/allowed_auth_methods", 388 method="GET", 389 ) 390 return data
List supported auth methods Returns the complete catalogue of authentication methods the platform supports, including each method's stable slug, user-facing name, and description. Use this endpoint to render method labels in sign-in UIs or org settings screens without hardcoding copy or maintaining your own enum list. Results reflect the platform's source-of-truth catalogue and are consistent across all orgs. This endpoint requires only a publishable key and is accessible without an active user session, making it suitable for pre-authentication flows such as login page rendering or onboarding configuration.
Returns:
Successful response
392 def login(self, email: str, password: str) -> AuthTokens: 393 """ 394 Authenticate with email and password 395 Authenticates a user with an email address and password and returns a short-lived 396 access token, a refresh token, and the authenticated user object. Use the refresh 397 token with the `/auth/refresh` endpoint to obtain new access tokens without 398 re-authenticating. 399 Password login must be enabled for the app; apps that have disabled password 400 authentication return HTTP 403. Requests are rate-limited per IP (10 per minute) 401 and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429. 402 403 Args: 404 email: Email address of the user to authenticate. 405 password: Password for the account associated with the given email. 406 407 Returns: 408 Access token, refresh token, and authenticated user object. 409 """ 410 body: dict[str, object] = {} 411 body["email"] = email 412 body["password"] = password 413 414 data = self._http.request( 415 "/api/v1/auth/login", 416 method="POST", 417 body=body, 418 ) 419 return AuthTokens( 420 token_expiry=data.get("expires_in"), 421 refresh_token=data.get("refresh_token"), 422 access_token=data.get("token"), 423 )
Authenticate with email and password
Authenticates a user with an email address and password and returns a short-lived
access token, a refresh token, and the authenticated user object. Use the refresh
token with the /auth/refresh endpoint to obtain new access tokens without
re-authenticating.
Password login must be enabled for the app; apps that have disabled password
authentication return HTTP 403. Requests are rate-limited per IP (10 per minute)
and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429.
Arguments:
- email: Email address of the user to authenticate.
- password: Password for the account associated with the given email.
Returns:
Access token, refresh token, and authenticated user object.
425 def request_login_magic_link( 426 self, email: str | None = None, redirect_uri: str | None = None 427 ) -> dict: 428 """ 429 Request a magic link for login 430 Sends a magic link to the given email address so an existing user can sign in 431 without a password. The user clicks the link in their email and is redirected to 432 `redirect_uri` with a token; pass that token to `/auth/verify_link` to obtain 433 session tokens. 434 If no account exists for the email, the endpoint still returns success to prevent 435 email enumeration no link is sent in that case. Both `email` and `redirect_uri` 436 are required. Requests are rate-limited per IP (10 per minute) and per email-IP pair 437 (3 per minute) exceeding either limit returns HTTP 429. Returns HTTP 204 on success. 438 439 Args: 440 email: Email address of the account to send the magic link to. 441 redirect_uri: URL the user is redirected to after clicking the magic link. The token is appended as a query parameter. 442 443 Returns: 444 No content 445 """ 446 body: dict[str, object] = {} 447 if email is not None: 448 body["email"] = email 449 if redirect_uri is not None: 450 body["redirect_uri"] = redirect_uri 451 452 data = self._http.request( 453 "/api/v1/auth/login/link", 454 method="POST", 455 body=body, 456 ) 457 return data
Request a magic link for login
Sends a magic link to the given email address so an existing user can sign in
without a password. The user clicks the link in their email and is redirected to
redirect_uri with a token; pass that token to /auth/verify_link to obtain
session tokens.
If no account exists for the email, the endpoint still returns success to prevent
email enumeration no link is sent in that case. Both email and redirect_uri
are required. Requests are rate-limited per IP (10 per minute) and per email-IP pair
(3 per minute) exceeding either limit returns HTTP 429. Returns HTTP 204 on success.
Arguments:
- email: Email address of the account to send the magic link to.
- redirect_uri: URL the user is redirected to after clicking the magic link. The token is appended as a query parameter.
Returns:
No content
459 def refresh(self, refresh_token: str) -> AuthTokens: 460 """ 461 Refresh an access token 462 Exchanges a valid refresh token for a new access token and a new refresh token, 463 rotating the refresh token on every call. The response also includes the updated 464 user object. Store the new refresh token and discard the old one. 465 Refresh tokens are single-use submitting an already-consumed token returns HTTP 401. 466 Rate limiting is applied per (user, IP) pair when the token can be verified, and 467 falls back to IP-only when it cannot. The limit is 30 exchanges per minute per 468 bucket; exceeding it returns HTTP 429. 469 470 Args: 471 refresh_token: Refresh token previously issued by a login, registration, or token-refresh response. 472 473 Returns: 474 New access token, new refresh token, and the authenticated user object. 475 """ 476 body: dict[str, object] = {} 477 body["refresh_token"] = refresh_token 478 479 data = self._http.request( 480 "/api/v1/auth/refresh", 481 method="POST", 482 body=body, 483 ) 484 return AuthTokens( 485 token_expiry=data.get("expires_in"), 486 refresh_token=data.get("refresh_token"), 487 access_token=data.get("token"), 488 )
Refresh an access token Exchanges a valid refresh token for a new access token and a new refresh token, rotating the refresh token on every call. The response also includes the updated user object. Store the new refresh token and discard the old one. Refresh tokens are single-use submitting an already-consumed token returns HTTP 401. Rate limiting is applied per (user, IP) pair when the token can be verified, and falls back to IP-only when it cannot. The limit is 30 exchanges per minute per bucket; exceeding it returns HTTP 429.
Arguments:
- refresh_token: Refresh token previously issued by a login, registration, or token-refresh response.
Returns:
New access token, new refresh token, and the authenticated user object.
490 def register( 491 self, 492 email: str, 493 alias: str | None = None, 494 full_name: str | None = None, 495 invite_code: str | None = None, 496 password: str | None = None, 497 set_org: str | None = None, 498 team_invite: str | None = None, 499 timezone: str | None = None, 500 ) -> AuthTokens: 501 """ 502 Register a new user with email and password 503 Creates a new user account and returns an access token, refresh token, and the new 504 user object. Two registration paths are supported: 505 - **Team registration**: supply `team_invite` with a valid team invite ID. The new 506 user is added to that team immediately upon registration. Returns HTTP 404 if the 507 invite is not found. 508 - **Standard registration**: supply `password`. An `invite_code` may optionally be 509 included for invite-gated apps; an invalid code returns HTTP 404. 510 Exactly one of `team_invite` or `password` must be provided; omitting both returns 511 HTTP 400. Password registration must be enabled for the app; disabled apps return 512 HTTP 403. The response status is HTTP 201 on success. 513 514 Args: 515 email: Email address for the new account. 516 alias: Display alias (handle) for the new account. 517 full_name: Full name for the new account. 518 invite_code: Invite code for invite-gated registration. Applied only in the standard registration path. 519 password: Password for the new account. Required for standard (non-team-invite) registration. 520 set_org: Create or reuse an organization from the work-email domain and stamp the new user into it. 521 team_invite: Team invite ID. When provided, the user is added to the team on registration. 522 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 523 524 Returns: 525 Access token, refresh token, and the newly created user object. 526 """ 527 body: dict[str, object] = {} 528 body["email"] = email 529 if alias is not None: 530 body["alias"] = alias 531 if full_name is not None: 532 body["full_name"] = full_name 533 if invite_code is not None: 534 body["invite_code"] = invite_code 535 if password is not None: 536 body["password"] = password 537 if set_org is not None: 538 body["set_org"] = set_org 539 if team_invite is not None: 540 body["team_invite"] = team_invite 541 if timezone is not None: 542 body["timezone"] = timezone 543 544 data = self._http.request( 545 "/api/v1/auth/register", 546 method="POST", 547 body=body, 548 ) 549 return AuthTokens( 550 token_expiry=data.get("expires_in"), 551 refresh_token=data.get("refresh_token"), 552 access_token=data.get("token"), 553 )
Register a new user with email and password Creates a new user account and returns an access token, refresh token, and the new user object. Two registration paths are supported:
- Team registration: supply
team_invitewith a valid team invite ID. The new user is added to that team immediately upon registration. Returns HTTP 404 if the invite is not found. - Standard registration: supply
password. Aninvite_codemay optionally be included for invite-gated apps; an invalid code returns HTTP 404. Exactly one ofteam_inviteorpasswordmust be provided; omitting both returns HTTP 400. Password registration must be enabled for the app; disabled apps return HTTP 403. The response status is HTTP 201 on success.
Arguments:
- email: Email address for the new account.
- alias: Display alias (handle) for the new account.
- full_name: Full name for the new account.
- invite_code: Invite code for invite-gated registration. Applied only in the standard registration path.
- password: Password for the new account. Required for standard (non-team-invite) registration.
- set_org: Create or reuse an organization from the work-email domain and stamp the new user into it.
- team_invite: Team invite ID. When provided, the user is added to the team on registration.
- timezone: IANA timezone name for the new account, e.g.
"America/New_York".
Returns:
Access token, refresh token, and the newly created user object.
555 def request_register_magic_link( 556 self, 557 alias: str | None = None, 558 email: str | None = None, 559 full_name: str | None = None, 560 redirect_uri: str | None = None, 561 set_org: str | None = None, 562 timezone: str | None = None, 563 ) -> dict: 564 """ 565 Request a magic link for registration 566 Starts a passwordless registration flow by sending a verification link to the given 567 email address. The recipient clicks the link and is redirected to `redirect_uri` with 568 a token; pass that token to `/auth/verify_link` to complete registration and obtain 569 session tokens. 570 Profile fields (`full_name`, `alias`, `timezone`) are captured now and applied when 571 the link is verified. Requests are rate-limited per IP (10 per minute) and per 572 email-IP pair (3 per minute) exceeding either limit returns HTTP 429. Returns 573 HTTP 204 on success. 574 575 Args: 576 alias: Display alias (handle) for the new account. 577 email: Email address to send the registration magic link to. 578 full_name: Full name for the new account. 579 redirect_uri: URL the user is redirected to after clicking the registration link. The token is appended as a query parameter. 580 set_org: Create or reuse an organization from the work-email domain during confirmation. 581 timezone: IANA timezone name for the new account, e.g. `"America/New_York"`. 582 583 Returns: 584 No content 585 """ 586 body: dict[str, object] = {} 587 if alias is not None: 588 body["alias"] = alias 589 if email is not None: 590 body["email"] = email 591 if full_name is not None: 592 body["full_name"] = full_name 593 if redirect_uri is not None: 594 body["redirect_uri"] = redirect_uri 595 if set_org is not None: 596 body["set_org"] = set_org 597 if timezone is not None: 598 body["timezone"] = timezone 599 600 data = self._http.request( 601 "/api/v1/auth/register/link", 602 method="POST", 603 body=body, 604 ) 605 return data
Request a magic link for registration
Starts a passwordless registration flow by sending a verification link to the given
email address. The recipient clicks the link and is redirected to redirect_uri with
a token; pass that token to /auth/verify_link to complete registration and obtain
session tokens.
Profile fields (full_name, alias, timezone) are captured now and applied when
the link is verified. Requests are rate-limited per IP (10 per minute) and per
email-IP pair (3 per minute) exceeding either limit returns HTTP 429. Returns
HTTP 204 on success.
Arguments:
- alias: Display alias (handle) for the new account.
- email: Email address to send the registration magic link to.
- full_name: Full name for the new account.
- redirect_uri: URL the user is redirected to after clicking the registration link. The token is appended as a query parameter.
- set_org: Create or reuse an organization from the work-email domain during confirmation.
- timezone: IANA timezone name for the new account, e.g.
"America/New_York".
Returns:
No content
607 def request_magic_link( 608 self, email: str | None = None, redirect_uri: str | None = None, set_org: str | None = None 609 ) -> dict: 610 """ 611 Request a magic link for login or registration 612 Sends a passwordless magic link to the given email address. If an account with that 613 email already exists, a login link is sent. If no account exists, a registration link 614 is sent and the recipient completes sign-up by clicking through. This unified endpoint 615 lets you implement a single email-entry UI that handles both cases transparently. 616 The `redirect_uri` is validated against the app's registered hosts; an unregistered 617 URI returns HTTP 400. Both `email` and `redirect_uri` are required. Requests are 618 rate-limited per IP (10 per minute) and per email-IP pair (3 per minute). Returns 619 HTTP 204 on success no body. 620 621 Args: 622 email: Email address to send the magic link to. 623 redirect_uri: URL the user is redirected to after clicking the magic link. Must be registered with the app. 624 set_org: For a new user, create or reuse an organization from the work-email domain during confirmation. 625 626 Returns: 627 No content 628 """ 629 body: dict[str, object] = {} 630 if email is not None: 631 body["email"] = email 632 if redirect_uri is not None: 633 body["redirect_uri"] = redirect_uri 634 if set_org is not None: 635 body["set_org"] = set_org 636 637 data = self._http.request( 638 "/api/v1/auth/request/link", 639 method="POST", 640 body=body, 641 ) 642 return data
Request a magic link for login or registration
Sends a passwordless magic link to the given email address. If an account with that
email already exists, a login link is sent. If no account exists, a registration link
is sent and the recipient completes sign-up by clicking through. This unified endpoint
lets you implement a single email-entry UI that handles both cases transparently.
The redirect_uri is validated against the app's registered hosts; an unregistered
URI returns HTTP 400. Both email and redirect_uri are required. Requests are
rate-limited per IP (10 per minute) and per email-IP pair (3 per minute). Returns
HTTP 204 on success no body.
Arguments:
- email: Email address to send the magic link to.
- redirect_uri: URL the user is redirected to after clicking the magic link. Must be registered with the app.
- set_org: For a new user, create or reuse an organization from the work-email domain during confirmation.
Returns:
No content
644 def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens: 645 """ 646 Exchange a one-time login token for session tokens 647 Consumes a single-use login token delivered via email and returns an access token, 648 refresh token, and the authenticated user object. One-time tokens are issued by the 649 passwordless login flow and expire after a short window; submitting an expired or 650 already-used token returns HTTP 401. 651 If `timezone` is provided and the user's current timezone is still the default 652 (`"America/Los_Angeles"`), the account timezone is updated in the same request. 653 Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429. 654 655 Args: 656 token: Single-use login token extracted from the magic link or email code flow. 657 timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged. 658 659 Returns: 660 Access token, refresh token, and the authenticated user object. 661 """ 662 body: dict[str, object] = {} 663 body["token"] = token 664 if timezone is not None: 665 body["timezone"] = timezone 666 667 data = self._http.request( 668 "/api/v1/auth/token", 669 method="POST", 670 body=body, 671 ) 672 return AuthTokens( 673 token_expiry=data.get("expires_in"), 674 refresh_token=data.get("refresh_token"), 675 access_token=data.get("token"), 676 )
Exchange a one-time login token for session tokens
Consumes a single-use login token delivered via email and returns an access token,
refresh token, and the authenticated user object. One-time tokens are issued by the
passwordless login flow and expire after a short window; submitting an expired or
already-used token returns HTTP 401.
If timezone is provided and the user's current timezone is still the default
("America/Los_Angeles"), the account timezone is updated in the same request.
Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429.
Arguments:
- token: Single-use login token extracted from the magic link or email code flow.
- timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g.
"Europe/London". Omit to leave the timezone unchanged.
Returns:
Access token, refresh token, and the authenticated user object.
678 def verify_magic_link(self, token: str | None = None) -> AuthTokens: 679 """ 680 Verify a magic link token 681 Consumes a single-use token from a magic link URL and returns an access token, 682 refresh token, and the authenticated user object. This endpoint completes both the 683 login flow (initiated by `/auth/request_login_link`) and the registration flow 684 (initiated by `/auth/request_register_link` or `/auth/request_link`). 685 Extract the token from the `token` query parameter of the magic link redirect URI 686 and POST it here. Expired or already-used tokens return HTTP 401 expired links 687 carry the error code `expired_token`, unknown or already-used tokens carry 688 `invalid_or_expired_token`. If the app has disabled passwordless authentication 689 the request returns HTTP 403. Rate-limited to 10 requests per IP per minute 690 exceeding this returns HTTP 429. 691 692 Args: 693 token: Single-use magic link token extracted from the redirect URI query parameter. 694 695 Returns: 696 Access token, refresh token, and the authenticated user object. 697 """ 698 body: dict[str, object] = {} 699 if token is not None: 700 body["token"] = token 701 702 data = self._http.request( 703 "/api/v1/auth/verify/link", 704 method="POST", 705 body=body, 706 ) 707 return AuthTokens( 708 token_expiry=data.get("expires_in"), 709 refresh_token=data.get("refresh_token"), 710 access_token=data.get("token"), 711 )
Verify a magic link token
Consumes a single-use token from a magic link URL and returns an access token,
refresh token, and the authenticated user object. This endpoint completes both the
login flow (initiated by /auth/request_login_link) and the registration flow
(initiated by /auth/request_register_link or /auth/request_link).
Extract the token from the token query parameter of the magic link redirect URI
and POST it here. Expired or already-used tokens return HTTP 401 expired links
carry the error code expired_token, unknown or already-used tokens carry
invalid_or_expired_token. If the app has disabled passwordless authentication
the request returns HTTP 403. Rate-limited to 10 requests per IP per minute
exceeding this returns HTTP 429.
Arguments:
- token: Single-use magic link token extracted from the redirect URI query parameter.
Returns:
Access token, refresh token, and the authenticated user object.