archastro.platform.auth

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

async def login(self, email: str, password: str) -> AuthTokens:
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.

async def refresh(self, refresh_token: str) -> AuthTokens:
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.

async def register( self, email: str, alias: str | None = None, full_name: str | None = None, invite_code: str | None = None, password: str | None = None, set_org: str | None = None, team_invite: str | None = None, timezone: str | None = None) -> AuthTokens:
143    async def register(
144        self,
145        email: str,
146        alias: str | None = None,
147        full_name: str | None = None,
148        invite_code: str | None = None,
149        password: str | None = None,
150        set_org: str | None = None,
151        team_invite: str | None = None,
152        timezone: str | None = None,
153    ) -> AuthTokens:
154        """
155        Register a new user with email and password
156        Creates a new user account and returns an access token, refresh token, and the new
157        user object. Two registration paths are supported:
158        - **Team registration**: supply `team_invite` with a valid team invite ID. The new
159        user is added to that team immediately upon registration. Returns HTTP 404 if the
160        invite is not found.
161        - **Standard registration**: supply `password`. An `invite_code` may optionally be
162        included for invite-gated apps; an invalid code returns HTTP 404.
163        Exactly one of `team_invite` or `password` must be provided; omitting both returns
164        HTTP 400. Password registration must be enabled for the app; disabled apps return
165        HTTP 403. The response status is HTTP 201 on success.
166
167        Args:
168            email: Email address for the new account.
169            alias: Display alias (handle) for the new account.
170            full_name: Full name for the new account.
171            invite_code: Invite code for invite-gated registration. Applied only in the standard registration path.
172            password: Password for the new account. Required for standard (non-team-invite) registration.
173            set_org: Create or reuse an organization from the work-email domain and stamp the new user into it.
174            team_invite: Team invite ID. When provided, the user is added to the team on registration.
175            timezone: IANA timezone name for the new account, e.g. `"America/New_York"`.
176
177        Returns:
178            Access token, refresh token, and the newly created user object.
179        """
180        body: dict[str, object] = {}
181        body["email"] = email
182        if alias is not None:
183            body["alias"] = alias
184        if full_name is not None:
185            body["full_name"] = full_name
186        if invite_code is not None:
187            body["invite_code"] = invite_code
188        if password is not None:
189            body["password"] = password
190        if set_org is not None:
191            body["set_org"] = set_org
192        if team_invite is not None:
193            body["team_invite"] = team_invite
194        if timezone is not None:
195            body["timezone"] = timezone
196
197        data = await self._http.request(
198            "/api/v1/auth/register",
199            method="POST",
200            body=body,
201        )
202        return AuthTokens(
203            token_expiry=data.get("expires_in"),
204            refresh_token=data.get("refresh_token"),
205            access_token=data.get("token"),
206        )

Register a new user with email and password Creates a new user account and returns an access token, refresh token, and the new user object. Two registration paths are supported:

  • Team registration: supply team_invite with 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. An invite_code may optionally be included for invite-gated apps; an invalid code returns HTTP 404. Exactly one of team_invite or password must be provided; omitting both returns HTTP 400. Password registration must be enabled for the app; disabled apps return HTTP 403. The response status is HTTP 201 on success.
Arguments:
  • email: Email address for the new account.
  • alias: Display alias (handle) for the new account.
  • full_name: Full name for the new account.
  • invite_code: Invite code for invite-gated registration. Applied only in the standard registration path.
  • password: Password for the new account. Required for standard (non-team-invite) registration.
  • set_org: Create or reuse an organization from the work-email domain and stamp the new user into it.
  • team_invite: Team invite ID. When provided, the user is added to the team on registration.
  • timezone: IANA timezone name for the new account, e.g. "America/New_York".
Returns:

Access token, refresh token, and the newly created user object.

