Se connecter et créer un client
Créez un client d'API et connectez-vous avec un mot de passe, un navigateur, un appareil ou un jeton.
Fait partie de Fonctions des SDK. Chaque appel ci-dessous est montré dans chaque langage qui l’offre.
Créer un client d’API
Section intitulée « Créer un client d’API »Faites-le avant la découverte de hubs ou le provisionnement d’identités.
from thalovant import ThalovantControlPlane
api = ThalovantControlPlane()const api = new ThalovantControlPlane();api := thalovant.NewDefaultControlPlane("")use thalovant::ControlPlane;
let mut api = ControlPlane::default();Se connecter
Section intitulée « Se connecter »Connectez-vous avant les actions privées du plan de contrôle, comme la création d’une identité de client.
Les comptes où l’authentification multifacteur (AMF) est activée doivent fournir un code TOTP ou un code de récupération à usage unique. Sans l’un ou l’autre, l’API refuse la connexion avec un HTTP 401 et le code mfa_required. Chaque SDK n’envoie les champs d’AMF que lorsqu’ils sont définis.
# MFA-enabled accounts add a TOTP code or a one-time recovery code:
// MFA-enabled accounts add a TOTP code or a one-time recovery code:if err != nil { log.Fatal(err)}
// MFA-enabled accounts use LoginWithOptions with OTPCode or RecoveryCode: OTPCode: "123456",})if err != nil { log.Fatal(err)}
// MFA-enabled accounts use login_with_options with otp_code or recovery_code:use thalovant::LoginOptions;
api.login_with_options( "password", LoginOptions { otp_code: Some("123456".into()), ..Default::default() },).await?;
// MFA-enabled accounts add a TOTP code or a one-time recovery code:
// MFA-enabled accounts add a TOTP code or a one-time recovery code:try await api.login(email: "[email protected]", password: "password", recoveryCode: "abcd-efgh-ijkl")
// MFA-enabled accounts add a TOTP code or a one-time recovery code:Échec courant :
Missing Thalovant API access tokenPour le corriger, connectez-vous avant l’appel privé, ou passez un jeton d’API au client du plan de contrôle. Un HTTP 401 avec le code mfa_required signifie que le compte a besoin du code TOTP ou du code de récupération montré plus haut.
Se connecter sans mot de passe
Section intitulée « Se connecter sans mot de passe »Utilisez la connexion par appareil quand le code ne doit jamais voir votre mot de passe : outils en ligne de commande, agents, carnets (notebooks) et machines auxquelles vous ne confiez pas entièrement les identifiants de votre compte.
Le SDK affiche une ligne comme To sign in, visit https://dash.thalovant.com/activate and enter the code XXXX-XXXX, ouvre cette page dans votre navigateur quand il le peut, puis interroge l’API pendant que vous approuvez la connexion avec votre session de navigateur habituelle, y compris la connexion Google ou l’AMF. Une fois l’approbation donnée, le SDK reçoit un jeton d’API révocable, limité à certaines portées, et l’enregistre exactement comme après une connexion par mot de passe.
Pour approuver une connexion par appareil, l’espace de travail doit avoir un forfait payant; avec un forfait gratuit, l’approbation est refusée avec un HTTP 402. La connexion par appareil exige Python 0.4.22, Node.js 0.2.25, Go v0.3.3, Rust v0.2.20, ou Kotlin, Swift et .NET 0.1.1.
api.login_with_browser()
# Optional: request narrower scopes and label the token in the dashboard.api.login_with_browser(scopes=["hubs:read", "clients:write"], client_name="my-cli")// Prints: To sign in, visit https://dash.thalovant.com/activate and enter the code XXXX-XXXXconst token = await api.loginWithBrowser({ clientName: "my-laptop" });
// api.accessToken is now set, exactly like after api.login(...).token, err := api.LoginWithBrowser(ctx, thalovant.DeviceLoginOptions{ Scopes: []string{"hubs:read", "clients:write"}, // optional ClientName: "my-cli", // optional label in the dashboard})if err != nil { log.Fatal(err)}fmt.Println("signed in, token id:", token["token_id"])use thalovant::DeviceLoginOptions;
let token = api .login_with_browser(DeviceLoginOptions { scopes: vec!["hubs:read".into(), "clients:write".into()], client_name: Some("my-tool".into()), ..Default::default() }) .await?;println!("signed in, token scopes: {}", token["scopes"]);api.loginWithBrowser( DeviceLoginOptions(scopes = listOf("hubs:read"), clientName = "kotlin-demo"),)let token = try await api.loginWithBrowser(options: DeviceLoginOptions( scopes: ["hubs:read", "clients:write"], clientName: "my-macbook"))print(token.tokenId ?? "", token.scopes)var result = await api.LoginWithBrowserAsync(new DeviceLoginOptions{ Scopes = new[] { "hubs:read", "clients:write" }, ClientName = "my-tool",});Console.WriteLine($"{result.TokenId} expires {result.ExpiresAt}");Chaque SDK accepte les mêmes options : scopes pour restreindre ce que le jeton peut faire, client_name (selon la casse propre au langage) pour nommer le jeton dans le tableau de bord, open_browser pour ne pas ouvrir la page, prompt pour remplacer la ligne affichée, et un délai d’expiration de 15 minutes par défaut.
La personne qui approuve gère le jeton obtenu sur la page API Tokens du tableau de bord : portées, expiration, dernière utilisation et révocation.
Se connecter depuis une application mobile ou de bureau
Section intitulée « Se connecter depuis une application mobile ou de bureau »Utilisez la connexion native quand le code est l’application que la personne a sous les yeux : une application Android ou iOS, ou un client de bureau. L’application ouvre le navigateur, la personne se connecte avec sa session habituelle, et le navigateur remet à l’application un code d’autorisation. Aucun mot de passe n’atteint jamais votre processus, et aucun code d’appareil n’a à être saisi.
La connexion par appareil est le bon choix pour un outil en ligne de commande sur une machine sans navigateur. La connexion native est le bon choix quand l’application peut en ouvrir un et recevoir une redirection.
Le flux est celui du code d’autorisation OAuth 2.0 avec PKCE (RFC 7636, RFC 8252, RFC 9700), et il tient en trois appels :
- Commencer. Le SDK construit l’URL d’autorisation et vous remet deux secrets à conserver : un
stateet unverifierPKCE. - Ouvrir et attendre. L’application ouvre cette URL et attend sa redirection enregistrée.
- Terminer. L’application extrait le code de la redirection et l’échange en envoyant le vérificateur, qui n’est jamais passé par le navigateur.
La connexion native exige Python 0.7.6, Node.js, Kotlin, Swift ou .NET 0.7.2, Go v0.9.2 ou Rust 0.9.1.
from thalovant.native_auth import begin_native_sign_in
begun = begin_native_sign_in( client_id="my-app", redirect_uri="myapp://auth",)webbrowser.open(begun.authorization_url)
# ... the browser comes back to myapp://auth?code=...&state=...code = begun.code_from(redirect)if code: api.complete_native_sign_in(code, begun.verifier, "my-app", "myapp://auth")const begun = await beginNativeSignIn({ clientId: "my-app", redirectUri: "myapp://auth",});open(begun.authorizationUrl);
// ... the browser comes back to myapp://auth?code=...&state=...const code = codeFrom(begun, redirect);if (code) { await api.completeNativeSignIn(code, begun.verifier, "my-app", "myapp://auth");}beginNativeSignIn est asynchrone parce qu’il calcule le hachage du défi PKCE avec Web Crypto, ce qui permet au paquet pour navigateur de continuer à fonctionner.
begun, err := thalovant.BeginNativeSignIn(thalovant.NativeSignInOptions{ ClientID: "my-app", RedirectURI: "myapp://auth",})// open begun.AuthorizationURL
// ... the browser comes back to myapp://auth?code=...&state=...if code, ok := begun.CodeFrom(redirect); ok { token, err := plane.CompleteNativeSignIn(ctx, code, begun.Verifier, "my-app", "myapp://auth")}use thalovant::native_auth::{begin_native_sign_in, NativeSignInOptions};
let begun = begin_native_sign_in(NativeSignInOptions { client_id: "my-app".into(), redirect_uri: "myapp://auth".into(), scopes: None, dashboard_url: None,})?;// open begun.authorization_url
// ... the browser comes back to myapp://auth?code=...&state=...if let Some(code) = begun.code_from(&redirect) { plane.complete_native_sign_in(&code, &begun.verifier, "my-app", "myapp://auth").await?;}val begun = NativeSignIn.begin( clientId = "my-app", redirectUri = "myapp://auth",)// open begun.authorizationUrl
// ... the browser comes back to myapp://auth?code=...&state=...val code = begun.codeFrom(redirect)if (code != null) { plane.completeNativeSignIn(code, begun.verifier, "my-app", "myapp://auth")}let begun = try NativeSignIn.begin( clientID: "my-app", redirectURI: "myapp://auth")// open begun.authorizationURL
// ... the browser comes back to myapp://auth?code=...&state=...if let code = begun.code(from: redirect) { try await plane.completeNativeSignIn( code: code, verifier: begun.verifier, clientID: "my-app", redirectURI: "myapp://auth" )}var begun = NativeSignIn.Begin( clientId: "my-app", redirectUri: "myapp://auth");// open begun.AuthorizationUrl
// ... the browser comes back to myapp://auth?code=...&state=...var code = begun.CodeFrom(redirect);if (code is not null){ await plane.CompleteNativeSignInAsync(code, begun.Verifier, "my-app", "myapp://auth");}Les portées par défaut sont hubs:read, clients:read et clients:write, ce qui suffit à une application pour lister les hubs et gérer sa propre identité de client. Passez scopes pour les restreindre.
Ce que le SDK refuse, et pourquoi
Section intitulée « Ce que le SDK refuse, et pourquoi »Ce ne sont pas des suggestions auxquelles l’appelant peut se soustraire. C’est ce qui rend le flux sûr sur un appareil où une autre application pourrait être à l’écoute de la même redirection.
S256uniquement. L’API refuseplain, et les SDK ne l’offrent pas : il est impossible de demander le défi le plus faible.- Le vérificateur ne passe jamais par le navigateur. Il va au point de terminaison du jeton et nulle part ailleurs. C’est ce qui rend un code intercepté inutile pour qui l’a intercepté.
- La redirection doit arriver là où la tentative l’a demandée.
stateprouve que la réponse appartient à cette requête; le SDK compare aussi le schéma, l’hôte et le chemin de la redirection à ceux que la tentative a enregistrés. Un rappel qui arrive à une autre adresse n’appartient pas à cette tentative, aussi valide que paraisse son état. - Un refus qui contient un code reste un refus. Une redirection avec
error=ne renvoie aucun code, même si uncode=l’accompagne. - L’échange de jeton refuse le texte en clair. Envoyer un vérificateur et un code en HTTP simple les exposerait tous deux sur le réseau (CWE-319); l’échange exige HTTPS, sauf pour les points de terminaison de développement explicitement en boucle locale (loopback).
- L’URL du tableau de bord est validée. Ce doit être une URL Thalovant, et une URL qui contient un fragment est refusée : un fragment placerait chaque paramètre là où un navigateur ne l’envoie jamais.
code_from (selon la casse propre au langage) ne renvoie rien, au lieu de lever une exception, en cas d’état non concordant, de code manquant ou de réponse error=. Les trois veulent dire « ne continuez pas », et un appelant qui les traite de la même façon ne peut pas en prendre un par erreur pour une réussite.
Ne réessayez pas un échange échoué avec le même code. Un code présenté deux fois révoque le jeton émis par le premier échange (RFC 9700) : une nouvelle tentative détruit donc le jeton qu’elle cherche à obtenir. Recommencez à partir de l’appel de départ.
S’authentifier avec un jeton d’API
Section intitulée « S’authentifier avec un jeton d’API »Utilisez un jeton d’API enregistré quand le processus doit démarrer déjà authentifié, par exemple pour des tâches d’intégration continue, des serveurs et des configurations d’agents d’IA.
Créez un jeton sur la page API Tokens du tableau de bord ou par la connexion par appareil, puis passez-le au constructeur du plan de contrôle. Les jetons sont limités à des portées, révocables, et leur dernière utilisation s’affiche sur cette page; en créer un exige un forfait payant pour l’espace de travail. Les exemples lisent le jeton dans une variable d’environnement THALOVANT_API_TOKEN, la même convention que celle du serveur MCP.
from thalovant import ThalovantControlPlane
api = ThalovantControlPlane(access_token=os.environ["THALOVANT_API_TOKEN"])# Ready immediately; no login call needed.const api = new ThalovantControlPlane("https://api.thalovant.com", { accessToken: process.env.THALOVANT_API_TOKEN,});
// Ready immediately; no login call needed.api := thalovant.NewDefaultControlPlane(os.Getenv("THALOVANT_API_TOKEN"))use thalovant::ControlPlane;
let token = std::env::var("THALOVANT_API_TOKEN").expect("THALOVANT_API_TOKEN is set");let api = ControlPlane::with_access_token(token);val api = ThalovantControlPlane(accessToken = System.getenv("THALOVANT_API_TOKEN"))let api = ThalovantControlPlane(accessToken: ProcessInfo.processInfo.environment["THALOVANT_API_TOKEN"])var api = new ThalovantControlPlane(accessToken: Environment.GetEnvironmentVariable("THALOVANT_API_TOKEN"));