Claude API su VPS: tutorial Python con Ubuntu 24.04
Crea un analizzatore di log in Python su Ubuntu 24.04: key sicura, virtualenv, streaming, errori tipizzati, systemd e controllo dei costi API.
Cosa stai costruendo
Uno strumento da riga di comando su un VPS Ubuntu 24.04 appena installato. È possibile passargli tramite pipe un messaggio di errore o una parte di un log per ottenere una diagnosi in linguaggio semplice: journalctl -u nginx -n 50 | explain. Il programma contiene circa sessanta righe di Python e mette alla prova tutti gli elementi necessari in una vera applicazione che usa la Claude API: una key archiviata correttamente, un virtualenv, le strutture delle risposte dell'SDK, lo streaming, la catena delle eccezioni tipizzate e un'unità systemd, così il programma viene eseguito senza interventi manuali.
Ho scelto volutamente questo progetto. La maggior parte dei tutorial sulle «prime applicazioni API» propone di creare un chatbot che non verrà mai più utilizzato. Un analizzatore di log è utile su un server fin dal primo giorno e obbliga ad affrontare i due aspetti che i principianti gestiscono più spesso in modo errato: leggere correttamente l'oggetto della risposta e controllare la spesa. L'API addebita i token utilizzati e non impone alcun limite massimo oltre a quelli configurati dall'utente. Per questo il controllo dei costi è un requisito progettuale, non un'attività successiva: è la stessa disciplina necessaria quando si passa a usare Claude Code su questo stesso VPS in tmux.
Ottieni una chiave API dalla Console
L'accesso all'API si gestisce nella Anthropic Console all'indirizzo platform.claude.com. Registrati, quindi crea una chiave in Settings → API Keys. La documentazione rimanda direttamente a platform.claude.com/settings/keys. La chiave viene mostrata una sola volta, inizia con sk-ant- e non può essere recuperata in seguito. Copiala immediatamente oppure eliminala e generane una nuova.
Per quanto riguarda i costi, a luglio 2026 non esiste un piano gratuito continuativo per l'API. La documentazione sui prezzi di Anthropic indica che i nuovi utenti ricevono una piccola quantità di crediti gratuiti per i test. L'importo esatto è quello mostrato dalla Console durante la registrazione. Quando i crediti terminano, devi aggiungere fondi all'account prima che le richieste vadano a buon fine. Questo è distinto da un abbonamento claude.ai: un piano Pro o Max non include crediti API e una chiave API non consente di usare l'app di chat. Se devi scegliere tra abbonamento e API, il compromesso è un argomento separato: quale piano Claude ti serve davvero.
Crea la chiave limitandola a un singolo progetto o server. Quando una chiave viene divulgata, prima o poi può succedere. Devi quindi poterla revocare senza interrompere tutto il resto che gestisci.
Tieni la chiave fuori da .bashrc
La prima reazione è export ANTHROPIC_API_KEY=sk-ant-... in ~/.bashrc. Non farlo. I problemi sono tre:
- Ogni processo la eredita. Una variabile d'ambiente esportata nella shell di login viene propagata a tutto ciò che avvii: l'applicazione web, il crash reporter che salva opportunamente l'ambiente in un report di errore, la pagina
phpinfo()che qualcuno ha lasciato abilitata. La superficie di esposizione della chiave diventa "tutto ciò che questo utente esegue". - Se la digiti, finisce in
~/.bash_history. Esegui l'export manualmente una sola volta e la chiave resta in un file in testo semplice, per sempre, oltre a essere inclusa in ogni backup della tua home directory. - Non è disponibile quando serve a systemd. I servizi non leggono il tuo
.bashrc, quindi questo approccio fallisce proprio quando trasformi lo script in un'unità, di solito con un misterioso errore 401 alle 6 del mattino.
Su un server, l'approccio corretto consiste in un file d'ambiente dedicato con permessi 600, caricato solo dal processo che ne ha bisogno:
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/nullUsa tee da un printf invece di un editor se vuoi evitare che la chiave finisca nei file di swap dell'editor; in entrambi i casi, verifica con ls -l /etc/claude-explain.env che legga -rw------- e che il file sia di proprietà di root. Le shell interattive ricevono la chiave a ogni esecuzione tramite un wrapper (riportato sotto), mentre systemd la riceve tramite EnvironmentFile=; root legge il file prima di abbandonare i privilegi, quindi l'utente del servizio non deve avere accesso in lettura. La chiave non compare mai nel codice, in git, nell'output di ps o nella cronologia della shell.
Installare l’SDK in un venv
Ubuntu 24.04 include Python 3.12 con l’applicazione di PEP 668, quindi un comando pip install anthropic eseguito sull’interprete di sistema restituisce error: externally-managed-environment. L’errore indica che il sistema operativo sta funzionando come previsto. Usare 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 anthropicSu un server non è necessaria alcuna attivazione: chiamando direttamente /opt/explain/venv/bin/python si usano sempre i pacchetti del venv.
Prima chiamata e lettura corretta della risposta
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)In quelle dodici righe, due aspetti definiscono il modello mentale principale dell'API. Primo: anthropic.Anthropic() senza argomenti legge la chiave dall'ambiente; non passarla mai come stringa letterale. Secondo: response.content è un elenco di blocchi di contenuto, non una stringa. Se lo si stampa direttamente, si ottiene il tipico output di chi esegue il codice per la prima volta:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Non è un bug, ma la rappresentazione dell'oggetto. Le risposte possono contenere più tipi di blocco, ad esempio testo, chiamate a strumenti e contenuto di ragionamento; per questo è necessario iterare e controllare block.type == "text" prima di accedere a .text. Inserire questo ciclo fin dal primo giorno evita completamente una classe di problemi del tipo «stampa dati incomprensibili».
Usa esattamente l'ID modello claude-opus-4-8. Gli ID delle generazioni attuali non contengono date. Non aggiungere automaticamente un suffisso con la data, come suggeriscono l'abitudine o un vecchio articolo del blog: in questo modo si ottiene un errore 404, descritto più avanti.
Lo strumento vero e proprio: spiegazione
Ecco il programma completo: legge da stdin, restituisce una diagnosi in streaming e gestisce gli errori:
#!/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())Salvalo come /opt/explain/explain.py, quindi aggiungi un wrapper che carichi la chiave per l'uso interattivo:
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(Il wrapper deve essere eseguito tramite sudo oppure il file dell'ambiente deve appartenere a un gruppo di cui fa parte l'utente amministratore. Scegli consapevolmente una delle due opzioni, invece di rendere il file leggibile da tutti con i permessi 644.)
Perché usare lo streaming. client.messages.stream stampa i token non appena arrivano, invece di restare inattivo per tutta la durata della generazione. Inoltre evita i timeout HTTP per gli output lunghi. Per lo stesso motivo, l'SDK rifiuta valori max_tokens molto elevati nelle chiamate non in streaming. Se in seguito ti serve l'oggetto completo, chiama stream.get_final_message() all'interno del blocco with.
Perché questo ordine delle eccezioni. L'SDK genera eccezioni tipizzate, in ordine dalla più specifica: RateLimitError corrisponde a un errore 429 e contiene un'intestazione retry-after che indica per quanto tempo attendere; APIStatusError copre le altre risposte non 2xx. Controlla e.status_code >= 500 per individuare eventuali problemi lato server. APIConnectionError indica che la richiesta non ha ricevuto alcuna risposta. Prima di implementare un ciclo di retry, considera che l'SDK ripete già automaticamente le richieste in caso di errori 429 e 5xx, per impostazione predefinita 2 volte con exponential backoff (max_retries sul client). Quando viene eseguito except, i retry sono quindi già esauriti. In una CLI è corretto segnalare l'errore e terminare, non attendere e sovraccaricare nuovamente il servizio.
Controllo dei costi
Questa gestione merita una sezione dedicata perché l’API non applica un limite mensile integrato oltre a quello configurato e ogni errore si somma senza essere rilevato.
max_tokens è il limite massimo di spesa per chiamata. I token di output sono la voce più costosa: con Opus 4.8 costano cinque volte più di quelli di input, mentre max_tokens impone un limite rigido al numero di token che il modello può generare. Un prompt fuori controllo non può produrre più output di quello consentito. Imposta il valore in base all’attività: 1,500 token sono sufficienti per diagnosticare un log; per una classificazione ne bastano 100. Se le risposte si interrompono a metà frase con stop_reason: "max_tokens", il limite è troppo basso: aumentalo consapevolmente, senza impostarlo automaticamente su valori molto elevati.
Conta i token prima di inviare la richiesta. Anche l’input ha un costo e i log possono essere voluminosi. L’API offre un endpoint di conteggio gratuito, con limiti di frequenza propri e distinti da quelli per la creazione dei messaggi:
count = client.messages.count_tokens(
model="claude-opus-4-8",
messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)Usalo per evitare di inoltrare accidentalmente un log da 2 GB allo strumento. Non usare tiktoken per questo scopo: è il tokenizer di OpenAI e, nei testi comuni, sottostima di circa il 15–20% il numero di token Claude; la differenza è maggiore con il codice.
Scegli il modello in base all’attività, non per abitudine. A luglio 2026, Opus 4.8 (claude-opus-4-8) costa $5 per milione di token di input e $25 per milione di token di output; Haiku 4.5 (claude-haiku-4-5) costa $1/$5 e offre un contesto di 200K; Sonnet 5 (claude-sonnet-5) si colloca tra i due, a $3/$15, con prezzi introduttivi di $2/$10 fino al 31 agosto 2026. In concreto, un estratto di log da 2,000 token con una risposta da 500 token costa circa $0.0225 su Opus e $0.0045 su Haiku. Inizia con Opus mentre valuti la qualità dell’output, poi prova gli stessi prompt su Haiku: per trasformazioni semplici e ad alto volume, la differenza è spesso impercettibile a un quinto del costo. Verifica i valori aggiornati nella pagina dei prezzi prima di inserirli in modo statico in un budget.
Usa i batch per le attività non urgenti. La Batches API elabora le richieste in modo asincrono al 50% dei prezzi standard e la maggior parte dei batch termina entro un’ora. Digest notturni, backfill, classificazioni in blocco e tutte le attività per cui nessun operatore deve attendere la risposta appartengono a questa categoria.
Usa il caching dei prompt per il contesto ripetuto. Se ogni chiamata reinvia lo stesso prompt di sistema voluminoso o lo stesso runbook, contrassegnalo come memorizzabile nella 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 onLa scrittura nella cache costa circa 1.25 volte il prezzo dell’input, mentre le letture dalla cache costano circa 0.1 volte, con un TTL di 5 minuti. La seconda chiamata nella stessa finestra temporale ripaga quindi già il costo della prima. Ci sono due aspetti da considerare. Il prefisso memorizzato nella cache deve superare una dimensione minima specifica per il modello, pari a qualche migliaio di token su Opus; un prompt di sistema breve, quindi, potrebbe non essere memorizzato affatto. Inoltre, se cache_read_input_tokens resta pari a zero tra chiamate identiche, significa che qualcosa nel prefisso cambia a ogni richiesta; nella maggior parte dei casi si tratta di un timestamp.
Ricorda cosa viene conteggiato come input. I prompt di sistema, le definizioni degli strumenti e, nelle conversazioni con più turni, l’intera cronologia reinviata a ogni turno vengono tutti fatturati come token di input. Un ciclo di chat che non elimina mai la cronologia aumenta il costo in modo quadratico. Prima di creare un sistema conversazionale conviene comprendere l’intero calcolo: come vengono calcolati realmente l’utilizzo dei token e la fatturazione di Claude.
Eseguirlo con systemd
Il vantaggio della disciplina nell’uso dei file di ambiente è un timer che ogni mattina riepiloga gli errori del giorno precedente.
# /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 nowNotare cosa consente EnvironmentFile=: systemd legge il file di proprietà di root e con modalità 600 prima di passare all’utente senza privilegi explain. In questo modo il processo riceve la variabile, mentre l’utente non può leggere il file che contiene la chiave. Il gruppo systemd-journal concede l’accesso ai log. Eseguire un test con un systemctl start manuale e leggere journalctl -u log-digest.service. Non aspettare le 06:15 per scoprire un errore di battitura. Quando questo schema supera le possibilità di una pipeline shell, lo stesso approccio basato sulla chiave nel file di ambiente può essere trasferito direttamente ai workflow n8n basati su Claude sullo stesso server.
Cause di errore, con le stringhe visualizzate
401 con una chiave valida. L'eccezione è:
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}Se la chiave funziona nella shell ma il servizio restituisce 401, il servizio non l'ha mai ricevuta. Ricorda che systemd non legge .bashrc; verifica che EnvironmentFile= punti al percorso corretto. Altre cause possibili sono le virgolette incollate nel file env (ANTHROPIC_API_KEY="sk-ant-...": systemd rimuove le virgolette, ma il valore di . file nel wrapper della shell le conserva se le hai usate in modo non corretto), gli spazi bianchi finali oppure una chiave revocata nella Console la settimana scorsa.
404 a causa di un errore nell'ID del modello. Il caso più comune consiste nell'aggiungere un suffisso con la data all'ID di un modello corrente:
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-4-8-20260115'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}Gli ID delle generazioni attuali devono corrispondere esattamente a quelli indicati, claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Copiali dalla documentazione dei modelli, senza trascriverli a memoria o da un tutorial obsoleto.
429 rate_limit_error. La stringa del tipo di errore è rate_limit_error e la risposta include un'intestazione retry-after con il numero di secondi da attendere. L'SDK ha già riprovato 2 volte con un backoff prima di mostrare l'eccezione. Se gli errori 429 persistono, significa che il tuo limite di utilizzo sostenuto supera effettivamente il livello assegnato. Suddividi il lavoro in batch o distribuiscilo nel tempo; non ridurre l'intervallo tra i tentativi.
Stampa l'oggetto invece del testo. L'output è simile a [TextBlock(citations=None, text='...', type='text')]. Hai stampato response.content invece di iterare sui blocchi e leggere .text da quelli per cui block.type == "text". Tutti gli esempi SDK precedenti lo fanno correttamente; copia il ciclo.
error: externally-managed-environment. Hai eseguito pip install usando il Python di sistema di Ubuntu 24.04. Usa l'ambiente virtuale; non usare mai --break-system-packages su un server importante.
Risposte troncate. response.stop_reason == "max_tokens" significa che il modello ha raggiunto il limite di output nel mezzo della risposta. Il comportamento è previsto; aumenta il limite in modo intenzionale.
Dopo aver eseguito la prima applicazione, creare un agente AI con Claude trasforma le stesse chiamate API in un agente che usa strumenti.
FAQ
Quanto costa provare la Claude API?
Davvero poco per uno strumento di questo tipo. A luglio 2026, Opus 4.8 costa $5 per milione di token di input e $25 per milione di token di output. Una diagnosi tipica dei log, con qualche migliaio di token in input e poche centinaia in output, costa quindi circa due centesimi. Con Haiku 4.5 ($1/$5) costa meno di mezzo centesimo. Un mese di digest giornalieri costa meno di un caffè. Il rischio non è il costo della singola richiesta, ma i cicli senza limite e max_tokens senza limite. Per questo, in questa guida, entrambi vengono impostati esplicitamente.
Esiste un piano gratuito per la Claude API?
No, a luglio 2026 non esiste un piano gratuito continuativo. La documentazione sui prezzi di Anthropic indica che i nuovi utenti ricevono una piccola quantità di crediti gratuiti per provare l'API. Si tratta di una prova una tantum e l'importo esatto viene mostrato nella Console durante la registrazione. In seguito, è necessario finanziare l'account. Se l'obiettivo è azzerare il costo marginale per richiesta, invece di ottenere la massima qualità disponibile, l'alternativa è eseguire in autonomia un modello open-weight con Ollama e pagare in RAM anziché in token.
Come posso proteggere la mia API key su un server?
Mai nel codice, mai in git e mai esportata da .bashrc. Non digitarla mai in una shell in cui la cronologia possa conservarla. Inseriscila in un file di proprietà di root con permessi 600 e caricala per singolo processo: usa uno script wrapper per l'interazione e EnvironmentFile= per systemd. Usa una chiave distinta per ogni server o progetto, così la revoca di una chiave esposta resta un'operazione mirata. Se la chiave finisce su un sito di paste o in un commit git, revocala immediatamente nella Console. Eliminare il commit non annulla l'esposizione della chiave.
Con quale modello Claude dovrei iniziare?
Inizia con claude-opus-4-8 mentre valuti se gli output sono abbastanza validi per costruirci sopra. In questa fase vuoi giudicare l'idea alla massima qualità disponibile e, per volumi da hobbista, la differenza di costo è di pochi centesimi. Quando il prompt è definito, ripeti l'elaborazione sugli input reali con claude-haiku-4-5. Per riepiloghi, classificazione e triage dei log, spesso offre una qualità equivalente a un quinto del prezzo. Passa a Haiku o Sonnet sulla base delle misurazioni, non per impostazione predefinita.