async def exchange_login_token( self, token: str, timezone: str | None = None) -> AuthTokens:
297    async def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens:
298        """
299        Exchange a one-time login token for session tokens
300        Consumes a single-use login token delivered via email and returns an access token,
301        refresh token, and the authenticated user object. One-time tokens are issued by the
302        passwordless login flow and expire after a short window; submitting an expired or
303        already-used token returns HTTP 401.
304        If `timezone` is provided and the user's current timezone is still the default
305        (`"America/Los_Angeles"`), the account timezone is updated in the same request.
306        Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429.
307
308        Args:
309            token: Single-use login token extracted from the magic link or email code flow.
310            timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged.
311
312        Returns:
313            Access token, refresh token, and the authenticated user object.
314        """
315        body: dict[str, object] = {}
316        body["token"] = token
317        if timezone is not None:
318            body["timezone"] = timezone
319
320        data = await self._http.request(
321            "/api/v1/auth/token",
322            method="POST",
323            body=body,
324        )
325        return AuthTokens(
326            token_expiry=data.get("expires_in"),
327            refresh_token=data.get("refresh_token"),
328            access_token=data.get("token"),
329        )

Exchange a one-time login token for session tokens Consumes a single-use login token delivered via email and returns an access token, refresh token, and the authenticated user object. One-time tokens are issued by the passwordless login flow and expire after a short window; submitting an expired or already-used token returns HTTP 401. If timezone is provided and the user's current timezone is still the default ("America/Los_Angeles"), the account timezone is updated in the same request. Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429.

Arguments:
  • token: Single-use login token extracted from the magic link or email code flow.
  • timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. "Europe/London". Omit to leave the timezone unchanged.
Returns:

Access token, refresh token, and the authenticated user object.

