Tutoriel API Claude : créer votre première app sur VPS
Créez une clé API Claude, protégez-la sur Ubuntu 24.04 et déployez en Python un analyseur de logs avec streaming, erreurs typées et contrôle précis des coûts.
Ce que vous allez construire
Un outil en ligne de commande installé sur un VPS Ubuntu 24.04 vierge. Vous lui transmettez un message d’erreur ou un extrait de journal avec un pipe, et il renvoie un diagnostic en français courant : journalctl -u nginx -n 50 | explain. Le programme tient dans une soixantaine de lignes Python. Il met en pratique tous les éléments nécessaires à une véritable application utilisant l’API Claude : stockage correct d’une clé, virtualenv, formats de réponse du SDK, streaming, chaîne d’exceptions typées et unité systemd, afin qu’il s’exécute sans intervention.
J’ai choisi ce projet à dessein. Dans la plupart des tutoriels consacrés à une « première application API », vous construisez un chatbot que vous ne rouvrirez jamais. Un outil d’analyse des journaux est utile sur un serveur dès le premier jour. Il vous oblige aussi à maîtriser les deux points que les débutants traitent généralement mal : lire correctement l’objet de réponse et contrôler les dépenses. La facturation de l’API dépend du nombre de tokens, sans plafond autre que ceux que vous définissez. Le contrôle des coûts fait donc partie de la conception, et non des tâches ajoutées après coup. C’est la même discipline qui devient nécessaire lorsque vous passez à l’exécution de Claude Code sur ce même VPS dans tmux.
Obtenir une clé API depuis la Console
L’accès à l’API se gère dans l’Anthropic Console, à l’adresse platform.claude.com. Inscrivez-vous, puis créez une clé dans Settings → API Keys (la documentation renvoie directement vers platform.claude.com/settings/keys). La clé ne s’affiche qu’une seule fois, commence par sk-ant- et ne peut pas être récupérée ensuite. Copiez-la immédiatement, ou supprimez-la et générez-en une nouvelle.
Concernant le coût, en juillet 2026, l’API ne propose pas de niveau gratuit permanent. La documentation tarifaire d’Anthropic indique que les nouveaux utilisateurs reçoivent un petit montant de crédits gratuits pour effectuer des tests. Le montant exact correspond à celui affiché par la Console lors de l’inscription. Une fois ces crédits épuisés, vous devez approvisionner le compte pour que les requêtes aboutissent. Cela est distinct d’un abonnement à claude.ai : un forfait Pro ou Max n’inclut pas de crédits API, et une clé API ne donne pas accès à l’application de chat. Si vous hésitez entre un abonnement et l’API, ce choix mérite un sujet distinct : le forfait Claude dont vous avez réellement besoin.
Créez la clé en la limitant à un seul projet ou serveur. Lorsqu’une clé fuit — ce qui finira par arriver si vous attendez suffisamment longtemps — vous devez pouvoir la révoquer sans interrompre tous vos autres services.
Ne mettez pas la clé dans .bashrc
Le réflexe consiste à export ANTHROPIC_API_KEY=sk-ant-... dans ~/.bashrc. Ne le faites pas. Cela pose trois problèmes distincts :
- Chaque processus en hérite. Une variable d’environnement exportée dans votre shell de connexion est transmise à tout ce que vous lancez : l’application web, le rapporteur de plantage qui enregistre utilement votre environnement dans un rapport de bug, ou la page
phpinfo()que quelqu’un a laissée activée. La surface d’exposition de la clé devient « tout ce que cet utilisateur exécute ». - La saisir l’inscrit dans
~/.bash_history. Exécutez l’export manuellement une seule fois et votre clé reste dans un fichier en clair, définitivement, puis est copiée dans chaque sauvegarde de votre répertoire personnel. - Elle n’est pas disponible quand systemd en a besoin. Les services ne lisent pas votre
.bashrc. Cette méthode échoue donc précisément lorsque vous transformez le script en unité, généralement avec une erreur 401 incompréhensible à 6 heures du matin.
Sur un serveur, la bonne méthode consiste à utiliser un fichier d’environnement dédié avec des permissions 600, chargé uniquement par le processus qui en a besoin :
sudo mkdir -p /opt/explain
sudo install -m 600 -o root -g root /dev/null /etc/claude-explain.env
printf 'ANTHROPIC_API_KEY=sk-ant-YOUR-KEY-HERE\n' | sudo tee /etc/claude-explain.env >/dev/nullUtilisez tee avec printf plutôt qu’un éditeur si vous voulez éviter que la clé soit copiée dans les fichiers de swap de l’éditeur. Dans tous les cas, vérifiez avec ls -l /etc/claude-explain.env qu’il lit -rw------- et que le fichier appartient à root. Les shells interactifs reçoivent la clé à chaque invocation par l’intermédiaire d’un wrapper (ci-dessous), et systemd l’obtient via EnvironmentFile=. root lit le fichier avant l’abandon des privilèges ; l’utilisateur du service n’a donc jamais besoin d’y avoir accès en lecture. La clé n’apparaît ni dans le code, ni dans git, ni dans la sortie de ps, ni dans l’historique du shell.
Installer le SDK dans un venv
Ubuntu 24.04 fournit Python 3.12 avec l’application de PEP 668. Un pip install anthropic exécuté directement avec l’interpréteur système échoue donc avec error: externally-managed-environment. Ce comportement est normal pour l’OS. Utilisez un virtualenv :
sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropicAucune activation n’est nécessaire sur un serveur : l’appel direct à /opt/explain/venv/bin/python utilise toujours les paquets du venv.
Premier appel et lecture correcte de la réponse
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1000,
messages=[{"role": "user", "content": "Explain what a systemd unit file is in three sentences."}],
)
for block in response.content:
if block.type == "text":
print(block.text)Deux éléments de ces douze lignes couvrent l’essentiel du modèle mental de l’API. Premièrement, anthropic.Anthropic() sans argument lit la clé depuis l’environnement. Ne la transmettez jamais sous forme de chaîne littérale. Deuxièmement, response.content est une liste de blocs de contenu, et non une chaîne. Si vous l’affichez directement, vous obtenez le résultat classique d’un premier essai :
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Ce n’est pas un bug, mais la représentation de l’objet. Les réponses peuvent contenir plusieurs types de blocs (texte, appels d’outils, réflexion). Vous devez donc les parcourir et vérifier block.type == "text" avant d’accéder à .text. Mettez cette boucle en place dès le premier jour : vous éviterez toute une catégorie de problèmes où « le résultat s’affiche de façon incohérente ».
Utilisez exactement l’identifiant de modèle claude-opus-4-8. Les identifiants actuels ne contiennent pas de date. Ne cédez pas à l’habitude, ni à un ancien article de blog, qui vous inciterait à ajouter un suffixe de date : cela produit une erreur 404, expliquée plus bas.
L’outil lui-même : explications
Voici le programme complet. Il lit depuis stdin, produit le diagnostic au fil de l’eau et gère les erreurs :
#!/usr/bin/env python3
"""explain: pipe an error or log excerpt in, get a diagnosis out."""
import sys
import anthropic
MODEL = "claude-opus-4-8"
def main() -> int:
text = sys.stdin.read().strip()
if not text:
print("usage: journalctl -u nginx -n 50 | explain", file=sys.stderr)
return 1
client = anthropic.Anthropic()
try:
with client.messages.stream(
model=MODEL,
max_tokens=1500,
system=(
"You are a senior Linux sysadmin. The user pipes you server "
"logs or error output. Name the most likely cause outright, "
"then give the commands to confirm and fix it. Be terse."
),
messages=[{"role": "user", "content": text}],
) as stream:
for chunk in stream.text_stream:
print(chunk, end="", flush=True)
print()
except anthropic.RateLimitError as e:
retry_after = e.response.headers.get("retry-after", "60")
print(f"rate limited; retry in {retry_after}s", file=sys.stderr)
return 2
except anthropic.APIStatusError as e:
print(f"API error {e.status_code}: {e.message}", file=sys.stderr)
return 2
except anthropic.APIConnectionError:
print("network error reaching the API", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())Enregistrez-le sous /opt/explain/explain.py, puis ajoutez un wrapper qui charge la clé pour l’utilisation interactive :
sudo tee /usr/local/bin/explain >/dev/null <<'EOF'
#!/bin/sh
set -a; . /etc/claude-explain.env; set +a
exec /opt/explain/venv/bin/python /opt/explain/explain.py "$@"
EOF
sudo chmod 755 /usr/local/bin/explain(Le wrapper doit être exécuté via sudo, ou le fichier d’environnement doit appartenir à un groupe auquel votre utilisateur administrateur appartient. Choisissez délibérément l’une de ces options au lieu d’assouplir les permissions du fichier à 644.)
Pourquoi le streaming. client.messages.stream affiche les tokens dès leur arrivée au lieu de rester silencieux pendant toute la génération. Cela évite aussi les timeouts HTTP avec les sorties longues. Pour cette même raison, le SDK refuse les valeurs max_tokens très élevées dans les appels sans streaming. Si vous avez besoin de l’objet assemblé ensuite, appelez stream.get_final_message() dans le bloc with.
Pourquoi cet ordre des exceptions. Le SDK lève des exceptions typées, de la plus spécifique à la plus générale : RateLimitError correspond à une réponse 429 et contient un en-tête retry-after qui indique le délai d’attente ; APIStatusError couvre les autres réponses qui ne sont pas en 2xx (consultez e.status_code >= 500 en cas de problème côté serveur) ; APIConnectionError signifie que la requête n’a reçu aucune réponse. Avant de créer une boucle de nouvelle tentative, retenez ceci : le SDK réessaie déjà automatiquement les erreurs 429 et 5xx, deux fois par défaut avec un backoff exponentiel (max_retries sur le client). Lorsque votre except est exécuté, les nouvelles tentatives sont terminées. Dans une CLI, il faut donc signaler l’erreur et quitter, plutôt que d’attendre puis de relancer les requêtes en boucle.
Maîtrise des coûts
Cette question mérite une section à part, car l’API n’impose aucun plafond mensuel intégré au-delà de celui que vous configurez, et chaque erreur se cumule silencieusement.
max_tokens est le plafond de dépense par appel. Les tokens de sortie sont la partie la plus coûteuse : avec Opus 4.8, ils coûtent cinq fois plus cher que les tokens d’entrée, et max_tokens fixe un plafond strict au nombre de tokens que le modèle peut produire. Un prompt qui s’emballe ne peut pas générer plus de sortie que ce que vous avez autorisé. Adaptez cette valeur à la tâche : 1,500 suffisent largement pour diagnostiquer un journal, tandis qu’une tâche de classification nécessite 100. Si les réponses s’interrompent au milieu d’une phrase avec stop_reason: "max_tokens", le plafond est trop bas. Augmentez-le volontairement au lieu de choisir par défaut une valeur très élevée.
Comptez les tokens avant l’envoi. Les tokens d’entrée sont également facturés, et les journaux sont volumineux. L’API propose un endpoint de comptage gratuit, avec ses propres limites de débit, distinctes de celles de la création de messages :
count = client.messages.count_tokens(
model="claude-opus-4-8",
messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)Utilisez-le pour éviter d’envoyer accidentellement un journal de 2 GB à l’outil. N’utilisez pas tiktoken pour cela : c’est le tokenizer d’OpenAI, et il sous-estime d’environ 15–20% le nombre de tokens Claude sur du texte courant, et davantage encore sur du code.
Choisissez le modèle selon la tâche, pas par fidélité. En juillet 2026, Opus 4.8 (claude-opus-4-8) coûte $5 par million de tokens d’entrée et $25 par million de tokens de sortie ; Haiku 4.5 (claude-haiku-4-5) coûte $1/$5 avec un contexte de 200K ; Sonnet 5 (claude-sonnet-5) se situe entre les deux, à $3/$15, avec un tarif de lancement de $2/$10 jusqu’au 31 août 2026. Concrètement, un extrait de journal de 2,000 tokens suivi d’une réponse de 500 tokens coûte environ $0.0225 avec Opus et $0.0045 avec Haiku. Commencez avec Opus pour évaluer la qualité des sorties, puis testez les mêmes prompts avec Haiku. Pour les transformations simples à gros volume, le résultat est souvent indiscernable pour un cinquième du prix. Vérifiez les tarifs actuels sur la page de tarification avant d’intégrer ces valeurs en dur dans un budget.
Utilisez les batches pour les tâches qui peuvent attendre. L’API Batches traite les requêtes de manière asynchrone, à 50% des tarifs standard, et la plupart des batches sont terminés en moins d’une heure. Les synthèses nocturnes, les backfills, la classification en masse et toute tâche qui ne nécessite pas qu’un humain attende doivent être traités de cette manière.
Mettez en cache le contexte répété. Si chaque appel renvoie le même gros prompt système ou le même runbook, marquez-le comme pouvant être mis en cache :
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1000,
system=[{
"type": "text",
"text": RUNBOOK_TEXT, # the same 30K tokens on every call
"cache_control": {"type": "ephemeral"},
}],
messages=[{"role": "user", "content": question}],
)
print(response.usage.cache_read_input_tokens) # non-zero from the second call onL’écriture dans le cache coûte environ 1.25 fois le tarif des tokens d’entrée, et la lecture environ 0.1 fois ce tarif, avec un TTL de 5 minutes. Le deuxième appel effectué pendant cette période amortit donc déjà le premier. Deux points sont à retenir. Le préfixe mis en cache doit dépasser un minimum propre à chaque modèle, de quelques milliers de tokens avec Opus. Un prompt système court ne sera donc tout simplement pas mis en cache. De plus, si cache_read_input_tokens reste à zéro lors d’appels identiques, un élément de votre préfixe change à chaque requête ; un timestamp est généralement en cause.
N’oubliez pas ce qui est comptabilisé comme entrée. Les prompts système, les définitions d’outils et, dans les conversations à plusieurs tours, l’intégralité de l’historique renvoyé à chaque tour sont tous facturés comme des tokens d’entrée. Une boucle de chat qui ne réduit jamais l’historique voit ses coûts augmenter de manière quadratique. Il est utile de comprendre le détail de la facturation avant de développer un système conversationnel : comment sont réellement calculés l’utilisation des tokens et la facturation de Claude.
Exécuter le script avec systemd
L’intérêt de cette gestion rigoureuse du fichier d’environnement : un timer qui récapitule chaque matin les erreurs de la veille.
# /etc/systemd/system/log-digest.service
[Unit]
Description=Daily error-log digest via the Claude API
[Service]
Type=oneshot
User=explain
Group=systemd-journal
EnvironmentFile=/etc/claude-explain.env
ExecStart=/bin/sh -c 'journalctl -p err --since yesterday | /opt/explain/venv/bin/python /opt/explain/explain.py >> /var/log/log-digest.txt'# /etc/systemd/system/log-digest.timer
[Unit]
Description=Run the log digest every morning
[Timer]
OnCalendar=06:15
Persistent=true
[Install]
WantedBy=timers.targetsudo useradd -r -s /usr/sbin/nologin explain
sudo touch /var/log/log-digest.txt && sudo chown explain /var/log/log-digest.txt
sudo systemctl daemon-reload
sudo systemctl enable --now log-digest.timer
sudo systemctl start log-digest.service # test it once, right nowNotez ce que EnvironmentFile= permet : systemd lit le fichier appartenant à root et configuré en mode 600 avant de basculer vers l’utilisateur non privilégié explain. Le processus reçoit donc la variable, tandis que l’utilisateur ne peut pas lire le fichier contenant la clé. Le groupe systemd-journal donne accès aux journaux. Testez avec un systemctl start manuel et consultez journalctl -u log-digest.service. N’attendez pas 06:15 pour découvrir une faute de frappe. Lorsque ce modèle dépasse les possibilités d’un pipeline shell, la même approche fondée sur une clé dans un fichier d’environnement s’intègre directement aux workflows n8n utilisant Claude sur le même serveur.
Modes d’échec et messages affichés
401 avec une clé valide. L’exception est la suivante :
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}Si la clé fonctionne dans votre shell, mais que le service renvoie 401, c’est que le service ne l’a jamais reçue. N’oubliez pas que systemd ne lit pas .bashrc. Vérifiez que EnvironmentFile= pointe vers le bon chemin. Autres causes possibles : des guillemets copiés dans le fichier d’environnement (ANTHROPIC_API_KEY="sk-ant-..." ; systemd retire les guillemets, mais le . file de votre wrapper shell les conserve dans la valeur si vous avez utilisé des guillemets de manière incorrecte), des espaces en fin de ligne ou une clé révoquée dans la Console la semaine dernière.
404 à cause d’une faute dans l’identifiant du modèle. Le cas le plus fréquent consiste à ajouter un suffixe de date à l’identifiant d’un modèle actuel :
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-4-8-20260115'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}Les identifiants des modèles actuels doivent être saisis exactement comme indiqué : claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Copiez-les depuis la documentation des modèles, et non de mémoire ou depuis un ancien tutoriel.
429 rate_limit_error. La chaîne du type d’erreur est rate_limit_error et la réponse contient un en-tête retry-after indiquant le nombre de secondes à attendre. Le SDK a déjà effectué deux nouvelles tentatives avec backoff avant d’afficher l’exception. Des erreurs 429 persistantes indiquent donc que votre débit soutenu dépasse réellement les limites de votre offre. Regroupez le travail ou répartissez-le dans le temps. Ne réduisez pas l’intervalle entre les tentatives.
Le programme affiche l’objet au lieu du texte. La sortie ressemble à [TextBlock(citations=None, text='...', type='text')]. Vous avez affiché response.content au lieu de parcourir les blocs et de lire .text dans ceux pour lesquels block.type == "text". Tous les exemples du SDK ci-dessus utilisent la bonne méthode. Copiez la boucle.
error: externally-managed-environment. Vous avez exécuté pip install avec le Python système d’Ubuntu 24.04. Utilisez l’environnement virtuel. N’exécutez jamais --break-system-packages sur un serveur auquel vous tenez.
Réponses tronquées. response.stop_reason == "max_tokens" signifie que le modèle a atteint votre limite de sortie au milieu de sa réponse. Le comportement est conforme au fonctionnement prévu. Augmentez la limite de manière délibérée.
Une fois votre première application opérationnelle, créer un agent IA avec Claude transforme ces mêmes appels d’API en un agent capable d’utiliser des outils.
FAQ
Combien coûte l’API Claude pour faire un essai ?
Très peu pour un outil de ce type. En juillet 2026, Opus 4.8 coûte 5 $ par million de tokens en entrée et 25 $ par million en sortie. Un diagnostic de journaux classique, avec quelques milliers de tokens en entrée et quelques centaines en sortie, coûte donc environ 2 cents. Avec Haiku 4.5 (1 $/5 $), le coût est inférieur à un demi-cent. Un mois de synthèses quotidiennes coûte moins cher qu’un café. Le risque ne vient pas du prix par appel, mais des boucles sans limite et de max_tokens sans limite. C’est pourquoi les deux sont définis explicitement dans ce guide.
Existe-t-il une offre gratuite pour l’API Claude ?
Il n’existe pas d’offre gratuite permanente en juillet 2026. La documentation tarifaire d’Anthropic indique que les nouveaux utilisateurs reçoivent un petit montant de crédits gratuits pour tester l’API, sous la forme d’un essai unique. Le montant exact est affiché dans la Console lors de l’inscription. Vous devez ensuite approvisionner le compte. Si votre objectif est de ne rien payer par requête plutôt que d’obtenir la meilleure qualité disponible, vous pouvez auto-héberger un modèle open-weight avec Ollama et utiliser de la RAM au lieu de tokens.
Comment protéger ma clé API sur un serveur ?
Ne la mettez jamais dans le code, dans git ou dans une variable exportée depuis .bashrc. Ne la saisissez jamais dans un shell dont l’historique la conservera. Placez-la dans un fichier appartenant à root, avec les permissions 600. Chargez-la par processus : utilisez un script wrapper pour un usage interactif et EnvironmentFile= avec systemd. Utilisez une clé distincte par serveur ou par projet afin de pouvoir révoquer une clé compromise sans devoir en remplacer davantage. Si la clé apparaît un jour sur un site de partage de texte ou dans un commit git, révoquez-la immédiatement dans la Console. Supprimer le commit ne suffit pas à faire disparaître la fuite.
Avec quel modèle Claude dois-je commencer ?
Commencez avec claude-opus-4-8 pendant que vous vérifiez si les sorties sont suffisamment bonnes pour servir de base. Vous pourrez ainsi évaluer l’idée avec la meilleure qualité, et le coût reste de quelques cents pour un usage personnel. Une fois le prompt défini, relancez vos entrées réelles avec claude-haiku-4-5. Pour la synthèse, la classification et le triage des journaux, ce modèle est souvent aussi performant pour un cinquième du prix. Passez à Haiku ou Sonnet en fonction des mesures, pas par défaut.