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