class AuthClient:
367class AuthClient:
368    def __init__(self, http: SyncHttpClient):
369        self._http = http
370
371    def allowed_auth_methods(self) -> dict:
372        """
373        List supported auth methods
374        Returns the complete catalogue of authentication methods the platform supports,
375        including each method's stable slug, user-facing name, and description.
376        Use this endpoint to render method labels in sign-in UIs or org settings screens
377        without hardcoding copy or maintaining your own enum list. Results reflect the
378        platform's source-of-truth catalogue and are consistent across all orgs.
379        This endpoint requires only a publishable key and is accessible without an active
380        user session, making it suitable for pre-authentication flows such as login page
381        rendering or onboarding configuration.
382
383        Returns:
384            Successful response
385        """
386        data = self._http.request(
387            "/api/v1/auth/allowed_auth_methods",
388            method="GET",
389        )
390        return data
391
392    def login(self, email: str, password: str) -> AuthTokens:
393        """
394        Authenticate with email and password
395        Authenticates a user with an email address and password and returns a short-lived
396        access token, a refresh token, and the authenticated user object. Use the refresh
397        token with the `/auth/refresh` endpoint to obtain new access tokens without
398        re-authenticating.
399        Password login must be enabled for the app; apps that have disabled password
400        authentication return HTTP 403. Requests are rate-limited per IP (10 per minute)
401        and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429.
402
403        Args:
404            email: Email address of the user to authenticate.
405            password: Password for the account associated with the given email.
406
407        Returns:
408            Access token, refresh token, and authenticated user object.
409        """
410        body: dict[str, object] = {}
411        body["email"] = email
412        body["password"] = password
413
414        data = self._http.request(
415            "/api/v1/auth/login",
416            method="POST",
417            body=body,
418        )
419        return AuthTokens(
420            token_expiry=data.get("expires_in"),
421            refresh_token=data.get("refresh_token"),
422            access_token=data.get("token"),
423        )
424
425    def request_login_magic_link(
426        self, email: str | None = None, redirect_uri: str | None = None
427    ) -> dict:
428        """
429        Request a magic link for login
430        Sends a magic link to the given email address so an existing user can sign in
431        without a password. The user clicks the link in their email and is redirected to
432        `redirect_uri` with a token; pass that token to `/auth/verify_link` to obtain
433        session tokens.
434        If no account exists for the email, the endpoint still returns success to prevent
435        email enumeration no link is sent in that case. Both `email` and `redirect_uri`
436        are required. Requests are rate-limited per IP (10 per minute) and per email-IP pair
437        (3 per minute) exceeding either limit returns HTTP 429. Returns HTTP 204 on success.
438
439        Args:
440            email: Email address of the account to send the magic link to.
441            redirect_uri: URL the user is redirected to after clicking the magic link. The token is appended as a query parameter.
442
443        Returns:
444            No content
445        """
446        body: dict[str, object] = {}
447        if email is not None:
448            body["email"] = email
449        if redirect_uri is not None:
450            body["redirect_uri"] = redirect_uri
451
452        data = self._http.request(
453            "/api/v1/auth/login/link",
454            method="POST",
455            body=body,
456        )
457        return data
458
459    def refresh(self, refresh_token: str) -> AuthTokens:
460        """
461        Refresh an access token
462        Exchanges a valid refresh token for a new access token and a new refresh token,
463        rotating the refresh token on every call. The response also includes the updated
464        user object. Store the new refresh token and discard the old one.
465        Refresh tokens are single-use submitting an already-consumed token returns HTTP 401.
466        Rate limiting is applied per (user, IP) pair when the token can be verified, and
467        falls back to IP-only when it cannot. The limit is 30 exchanges per minute per
468        bucket; exceeding it returns HTTP 429.
469
470        Args:
471            refresh_token: Refresh token previously issued by a login, registration, or token-refresh response.
472
473        Returns:
474            New access token, new refresh token, and the authenticated user object.
475        """
476        body: dict[str, object] = {}
477        body["refresh_token"] = refresh_token
478
479        data = self._http.request(
480            "/api/v1/auth/refresh",
481            method="POST",
482            body=body,
483        )
484        return AuthTokens(
485            token_expiry=data.get("expires_in"),
486            refresh_token=data.get("refresh_token"),
487            access_token=data.get("token"),
488        )
489
490    def register(
491        self,
492        email: str,
493        alias: str | None = None,
494        full_name: str | None = None,
495        invite_code: str | None = None,
496        password: str | None = None,
497        set_org: str | None = None,
498        team_invite: str | None = None,
499        timezone: str | None = None,
500    ) -> AuthTokens:
501        """
502        Register a new user with email and password
503        Creates a new user account and returns an access token, refresh token, and the new
504        user object. Two registration paths are supported:
505        - **Team registration**: supply `team_invite` with a valid team invite ID. The new
506        user is added to that team immediately upon registration. Returns HTTP 404 if the
507        invite is not found.
508        - **Standard registration**: supply `password`. An `invite_code` may optionally be
509        included for invite-gated apps; an invalid code returns HTTP 404.
510        Exactly one of `team_invite` or `password` must be provided; omitting both returns
511        HTTP 400. Password registration must be enabled for the app; disabled apps return
512        HTTP 403. The response status is HTTP 201 on success.
513
514        Args:
515            email: Email address for the new account.
516            alias: Display alias (handle) for the new account.
517            full_name: Full name for the new account.
518            invite_code: Invite code for invite-gated registration. Applied only in the standard registration path.
519            password: Password for the new account. Required for standard (non-team-invite) registration.
520            set_org: Create or reuse an organization from the work-email domain and stamp the new user into it.
521            team_invite: Team invite ID. When provided, the user is added to the team on registration.
522            timezone: IANA timezone name for the new account, e.g. `"America/New_York"`.
523
524        Returns:
525            Access token, refresh token, and the newly created user object.
526        """
527        body: dict[str, object] = {}
528        body["email"] = email
529        if alias is not None:
530            body["alias"] = alias
531        if full_name is not None:
532            body["full_name"] = full_name
533        if invite_code is not None:
534            body["invite_code"] = invite_code
535        if password is not None:
536            body["password"] = password
537        if set_org is not None:
538            body["set_org"] = set_org
539        if team_invite is not None:
540            body["team_invite"] = team_invite
541        if timezone is not None:
542            body["timezone"] = timezone
543
544        data = self._http.request(
545            "/api/v1/auth/register",
546            method="POST",
547            body=body,
548        )
549        return AuthTokens(
550            token_expiry=data.get("expires_in"),
551            refresh_token=data.get("refresh_token"),
552            access_token=data.get("token"),
553        )
554
555    def request_register_magic_link(
556        self,
557        alias: str | None = None,
558        email: str | None = None,
559        full_name: str | None = None,
560        redirect_uri: str | None = None,
561        set_org: str | None = None,
562        timezone: str | None = None,
563    ) -> dict:
564        """
565        Request a magic link for registration
566        Starts a passwordless registration flow by sending a verification link to the given
567        email address. The recipient clicks the link and is redirected to `redirect_uri` with
568        a token; pass that token to `/auth/verify_link` to complete registration and obtain
569        session tokens.
570        Profile fields (`full_name`, `alias`, `timezone`) are captured now and applied when
571        the link is verified. Requests are rate-limited per IP (10 per minute) and per
572        email-IP pair (3 per minute) exceeding either limit returns HTTP 429. Returns
573        HTTP 204 on success.
574
575        Args:
576            alias: Display alias (handle) for the new account.
577            email: Email address to send the registration magic link to.
578            full_name: Full name for the new account.
579            redirect_uri: URL the user is redirected to after clicking the registration link. The token is appended as a query parameter.
580            set_org: Create or reuse an organization from the work-email domain during confirmation.
581            timezone: IANA timezone name for the new account, e.g. `"America/New_York"`.
582
583        Returns:
584            No content
585        """
586        body: dict[str, object] = {}
587        if alias is not None:
588            body["alias"] = alias
589        if email is not None:
590            body["email"] = email
591        if full_name is not None:
592            body["full_name"] = full_name
593        if redirect_uri is not None:
594            body["redirect_uri"] = redirect_uri
595        if set_org is not None:
596            body["set_org"] = set_org
597        if timezone is not None:
598            body["timezone"] = timezone
599
600        data = self._http.request(
601            "/api/v1/auth/register/link",
602            method="POST",
603            body=body,
604        )
605        return data
606
607    def request_magic_link(
608        self, email: str | None = None, redirect_uri: str | None = None, set_org: str | None = None
609    ) -> dict:
610        """
611        Request a magic link for login or registration
612        Sends a passwordless magic link to the given email address. If an account with that
613        email already exists, a login link is sent. If no account exists, a registration link
614        is sent and the recipient completes sign-up by clicking through. This unified endpoint
615        lets you implement a single email-entry UI that handles both cases transparently.
616        The `redirect_uri` is validated against the app's registered hosts; an unregistered
617        URI returns HTTP 400. Both `email` and `redirect_uri` are required. Requests are
618        rate-limited per IP (10 per minute) and per email-IP pair (3 per minute). Returns
619        HTTP 204 on success no body.
620
621        Args:
622            email: Email address to send the magic link to.
623            redirect_uri: URL the user is redirected to after clicking the magic link. Must be registered with the app.
624            set_org: For a new user, create or reuse an organization from the work-email domain during confirmation.
625
626        Returns:
627            No content
628        """
629        body: dict[str, object] = {}
630        if email is not None:
631            body["email"] = email
632        if redirect_uri is not None:
633            body["redirect_uri"] = redirect_uri
634        if set_org is not None:
635            body["set_org"] = set_org
636
637        data = self._http.request(
638            "/api/v1/auth/request/link",
639            method="POST",
640            body=body,
641        )
642        return data
643
644    def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens:
645        """
646        Exchange a one-time login token for session tokens
647        Consumes a single-use login token delivered via email and returns an access token,
648        refresh token, and the authenticated user object. One-time tokens are issued by the
649        passwordless login flow and expire after a short window; submitting an expired or
650        already-used token returns HTTP 401.
651        If `timezone` is provided and the user's current timezone is still the default
652        (`"America/Los_Angeles"`), the account timezone is updated in the same request.
653        Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429.
654
655        Args:
656            token: Single-use login token extracted from the magic link or email code flow.
657            timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged.
658
659        Returns:
660            Access token, refresh token, and the authenticated user object.
661        """
662        body: dict[str, object] = {}
663        body["token"] = token
664        if timezone is not None:
665            body["timezone"] = timezone
666
667        data = self._http.request(
668            "/api/v1/auth/token",
669            method="POST",
670            body=body,
671        )
672        return AuthTokens(
673            token_expiry=data.get("expires_in"),
674            refresh_token=data.get("refresh_token"),
675            access_token=data.get("token"),
676        )
677
678    def verify_magic_link(self, token: str | None = None) -> AuthTokens:
679        """
680        Verify a magic link token
681        Consumes a single-use token from a magic link URL and returns an access token,
682        refresh token, and the authenticated user object. This endpoint completes both the
683        login flow (initiated by `/auth/request_login_link`) and the registration flow
684        (initiated by `/auth/request_register_link` or `/auth/request_link`).
685        Extract the token from the `token` query parameter of the magic link redirect URI
686        and POST it here. Expired or already-used tokens return HTTP 401 expired links
687        carry the error code `expired_token`, unknown or already-used tokens carry
688        `invalid_or_expired_token`. If the app has disabled passwordless authentication
689        the request returns HTTP 403. Rate-limited to 10 requests per IP per minute
690        exceeding this returns HTTP 429.
691
692        Args:
693            token: Single-use magic link token extracted from the redirect URI query parameter.
694
695        Returns:
696            Access token, refresh token, and the authenticated user object.
697        """
698        body: dict[str, object] = {}
699        if token is not None:
700            body["token"] = token
701
702        data = self._http.request(
703            "/api/v1/auth/verify/link",
704            method="POST",
705            body=body,
706        )
707        return AuthTokens(
708            token_expiry=data.get("expires_in"),
709            refresh_token=data.get("refresh_token"),
710            access_token=data.get("token"),
711        )
368    def __init__(self, http: SyncHttpClient):
369        self._http = http
def allowed_auth_methods(self) -> dict:
371    def allowed_auth_methods(self) -> dict:
372        """
373        List supported auth methods
374        Returns the complete catalogue of authentication methods the platform supports,
375        including each method's stable slug, user-facing name, and description.
376        Use this endpoint to render method labels in sign-in UIs or org settings screens
377        without hardcoding copy or maintaining your own enum list. Results reflect the
378        platform's source-of-truth catalogue and are consistent across all orgs.
379        This endpoint requires only a publishable key and is accessible without an active
380        user session, making it suitable for pre-authentication flows such as login page
381        rendering or onboarding configuration.
382
383        Returns:
384            Successful response
385        """
386        data = self._http.request(
387            "/api/v1/auth/allowed_auth_methods",
388            method="GET",
389        )
390        return data

