Tutoriel API Claude : votre première app VPS
Obtenez une clé API Claude, sécurisez-la sur Ubuntu 24.04, livrez un analyseur de logs Python avec streaming, erreurs typées et vrai contrôle des coûts.
Ce que vous allez construire
Un outil en ligne de commande sur un VPS Ubuntu 24.04 tout neuf, dans lequel vous envoyez par un tube un message d'erreur ou un extrait de log, et qui vous renvoie un diagnostic en langage clair : journalctl -u nginx -n 50 | explain. Cela représente une soixantaine de lignes de Python, et cet outil met en pratique tout ce dont a besoin une vraie application de l'API Claude : une clé stockée correctement, un virtualenv, la forme des réponses du SDK, le streaming, la chaîne d'exceptions typées, et une unité systemd pour qu'il tourne sans vous.
J'ai choisi ce projet à dessein. La plupart des tutoriels « première application avec une API » vous font construire un chatbot que vous n'ouvrirez plus jamais. Un explicateur de logs justifie sa place sur un serveur dès le premier jour, et il vous confronte aux deux choses que les débutants ratent vraiment : lire correctement l'objet réponse, et maîtriser la dépense. L'API facture au jeton, sans autre plafond que ceux que vous fixez, donc le contrôle des coûts est ici un paramètre de conception, pas une réflexion après coup, la même discipline qui compte quand vous passerez à 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 la Console Anthropic sur platform.claude.com : créez un compte, puis créez une clé sous Settings → API Keys (la documentation renvoie directement vers platform.claude.com/settings/keys). La clé n'est affichée qu'une seule fois, commence par sk-ant-, et ne peut plus être récupérée ensuite : copiez-la immédiatement, ou supprimez-la et réémettez-en une.
Côté argent : en juillet 2026, il n'existe pas d'offre gratuite permanente pour l'API. La documentation tarifaire d'Anthropic indique que les nouveaux utilisateurs reçoivent un petit montant de crédits gratuits pour tester ; le montant exact est celui que la Console vous affiche à l'inscription, et une fois épuisé, vous devez approvisionner le compte avant que les requêtes n'aboutissent. C'est distinct d'un abonnement claude.ai : un forfait Pro ou Max n'inclut pas de crédit API, et une clé API ne vous donne pas l'application de chat. Si vous hésitez entre l'abonnement et l'API, ce choix mérite son propre sujet : quel forfait Claude il vous faut vraiment.
Créez la clé en la limitant à un seul projet ou serveur. Quand une clé fuite, et sur un horizon assez long, cela finira par arriver, vous voulez pouvoir la révoquer sans casser tout le reste de ce que vous possédez.
Garder la clé hors de .bashrc
Le réflexe est de mettre export ANTHROPIC_API_KEY=sk-ant-... dans ~/.bashrc. Ne le faites pas. Trois problèmes distincts :
- Chaque processus en hérite. Une variable d'environnement exportée dans votre shell de connexion se propage à tout ce que vous lancez : l'application web, le rapporteur de plantage qui vide obligeamment son environnement dans un rapport de bug, 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 un jour ». - La saisir l'inscrit dans
~/.bash_history. Lancez l'export à la main une seule fois et votre clé se retrouve dans un fichier en clair, pour toujours, et se synchronise dans chaque sauvegarde de votre répertoire personnel. - Elle n'est pas là quand systemd en a besoin. Les services ne lisent pas votre
.bashrc, donc le procédé échoue précisément au moment où vous promouvez le script en unité, en général sous la forme d'un mystérieux 401 à 6 h du matin.
Le bon procédé sur un serveur est 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 depuis un printf plutôt qu'un éditeur si vous voulez éviter que la clé se retrouve dans les fichiers d'échange de l'éditeur ; dans tous les cas, vérifiez avec ls -l /etc/claude-explain.env qu'il affiche -rw------- et qu'il appartient à root. Les shells interactifs reçoivent la clé à chaque invocation via un wrapper (ci-dessous), et systemd la reçoit via EnvironmentFile= : root lit le fichier avant d'abandonner ses privilèges, si bien que l'utilisateur du service n'a jamais besoin d'un accès en lecture. La clé n'apparaît jamais dans le code, dans git, dans la sortie de ps, ni dans l'historique du shell.
Installer le SDK dans un venv
Ubuntu 24.04 est livré avec Python 3.12 qui applique la PEP 668, donc un simple pip install anthropic contre l'interpréteur système échoue avec error: externally-managed-environment. Cette erreur, c'est le système d'exploitation qui fonctionne comme prévu : 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 cérémonie d'activation n'est nécessaire sur un serveur : appeler directement /opt/explain/venv/bin/python utilise toujours les paquets du venv.
Premier appel, et lire la réponse correctement
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 choses dans ces douze lignes portent l'essentiel du modèle mental de l'API. D'abord, anthropic.Anthropic() sans argument lit la clé depuis l'environnement : ne la passez jamais sous forme de chaîne littérale. Ensuite, response.content est une liste de blocs de contenu, pas une chaîne. Affichez-la directement et vous obtenez la sortie classique du débutant :
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Ce n'est pas un bug ; c'est la représentation (repr) de l'objet. Les réponses peuvent contenir plusieurs types de blocs (texte, appels d'outils, raisonnement), donc vous itérez et vérifiez block.type == "text" avant de toucher à .text. Mettez cette boucle en place dès le premier jour et toute une catégorie de confusion du type « ça affiche n'importe quoi » ne se produit jamais.
Utilisez l'identifiant de modèle exact claude-opus-4-8. Les identifiants de la génération actuelle sont sans date : résistez à l'automatisme (ou au vieux billet de blog) qui vous pousse à ajouter un suffixe de date ; cela produit un 404, traité plus bas.
L'outil concret : explain
Voici le programme complet : entrée sur stdin, diagnostic diffusé en streaming en sortie, erreurs gérées :
#!/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 un usage interactif :
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 s'exécuter via sudo, ou bien le fichier d'environnement doit avoir un groupe auquel appartient votre utilisateur administrateur : choisissez l'une des deux options délibérément plutôt que d'assouplir le fichier en 644.)
Pourquoi le streaming. client.messages.stream affiche les jetons à mesure qu'ils arrivent au lieu de rester silencieux pendant toute la génération, et il contourne les délais d'expiration HTTP sur les sorties longues : le SDK refusera d'ailleurs les très grandes valeurs de max_tokens sur les appels sans streaming pour exactement cette raison. Si vous avez besoin de l'objet assemblé ensuite, appelez stream.get_final_message() à l'intérieur du 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 à un 429 et porte un en-tête retry-after qui vous indique combien de temps attendre ; APIStatusError couvre les autres réponses non-2xx (vérifiez e.status_code >= 500 pour un problème côté serveur) ; APIConnectionError signifie que la requête n'a jamais reçu de réponse du tout. Et avant de construire une boucle de nouvelle tentative : le SDK réessaie déjà lui-même les 429 et les erreurs 5xx, deux fois par défaut avec un backoff exponentiel (max_retries sur le client). Au moment où votre except s'exécute, les tentatives sont épuisées : la bonne conduite dans une CLI est donc de signaler et de sortir, pas de dormir et de marteler.
Contrôle des coûts
Cela mérite sa propre section, car l'API n'a aucun plafond mensuel intégré au-delà de ce que vous configurez, et chaque erreur ici s'accumule silencieusement.
max_tokens est votre plafond de dépense par appel. Les jetons de sortie sont la direction coûteuse, sur Opus 4.8, cinq fois le prix de l'entrée, et max_tokens est un plafond strict sur le nombre de jetons que le modèle peut produire. Un prompt qui s'emballe ne peut pas coûter plus de sortie que ce que vous avez autorisé. Dimensionnez-le selon la tâche : 1 500 suffit largement pour un diagnostic de log ; une tâche de classification en demande 100. Si les réponses s'arrêtent au milieu d'une phrase avec stop_reason: "max_tokens", vous l'avez trop serré : relevez-le en conscience plutôt que d'opter par défaut pour une valeur énorme.
Comptez avant d'envoyer. L'entrée coûte de l'argent elle aussi, et les logs sont volumineux. L'API dispose d'un point de terminaison de comptage gratuit à l'usage (il a ses propres limites de débit, distinctes 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)Servez-vous-en pour vous prémunir contre l'envoi accidentel d'un log de 2 Go dans l'outil. N'utilisez pas tiktoken pour cela : c'est le tokeniseur d'OpenAI, et il sous-compte les jetons de Claude d'environ 15 à 20 % sur du texte courant, et davantage 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 jetons d'entrée et 25 $ par million en sortie ; Haiku 4.5 (claude-haiku-4-5) est à 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 log de 2 000 jetons avec une réponse de 500 jetons coûte environ 0,0225 $ sur Opus et 0,0045 $ sur Haiku. Commencez sur Opus pendant que vous jugez la qualité des sorties, puis essayez les mêmes prompts sur Haiku : pour les transformations simples à fort volume, la différence est souvent imperceptible, à un cinquième du prix. Vérifiez les chiffres actuels sur la page de tarification avant d'inscrire quoi que ce soit de tout cela en dur dans un budget.
Les lots pour tout ce qui peut attendre. L'API Batches traite les requêtes de manière asynchrone à 50 % des prix standard, et la plupart des lots se terminent en moins d'une heure. Les synthèses nocturnes, les reprises de données, la classification en masse, tout ce qui n'a pas d'humain en attente a sa place là.
La mise en cache de prompt pour un 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 onLes écritures en cache coûtent environ 1,25 fois le prix de l'entrée, les lectures en cache environ 0,1 fois, sur une durée de vie (TTL) de 5 minutes : le deuxième appel dans la fenêtre paie donc déjà pour le premier. Deux pièges. Le préfixe mis en cache doit dépasser un minimum propre à chaque modèle, quelques milliers de jetons sur Opus, si bien qu'un prompt système court ne sera silencieusement pas mis en cache du tout. Et si cache_read_input_tokens reste à zéro sur des appels identiques, c'est que quelque chose dans votre préfixe change à chaque requête (un horodatage est le coupable habituel).
Rappelez-vous ce qui compte comme entrée. Les prompts système, les définitions d'outils, et, dans les conversations à plusieurs tours, tout l'historique que vous renvoyez à chaque tour sont tous facturés comme jetons d'entrée. Une boucle de chat qui ne coupe jamais l'historique voit son coût croître de façon quadratique. Le décompte complet mérite d'être compris avant de construire quoi que ce soit de conversationnel : comment l'usage et la facturation des jetons Claude s'additionnent réellement.
Le faire tourner sous systemd
La récompense de la discipline du fichier d'environnement : un minuteur qui résume 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 vous apporte EnvironmentFile= : systemd lit le fichier appartenant à root, en mode 600, avant de redescendre vers l'utilisateur non privilégié explain, si bien que le processus obtient la variable alors que l'utilisateur ne peut pas lire le fichier de clé. Le groupe systemd-journal accorde l'accès aux logs. Testez avec un systemctl start manuel et lisez journalctl -u log-digest.service : n'attendez pas 6 h 15 pour découvrir une faute de frappe. Quand ce procédé dépasse les capacités d'un pipeline shell, la même approche de clé dans un fichier d'environnement se transpose directement dans des workflows n8n propulsés par Claude sur la même machine.
Modes de défaillance, avec les chaînes que vous verrez
401 sur une clé qui fonctionne. L'exception affiche :
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 un 401, c'est que le service ne l'a jamais reçue : rappelez-vous que systemd ne lit pas .bashrc ; vérifiez que EnvironmentFile= pointe vers le bon chemin. Autres causes : des guillemets collés dans le fichier d'environnement (ANTHROPIC_API_KEY="sk-ant-...", systemd les écarte, mais le . file de votre wrapper shell les conserve dans la valeur si vous avez mis les guillemets de travers), des espaces en fin de ligne, ou une clé que vous avez révoquée dans la Console la semaine dernière.
404 dû à une faute de frappe sur le modèle. La version de loin la plus courante est l'ajout d'un suffixe de date à un identifiant de 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 de la génération actuelle sont exacts tels qu'ils sont écrits : claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Copiez-les depuis la documentation des modèles, jamais de mémoire ni d'un vieux tutoriel.
429 rate_limit_error. La chaîne du type d'erreur est rate_limit_error et la réponse porte un en-tête retry-after avec le nombre de secondes à attendre. Le SDK a déjà réessayé deux fois avec backoff avant que vous ne voyiez l'exception, donc des 429 persistants signifient que votre débit soutenu dépasse réellement votre palier : traitez le travail par lots ou étalez-le, ne resserrez pas la boucle de nouvelle tentative.
Ça affiche l'objet, pas le texte. La sortie ressemble à [TextBlock(citations=None, text='...', type='text')]. Vous avez affiché response.content au lieu d'itérer sur les blocs et de lire .text sur ceux où block.type == "text". Chaque exemple de SDK ci-dessus le fait correctement ; copiez la boucle.
error: externally-managed-environment. Vous avez lancé pip install contre le Python système d'Ubuntu 24.04. Utilisez le venv, jamais --break-system-packages sur un serveur qui compte pour vous.
Réponses tronquées. response.stop_reason == "max_tokens" signifie que le modèle a atteint votre plafond de sortie en pleine réflexion. C'est le comportement prévu ; relevez le plafond délibérément.
Une fois votre première application fonctionnelle, la construction d'un agent IA avec Claude transforme ces mêmes appels d'API en un agent qui utilise des outils.
FAQ
Combien coûte l'essai de l'API Claude ?
Vraiment peu pour un outil de ce genre. En juillet 2026, Opus 4.8 coûte 5 $ par million de jetons d'entrée et 25 $ par million en sortie, donc un diagnostic de log typique, quelques milliers de jetons en entrée, quelques centaines en sortie, revient à environ deux centimes, et sur Haiku 4.5 (1 $/5 $) à moins d'un demi-centime. Un mois de synthèses quotidiennes coûte moins qu'un café. Le risque, ce n'est pas le prix par appel ; ce sont les boucles sans borne et les max_tokens sans borne, et c'est pourquoi les deux sont fixés explicitement dans ce guide.
Existe-t-il une offre gratuite pour l'API Claude ?
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, un essai unique, avec le montant exact affiché dans la Console à l'inscription, après quoi vous approvisionnez le compte. Si votre objectif est un coût marginal nul par requête plutôt qu'une qualité de pointe, l'alternative est d'héberger vous-même un modèle à poids ouverts avec Ollama et de payer en RAM au lieu de jetons.
Comment garder ma clé API en sécurité sur un serveur ?
Jamais dans le code, jamais dans git, jamais exportée depuis .bashrc, jamais saisie dans un shell dont l'historique la conservera. Mettez-la dans un fichier appartenant à root avec des permissions 600, chargez-la par processus, un script wrapper pour l'usage interactif, EnvironmentFile= pour systemd, et limitez une clé par serveur ou par projet pour que révoquer une clé fuitée relève de la chirurgie, pas de l'amputation. Si la clé touche un jour un site de collage ou un commit git, révoquez-la immédiatement dans la Console ; supprimer le commit ne défait pas la fuite.
Avec quel modèle Claude commencer ?
Commencez avec claude-opus-4-8 pendant que vous évaluez si les sorties sont assez bonnes pour construire dessus : vous voulez juger l'idée à pleine qualité, et à un volume de loisir la différence de coût se compte en centimes. Une fois le prompt stabilisé, relancez vos entrées réelles sur claude-haiku-4-5 ; pour la synthèse, la classification et le tri de logs, il est fréquemment aussi bon à un cinquième du prix. Passez à Haiku ou Sonnet par la mesure, pas par défaut.