Choisissez Auto, TokenLab Verified ou Official pour chaque demande, avec les prix affichés à l'avance.Voir les nouveautés

Créer un chatbot IA avec une seule API Key : de zéro à la production en 30 minutes

·19 septembre 2026·19 min de lecture·Mis à jour 2 octobre 2026·2788 vues
#chatbot#tutoriel#python#fastapi#streaming
Créer un chatbot IA avec une seule API Key : de zéro à la production en 30 minutes

Vous pouvez créer un chatbot IA qui diffuse une réponse en streaming en une après-midi. Le vrai travail consiste à le maintenir en vie après la première erreur 429. Nous allons construire un petit service FastAPI avec streaming SSE, mémoire de conversation côté serveur et une liste blanche de modèles, le tout derrière une seule clé TokenLab. L'objectif est d'obtenir un backend que vous pouvez placer derrière une interface produit réelle et changer de modèle sans réécrire l'intégration. Si vous n'avez pas encore effectué le changement d'URL de base, lisez d'abord le guide de migration. Si votre préoccupation principale est le backoff en cas de charge, associez ce guide au guide de limitation de débit de l'API IA.

Points clés à retenir

  • Un chatbot prêt pour la production nécessite six composants : un endpoint synchrone, un endpoint de streaming, un état de conversation côté serveur, une liste blanche de modèles, une gestion réelle des erreurs et un chemin de mise à niveau du stockage.
  • Chargez la clé depuis TOKENLAB_API_KEY dans chaque processus et gardez-la sur le serveur. La documentation sur l'authentification (observée le 03/10/2026) indique de ne jamais exposer les clés dans le code côté client ni de les commiter.
  • GET /v1/models est public, donc une réponse réussie ne prouve pas que votre clé fonctionne. Prouvez-le avec une vraie requête de chat (Quickstart, observé le 03/10/2026).
  • Le SSE couvre la plupart des produits de chat. Considérez un flux qui s'interrompt après l'affichage d'une sortie comme incomplet, et non comme une réponse terminée.
  • La liste blanche de ce guide associe des niveaux (tiers) à deepseek-v4-flash, gpt-5.5, claude-sonnet-5 et gemini-3.5-flash. Chacun répertorie openai_chat_completions comme format accepté (observé le 03/10/2026).
  • D'après les prix ci-dessous, un tour de 2 000 tokens en entrée et 500 en sortie coûte environ 0,0006 $ à 0,0075 $, selon le modèle.

Construire un chatbot IA : configuration et service principal

Le service final comporte six parties mobiles :

  1. Un endpoint /chat synchrone pour les tests de fumée.
  2. Un endpoint /chat/stream en streaming pour l'interface utilisateur réelle.
  3. Un état de conversation indexé par conversation_id.
  4. Une liste blanche de modèles, afin que le frontend ne puisse pas demander des IDs arbitraires.
  5. Une gestion des erreurs qui ne s'effondre pas à la première erreur 429.
  6. Un chemin allant du prototype en mémoire vers Redis ou PostgreSQL.

C'est suffisant pour un bot de support, un assistant interne ou la première version d'un widget de chat intégré. Chaque bloc de code ci-dessous s'ajoute à un seul fichier, main.py, dans l'ordre indiqué.

Étape 1 : Définir la clé et installer la stack

Créez une clé dans API Keys dans votre espace de travail. La valeur complète n'est affichée qu'une seule fois, et le préfixe dans la liste des clés ne peut pas authentifier les requêtes. Toutes les clés TokenLab commencent par sk-. Vérifiez également votre solde dans Billing, car la première requête est facturée au prix actuel du modèle (Quickstart, observé le 03/10/2026).

Stockez la clé dans une variable d'environnement, jamais dans le code source. L'invite ci-dessous l'empêche d'apparaître dans la ligne de commande et l'historique du shell, et elle ne s'applique qu'au terminal actuel et à ses processus enfants. Utilisez un gestionnaire de secrets pour les applications déployées. Le Quickstart propose également une version PowerShell.

printf 'TokenLab API key: '
read -r -s TOKENLAB_API_KEY
printf '\n'
export TOKENLAB_API_KEY
pip install fastapi uvicorn openai pydantic