List supported auth methods Returns the complete catalogue of authentication methods the platform supports, including each method's stable slug, user-facing name, and description. Use this endpoint to render method labels in sign-in UIs or org settings screens without hardcoding copy or maintaining your own enum list. Results reflect the platform's source-of-truth catalogue and are consistent across all orgs. This endpoint requires only a publishable key and is accessible without an active user session, making it suitable for pre-authentication flows such as login page rendering or onboarding configuration.

Returns:

Successful response

def login(self, email: str, password: str) -> AuthTokens:
392    def login(self, email: str, password: str) -> AuthTokens:
393        """
394        Authenticate with email and password
395        Authenticates a user with an email address and password and returns a short-lived
396        access token, a refresh token, and the authenticated user object. Use the refresh
397        token with the `/auth/refresh` endpoint to obtain new access tokens without
398        re-authenticating.
399        Password login must be enabled for the app; apps that have disabled password
400        authentication return HTTP 403. Requests are rate-limited per IP (10 per minute)
401        and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429.
402
403        Args:
404            email: Email address of the user to authenticate.
405            password: Password for the account associated with the given email.
406
407        Returns:
408            Access token, refresh token, and authenticated user object.
409        """
410        body: dict[str, object] = {}
411        body["email"] = email
412        body["password"] = password
413
414        data = self._http.request(
415            "/api/v1/auth/login",
416            method="POST",
417            body=body,
418        )
419        return AuthTokens(
420            token_expiry=data.get("expires_in"),
421            refresh_token=data.get("refresh_token"),
422            access_token=data.get("token"),
423        )

