archastro.platform.auth

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

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

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

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

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

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

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

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

Returns:

Successful response

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

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

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

Access token, refresh token, and authenticated user object.

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

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

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

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

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

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

  • Team registration: supply team_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.
  • 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:
618    def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens:
619        """
620        Exchange a one-time login token for session tokens
621        Consumes a single-use login token delivered via email and returns an access token,
622        refresh token, and the authenticated user object. One-time tokens are issued by the
623        passwordless login flow and expire after a short window; submitting an expired or
624        already-used token returns HTTP 401.
625        If `timezone` is provided and the user's current timezone is still the default
626        (`"America/Los_Angeles"`), the account timezone is updated in the same request.
627        Requests are rate-limited to 10 per IP per minute; exceeding this returns HTTP 429.
628
629        Args:
630            token: Single-use login token extracted from the magic link or email code flow.
631            timezone: IANA timezone name to apply to the account if the account timezone is still the default, e.g. `"Europe/London"`. Omit to leave the timezone unchanged.
632
633        Returns:
634            Access token, refresh token, and the authenticated user object.
635        """
636        body: dict[str, object] = {}
637        body["token"] = token
638        if timezone is not None:
639            body["timezone"] = timezone
640
641        data = self._http.request(
642            "/api/v1/auth/token",
643            method="POST",
644            body=body,
645        )
646        return AuthTokens(
647            token_expiry=data.get("expires_in"),
648            refresh_token=data.get("refresh_token"),
649            access_token=data.get("token"),
650        )

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

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

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