Vous n'avez pas besoin d'un SDK spécifique à TokenLab. Le SDK officiel OpenAI fonctionne une fois que vous avez modifié la clé et l'URL de base (Guide des SDK, observé le 03/10/2026).

Étape 2 : Définir le schéma, la liste blanche et la mémoire

Nous commençons par les éléments partagés. Le schéma de requête utilise tier, et non une chaîne model brute, et chaque exemple curl plus loin dans ce guide envoie les mêmes champs.

La première démo de chatbot conserve généralement la transcription dans le navigateur et la renvoie à chaque tour. Cela fonctionne pour un prototype. Cela devient complexe dès que vous avez besoin de tentatives, de sessions reprenables ou d'outils côté serveur. Le serveur possède donc l'historique dès le début.

import json
import logging
import os
import time
import uuid
from collections import defaultdict

from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from openai import APIConnectionError, APIStatusError, OpenAI, RateLimitError
from pydantic import BaseModel, Field

log = logging.getLogger("chatbot")
app = FastAPI()

client = OpenAI(
    api_key=os.environ["TOKENLAB_API_KEY"],
    base_url="https://api.tokenlab.sh/v1",
    timeout=30.0,
    max_retries=0,
)

ALLOWED_MODELS = {
    "default": "deepseek-v4-flash",
    "flagship": "gpt-5.5",
    "balanced": "claude-sonnet-5",
    "budget": "gemini-3.5-flash",
}
SYSTEM_PROMPT = "You are a helpful assistant. Be concise and direct."
MAX_HISTORY = 20          # messages kept per conversation (your choice)
MAX_REPLY_TOKENS = 1000   # output cap per turn (your choice)

class ChatRequest(BaseModel):
    message: str = Field(min_length=1, max_length=4000)
    tier: str = "default"
    conversation_id: str | None = None

def resolve_model(tier: str) -> str:
    model = ALLOWED_MODELS.get(tier)
    if model is None:
        raise HTTPException(status_code=400, detail=f"unknown tier: {tier}")
    return model

conversations: dict[str, list[dict]] = defaultdict(list)

def build_messages(conv_id: str, user_msg: str) -> list[dict]:
    history = conversations[conv_id][-MAX_HISTORY:]
    return [
        {"role": "system", "content": SYSTEM_PROMPT},
        *history,
        {"role": "user", "content": user_msg},
    ]

def remember(conv_id: str, user_msg: str, reply: str) -> None:
    conversations[conv_id].append({"role": "user", "content": user_msg})
    conversations[conv_id].append({"role": "assistant", "content": reply})

Le client définit max_retries=0 et un délai d'attente de 30 secondes, conformément au Quickstart. Nous prenons nous-mêmes les décisions de réessai, car un réessai aveugle du SDK sur un tour en streaming peut répéter une facturation.

Étape 3 : Mapper les erreurs et ajouter l'endpoint synchrone

Nous conservons une petite fonction qui transforme toute exception du SDK en un code d'état et un message sûr pour l'utilisateur. Les détails vont dans vos logs, pas à l'utilisateur.

def describe_error(exc: Exception) -> tuple[int, str]:
    if isinstance(exc, RateLimitError):  # check first: it subclasses APIStatusError
        return 429, "The assistant is busy. Please retry shortly."
    if isinstance(exc, APIConnectionError):
        return 502, "Connection problem. Please retry."
    if isinstance(exc, APIStatusError):
        log.error("upstream status=%s code=%s", exc.status_code, exc.code)
        if exc.status_code in (401, 402, 403):  # key, balance, or permission: operator problem
            return 503, "The assistant is unavailable right now."
        if exc.status_code in (400, 404):  # bad field or model ID: our bug
            return 500, "Something went wrong on our side."
        return 502, "The assistant is unavailable right now."
    return 500, "Something went wrong."

def retry_delay(exc: RateLimitError) -> float:
    try:
        return min(float(exc.response.headers.get("retry-after", "2")), 10.0)
    except ValueError:
        return 2.0

def create_with_one_retry(**kwargs):
    try:
        return client.chat.completions.create(**kwargs)
    except RateLimitError as exc:
        time.sleep(retry_delay(exc))
        return client.chat.completions.create(**kwargs)