Authenticate with email and password Authenticates a user with an email address and password and returns a short-lived access token, a refresh token, and the authenticated user object. Use the refresh token with the /auth/refresh endpoint to obtain new access tokens without re-authenticating. Password login must be enabled for the app; apps that have disabled password authentication return HTTP 403. Requests are rate-limited per IP (10 per minute) and per email-IP pair (5 per minute) exceeding either limit returns HTTP 429.

Arguments:
  • email: Email address of the user to authenticate.
  • password: Password for the account associated with the given email.
Returns:

Access token, refresh token, and authenticated user object.

def refresh(self, refresh_token: str) -> AuthTokens:
459    def refresh(self, refresh_token: str) -> AuthTokens:
460        """
461        Refresh an access token
462        Exchanges a valid refresh token for a new access token and a new refresh token,
463        rotating the refresh token on every call. The response also includes the updated
464        user object. Store the new refresh token and discard the old one.
465        Refresh tokens are single-use submitting an already-consumed token returns HTTP 401.
466        Rate limiting is applied per (user, IP) pair when the token can be verified, and
467        falls back to IP-only when it cannot. The limit is 30 exchanges per minute per
468        bucket; exceeding it returns HTTP 429.
469
470        Args:
471            refresh_token: Refresh token previously issued by a login, registration, or token-refresh response.
472
473        Returns:
474            New access token, new refresh token, and the authenticated user object.
475        """
476        body: dict[str, object] = {}
477        body["refresh_token"] = refresh_token
478
479        data = self._http.request(
480            "/api/v1/auth/refresh",
481            method="POST",
482            body=body,
483        )
484        return AuthTokens(
485            token_expiry=data.get("expires_in"),
486            refresh_token=data.get("refresh_token"),
487            access_token=data.get("token"),
488        )

Refresh an access token Exchanges a valid refresh token for a new access token and a new refresh token, rotating the refresh token on every call. The response also includes the updated user object. Store the new refresh token and discard the old one. Refresh tokens are single-use submitting an already-consumed token returns HTTP 401. Rate limiting is applied per (user, IP) pair when the token can be verified, and falls back to IP-only when it cannot. The limit is 30 exchanges per minute per bucket; exceeding it returns HTTP 429.

Arguments:
  • refresh_token: Refresh token previously issued by a login, registration, or token-refresh response.
Returns:

New access token, new refresh token, and the authenticated user object.

def register( self, email: str, alias: str | None = None, full_name: str | None = None, invite_code: str | None = None, password: str | None = None, set_org: str | None = None, team_invite: str | None = None, timezone: str | None = None) -> AuthTokens:
490    def register(
491        self,
492        email: str,
493        alias: str | None = None,
494        full_name: str | None = None,
495        invite_code: str | None = None,
496        password: str | None = None,
497        set_org: str | None = None,
498        team_invite: str | None = None,
499        timezone: str | None = None,
500    ) -> AuthTokens:
501        """
502        Register a new user with email and password
503        Creates a new user account and returns an access token, refresh token, and the new
504        user object. Two registration paths are supported:
505        - **Team registration**: supply `team_invite` with a valid team invite ID. The new
506        user is added to that team immediately upon registration. Returns HTTP 404 if the
507        invite is not found.
508        - **Standard registration**: supply `password`. An `invite_code` may optionally be
509        included for invite-gated apps; an invalid code returns HTTP 404.
510        Exactly one of `team_invite` or `password` must be provided; omitting both returns
511        HTTP 400. Password registration must be enabled for the app; disabled apps return
512        HTTP 403. The response status is HTTP 201 on success.
513
514        Args:
515            email: Email address for the new account.
516            alias: Display alias (handle) for the new account.
517            full_name: Full name for the new account.
518            invite_code: Invite code for invite-gated registration. Applied only in the standard registration path.
519            password: Password for the new account. Required for standard (non-team-invite) registration.
520            set_org: Create or reuse an organization from the work-email domain and stamp the new user into it.
521            team_invite: Team invite ID. When provided, the user is added to the team on registration.
522            timezone: IANA timezone name for the new account, e.g. `"America/New_York"`.
523
524        Returns:
525            Access token, refresh token, and the newly created user object.
526        """
527        body: dict[str, object] = {}
528        body["email"] = email
529        if alias is not None:
530            body["alias"] = alias
531        if full_name is not None:
532            body["full_name"] = full_name
533        if invite_code is not None:
534            body["invite_code"] = invite_code
535        if password is not None:
536            body["password"] = password
537        if set_org is not None:
538            body["set_org"] = set_org
539        if team_invite is not None:
540            body["team_invite"] = team_invite
541        if timezone is not None:
542            body["timezone"] = timezone
543
544        data = self._http.request(
545            "/api/v1/auth/register",
546            method="POST",
547            body=body,
548        )
549        return AuthTokens(
550            token_expiry=data.get("expires_in"),
551            refresh_token=data.get("refresh_token"),
552            access_token=data.get("token"),
553        )