@app.post("/chat")
def chat(req: ChatRequest):
    model = resolve_model(req.tier)
    conv_id = req.conversation_id or str(uuid.uuid4())
    messages = build_messages(conv_id, req.message)
    try:
        response = create_with_one_retry(
            model=model, messages=messages, max_tokens=MAX_REPLY_TOKENS
        )
    except (APIConnectionError, APIStatusError) as exc:
        status, message = describe_error(exc)
        raise HTTPException(status_code=status, detail=message)
    reply = response.choices[0].message.content or ""
    log.info("model=%s usage=%s", model, response.usage)
    remember(conv_id, req.message, reply)
    return {"conversation_id": conv_id, "reply": reply}

Exécutez une requête avant de construire quoi que ce soit d'autre. Si cela échoue, ne continuez pas à construire par-dessus.

Diffuser les réponses en streaming et maintenir la réactivité du chatbot IA

La plupart des produits de chatbot semblent lents non pas parce que le modèle est lent, mais parce que l'interface utilisateur reste vide jusqu'à ce que la réponse complète arrive. Nous ajoutons le SSE car il couvre la plupart des produits de chat et comporte moins de poids opérationnel que les websockets. Si votre produit exécute déjà un client navigateur sur HTTP standard, le SSE simplifie l'architecture.

Étape 4 : Ajouter l'endpoint de streaming

La documentation sur le streaming (Guide de streaming, observé le 03/10/2026) maintient le streaming des Chat Completions valide pour les frameworks qui attendent déjà des chunks SSE de /v1/chat/completions. Nous restons sur cette voie car les quatre modèles de la liste blanche l'acceptent. Nous encodons chaque événement en JSON, de sorte que les sauts de ligne dans une réponse ne puissent pas briser le formatage.

def sse(payload: dict) -> str:
    return f"data: {json.dumps(payload)}\n\n"

@app.post("/chat/stream")
def chat_stream(req: ChatRequest):
    model = resolve_model(req.tier)
    conv_id = req.conversation_id or str(uuid.uuid4())
    messages = build_messages(conv_id, req.message)

    def generate():
        parts: list[str] = []
        finish_reason = None
        try:
            with client.chat.completions.create(
                model=model,
                messages=messages,
                max_tokens=MAX_REPLY_TOKENS,
                stream=True,
                stream_options={"include_usage": True},
            ) as stream:
                for chunk in stream:
                    if chunk.usage:
                        log.info("model=%s usage=%s", model, chunk.usage)
                    if not chunk.choices:
                        continue
                    choice = chunk.choices[0]
                    if choice.delta.content:
                        parts.append(choice.delta.content)
                        yield sse({"delta": choice.delta.content})
                    if choice.finish_reason:
                        finish_reason = choice.finish_reason
        except (APIConnectionError, APIStatusError) as exc:
            _, message = describe_error(exc)
            yield sse({"error": message})
            return
        if finish_reason != "stop":
            yield sse({"error": f"Reply incomplete ({finish_reason})."})
            return
        remember(conv_id, req.message, "".join(parts))
        yield sse({"done": True, "conversation_id": conv_id})

    return StreamingResponse(generate(), media_type="text/event-stream")

Deux détails comptent ici. Premièrement, include_usage: true ajoute l'utilisation des tokens aux chunks du flux, et le chunk d'utilisation final n'a pas de choices, donc la boucle l'ignore. Deuxièmement, nous enregistrons le tour uniquement lorsque finish_reason est stop. Une fin de type length signifie que la limite de tokens a coupé la réponse, et la documentation répertorie les deux comme des conditions de fin normales.

Comme les erreurs apparaissent à l'intérieur du générateur, le statut HTTP est déjà 200 au moment où une défaillance se produit. Le client doit lire l'événement error. Le bloc with ferme le flux amont lorsque le générateur se termine. La documentation conseille d'annuler l'amont lorsque l'utilisateur s'arrête ou se déconnecte, alors vérifiez que votre serveur ASGI arrête réellement le générateur lors d'une déconnexion.

Étape 5 : Lire le flux dans le navigateur

Le navigateur doit appeler votre propre backend authentifié et jamais TokenLab directement. Analysez par limite d'événement, car un chunk réseau n'est pas nécessairement un événement SSE complet.