Register a new user with email and password Creates a new user account and returns an access token, refresh token, and the new user object. Two registration paths are supported:

  • Team registration: supply team_invite with 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. An invite_code may optionally be included for invite-gated apps; an invalid code returns HTTP 404. Exactly one of team_invite or password must be provided; omitting both returns HTTP 400. Password registration must be enabled for the app; disabled apps return HTTP 403. The response status is HTTP 201 on success.
Arguments:
  • email: Email address for the new account.
  • alias: Display alias (handle) for the new account.
  • full_name: Full name for the new account.
  • invite_code: Invite code for invite-gated registration. Applied only in the standard registration path.
  • password: Password for the new account. Required for standard (non-team-invite) registration.
  • set_org: Create or reuse an organization from the work-email domain and stamp the new user into it.
  • team_invite: Team invite ID. When provided, the user is added to the team on registration.
  • timezone: IANA timezone name for the new account, e.g. "America/New_York".
Returns:

Access token, refresh token, and the newly created user object.

def exchange_login_token( self, token: str, timezone: str | None = None) -> AuthTokens:
644    def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens:
645        """
646        Exchange a one-time login token for session tokens
647        Consumes a single-use login token delivered via email and returns an access token,
648        refresh token, and the authenticated user object. One-time tokens are issued by the
649        passwordless login flow and expire after a short window; submitting an expired or
650        already-used token returns HTTP 401.
651        If `timezone` is provided and the user's current timezone is still the default
652        (`"America/Los_Angeles"`), the account timezone is updated in the same request.
653        Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429.
654
655        Args:
656            token: Single-use login token extracted from the magic link or email code flow.
657            timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged.
658
659        Returns:
660            Access token, refresh token, and the authenticated user object.
661        """
662        body: dict[str, object] = {}
663        body["token"] = token
664        if timezone is not None:
665            body["timezone"] = timezone
666
667        data = self._http.request(
668            "/api/v1/auth/token",
669            method="POST",
670            body=body,
671        )
672        return AuthTokens(
673            token_expiry=data.get("expires_in"),
674            refresh_token=data.get("refresh_token"),
675            access_token=data.get("token"),
676        )

Exchange a one-time login token for session tokens Consumes a single-use login token delivered via email and returns an access token, refresh token, and the authenticated user object. One-time tokens are issued by the passwordless login flow and expire after a short window; submitting an expired or already-used token returns HTTP 401. If timezone is provided and the user's current timezone is still the default ("America/Los_Angeles"), the account timezone is updated in the same request. Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429.

Arguments:
  • token: Single-use login token extracted from the magic link or email code flow.
  • timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. "Europe/London". Omit to leave the timezone unchanged.
Returns:

Access token, refresh token, and the authenticated user object.