async function sendMessage(payload, onDelta) {
  const response = await fetch('/chat/stream', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload), // { message, tier, conversation_id }
  });
  if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`);

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const events = buffer.split('\n\n');
    buffer = events.pop();
    for (const raw of events) {
      if (!raw.startsWith('data: ')) continue;
      const event = JSON.parse(raw.slice(6));
      if (event.error) throw new Error(event.error);
      if (event.delta) onDelta(event.delta);
    }
  }
}

Mettre à niveau la mémoire vers Redis quand vous en avez besoin

Le dictionnaire en mémoire est suffisant pour un processus. Tournez-vous vers Redis lorsque les conversations ont besoin d'un TTL, d'une capacité de reprise ou de plusieurs instances. Tournez-vous vers PostgreSQL lorsque la transcription est une donnée produit que vous devez interroger, auditer ou rapporter. Le changement vers Redis est une question de plomberie de stockage, pas de logique. Exécutez pip install redis, puis faites en sorte que build_messages et remember lisent et écrivent via ces helpers.

import redis

redis_client = redis.Redis(
    host=os.environ.get("REDIS_HOST", "127.0.0.1"), port=6379, decode_responses=True
)

def load_history(conv_id: str) -> list:
    raw = redis_client.get(f"chat:{conv_id}")
    return json.loads(raw) if raw else []

def save_history(conv_id: str, history: list) -> None:
    redis_client.setex(f"chat:{conv_id}", 60 * 60 * 24, json.dumps(history))

Gérer les erreurs lors de la création d'un chatbot IA pour la production

Un utilisateur final ne se soucie pas de savoir si une défaillance provient d'une limitation de débit, d'un solde vide ou d'une panne en amont. Il se soucie de savoir si l'interface utilisateur se bloque. Imaginez un widget de support dont le flux s'interrompt après quarante mots. La documentation de TokenLab indique qu'un flux interrompu après l'arrivée de la sortie n'est pas une réponse complète, et le répéter peut générer une sortie différente et une seconde facturation (Gérer les erreurs API, observé le 03/10/2026). C'est pourquoi l'étape 4 n'enregistre jamais un tour inachevé.

Étape 6 : Appliquer les règles de réessai

Gérez les erreurs par statut HTTP et code, et non par le message lisible par l'homme, qui peut changer sans préavis. Le tableau combine la documentation avec ce que fait notre service.

Statut Signification (selon la doc) Ce que fait ce service
400 Champ, ID de modèle ou entrée invalide Retourner 500, loguer, ne jamais réessayer
401 Clé manquante, invalide, expirée ou révoquée Retourner 503, alerter l'opérateur
402 Solde ou limite de clé API trop bas (insufficient_balance, quota_exceeded) Retourner 503, alerter l'opérateur
403 La clé ne peut pas utiliser ce modèle Retourner 503, vérifier la liste des modèles de la clé
404 Ressource ou modèle introuvable Retourner 500, vérifier l'ID par rapport à /v1/models
429 Limite de requête atteinte Attendre Retry-After, réessayer une fois (synchrone uniquement)
500–504 Service indisponible ou échec réseau Réessayer uniquement si retryable est true ; respecter retry_after

Deux mises en garde de la documentation. Une erreur 503 all_channels_failed ne signifie pas toujours une panne temporaire. Si retryable est false, ne répétez pas la même requête et vérifiez GET /v1/models avant d'en choisir un autre. De plus, Chat Completions et Responses utilisent un objet error de style OpenAI, mais Anthropic Messages et Gemini conservent leurs propres formats. Notre service appelle Chat Completions sur chaque modèle de la liste blanche, donc un seul analyseur fonctionne. Si vous déplacez un niveau vers un format natif, vous aurez besoin d'un second analyseur.

Enregistrez l'ID de requête des en-têtes de réponse avec l'endpoint, le modèle, l'heure et votre propre ID utilisateur ou de travail. La documentation ne nomme pas l'en-tête sur les pages que nous avons utilisées, alors confirmez-le par rapport au chemin de dépannage avant de connecter vos logs.

Le reste de la liste de contrôle de production est court :

  • Ajoutez vos propres IDs de requête afin de pouvoir connecter les échecs du frontend aux logs du backend.
  • Limitez la concurrence par utilisateur et la taille des requêtes. Le max_length=4000 sur message est un point de départ que vous devriez ajuster.
  • Limitez l'historique par nombre de messages, comme le fait MAX_HISTORY. La documentation de facturation indique que la tokenisation diffère selon le modèle, la langue et l'entrée média, donc une formule basée sur le nombre de caractères n'est pas une estimation de facturation fiable.
  • Loguez le modèle, la latence, l'utilisation et la raison de fin à chaque tour.
  • Gardez les messages visibles par l'utilisateur séparés des détails d'erreur internes.
  • Envoyez un tour de test vers un niveau alternatif, afin de savoir que le repli fonctionne avant la première panne réelle.

Choisir les modèles et estimer le coût de votre chatbot IA

Ne laissez jamais le frontend transmettre une chaîne de modèle arbitraire à l'API. Un champ de texte libre invite aux fautes de frappe, aux modèles retirés et aux modèles que vous n'aviez jamais l'intention d'exposer. La carte ALLOWED_MODELS vous donne un seul endroit pour changer de modèle lorsqu'un fournisseur en déprécie un, sans mise à jour du client. Les IDs de modèle inconnus renvoient model_not_found, et la documentation vous renvoie vers /v1/models pour un ID actuel. Consultez le répertoire des modèles avant de verrouiller la liste blanche.

Prix actuels par million de tokens

Il s'agit des prix TokenLab en USD provenant de l'entrée API en direct de chaque modèle, observés le 03/10/2026. La tarification a été mise à jour pour la dernière fois le 02/10/2026 à 16:53:30.068Z. Les quatre modèles affichaient le stade de cycle de vie active.

Niveau ID Modèle Entrée / 1M Sortie / 1M Lecture cache / 1M Source
default deepseek-v4-flash (off-peak) 0,15 $ 0,60 $ 0,003 $ model API
default deepseek-v4-flash (peak) 0,30 $ 1,20 $ 0,006 $ model API
flagship gpt-5.5 1,50 $ 9,00 $ 0,15 $ model API
balanced claude-sonnet-5 0,90 $ 4,50 $ 0,09 $ model API
budget gemini-3.5-flash 0,75 $ 4,50 $ 0,075 $ model API

Pour deepseek-v4-flash, l'entrée répertorie les fenêtres de pointe en semaine, hors jours fériés chinois. Le hors-pointe inclut les week-ends et ces jours fériés. claude-sonnet-5 répertorie également un prix d'écriture en cache de 1,125 $ par million de tokens.

Une requête travaillée

Supposons un tour avec 2 000 tokens d'entrée et 500 tokens de sortie. Cette taille est notre hypothèse, pas une mesure. Pour gpt-5.5 :

  • Entrée : 2 000 × 1,50 $ / 1 000 000 = 0,0030 $
  • Sortie : 500 × 9,00 $ / 1 000 000 = 0,0045 $
  • Total : 0,0075 $ par tour (estimation, sans hits de cache)

En utilisant la même arithmétique, nous obtenons ces estimations : deepseek-v4-flash coûte 0,0006 $ en hors-pointe et 0,0012 $ en pointe, claude-sonnet-5 coûte 0,00405 $, et gemini-3.5-flash coûte 0,00375 $. Sur 1 000 tours de ce type, cela représente environ 7,50 $ sur gpt-5.5 contre 0,60 $ à 1,20 $ sur deepseek-v4-flash. Les tours réels augmentent à mesure que l'historique s'accumule, alors lisez usage à partir de la réponse ou du chunk de flux final plutôt que de faire confiance à ce dimensionnement.

En pratique, nous acheminons la plupart des conversations vers le niveau par défaut et réservons gpt-5.5 ou claude-sonnet-5 pour les tours qui en ont besoin. La documentation de facturation avertit que le prix le plus bas par token n'est pas toujours le coût le plus bas par tâche terminée. Exécutez les mêmes invites via deux niveaux et comparez le coût final dans Usage.

Deux vérifications avant de lancer. La documentation de facturation indique de ne pas coder en dur un tableau de prix copié, alors lisez GET /v1/models/:model/pricing si votre application affiche les coûts (Facturation et tarification, observé le 03/10/2026). De plus, l'entrée deepseek-v4-flash répertorie json-mode, prompt-cache et tool-use mais pas vision. Si votre chatbot accepte des images, acheminez ces tours vers un niveau dont l'entrée répertorie vision.

Test de fumée et lancement de votre chatbot IA

Étape 7 : Exécutez-le de bout en bout

Démarrez le serveur dans un terminal où TOKENLAB_API_KEY est exporté. Les charges utiles curl utilisent les mêmes champs message, tier et conversation_id que ChatRequest.

uvicorn main:app --reload --port 8000
# 1. Endpoint synchrone : attendez une réponse courte et un conversation_id
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Reply only with OK.", "tier": "default"}'

# 2. Streaming : attendez des lignes data: {"delta": ...}, puis {"done": true, ...}
curl -N -X POST http://localhost:8000/chat/stream \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello!", "tier": "default"}'

# 3. Mémoire : réutilisez un conversation_id renvoyé ci-dessus
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "What did I just say?", "tier": "default", "conversation_id": "PASTE_ID_FROM_STEP_1"}'

# 4. Échec forcé : attendez HTTP 400 et "unknown tier: nope"
curl -i -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello!", "tier": "nope"}'

Pour forcer une défaillance en amont, redémarrez le serveur avec une mauvaise TOKENLAB_API_KEY. Votre client devrait recevoir un message 503 propre, et vos logs devraient montrer l'erreur 401 en amont avec invalid_api_key. Ouvrez ensuite Requests pour confirmer le modèle, le statut et l'utilisation des appels réussis. Si vous pouvez diffuser un tour, préserver une conversation et renvoyer une erreur propre sur une défaillance forcée, vous avez la bonne base.

Après cela, les prochaines mises à niveau sont de l'infrastructure ennuyeuse plutôt que plus d'IA :

  • Authentification, pour qu'un utilisateur ne puisse pas lire la conversation d'un autre.
  • Persistance, pour que les sessions survivent aux déploiements.
  • Limitation de débit par utilisateur, pour qu'un utilisateur bruyant ne puisse pas épuiser votre quota.
  • Attribution de l'utilisation, si le chatbot est destiné aux clients.
  • Résumé en arrière-plan, si les conversations ont besoin d'une mémoire à long terme.

Une fois la migration de l'URL de base derrière vous, un changement de modèle est une modification de configuration dans ALLOWED_MODELS.

FAQ

Ai-je besoin de websockets pour construire un chatbot IA ?

Non. Le SSE, illustré à l'étape 4, couvre la plupart des produits de chat. Les websockets ajoutent de la valeur lorsque vous avez besoin d'une poussée bidirectionnelle en dehors de la requête et de la réponse, comme la collaboration en direct ou les événements initiés par le serveur. Pour une interface de chat standard, le SSE est plus simple à déployer et à déboguer.

Quel modèle mon chatbot devrait-il utiliser par défaut ?

Commencez par un niveau à bas prix tel que deepseek-v4-flash et ajoutez claude-sonnet-5 ou gpt-5.5 derrière la liste blanche pour les tours plus difficiles. Les prix ci-dessus, observés le 03/10/2026, montrent l'écart. Testez ensuite vos propres invites sur deux niveaux et comparez le coût final dans Usage. Consultez le répertoire des modèles avant de vous engager, car les gammes changent.

Pourquoi mon chatbot renvoie-t-il 402 ou 429 ?

Une erreur 402 signifie que le solde du compte ou la limite d'utilisation de la clé elle-même est trop bas. Les codes sont insufficient_balance et quota_exceeded, et répéter la requête n'aidera pas. Une erreur 429 signifie que vous avez atteint une limite de requête, alors attendez Retry-After puis réessayez. Notre endpoint synchrone réessaie une fois et l'endpoint de streaming signale l'erreur au client.

Le navigateur peut-il appeler TokenLab directement ?

Pas en toute sécurité. La documentation sur l'authentification indique de ne jamais exposer les clés API dans le code côté client. Gardez la clé dans l'environnement serveur et faites en sorte que le navigateur appelle votre propre backend authentifié, comme le fait l'exemple frontend.


Créez une clé API sur tokenlab.sh, ajoutez des fonds et exécutez le test de fumée ci-dessus.

Sources

Prix observé le 2026-10-03

Modèles liés

Modèles récemment publiés

Construire avec les modèles de ce guide

Comparez les prix, testez les routes et transformez la recherche en appel API fonctionnel.