Tutorial Claude API su VPS Ubuntu 24.04
Guida pratica per creare un log-explainer in Python. Impara a gestire lo streaming, le eccezioni tipizzate e il controllo dei costi su un VPS Ubuntu 24.04.
Cosa stai costruendo
Uno strumento a riga di comando su un nuovo VPS Ubuntu 24.04. Inviando un messaggio di errore o un frammento di log tramite pipe, riceverai una diagnosi in linguaggio naturale: journalctl -u nginx -n 50 | explain. Il codice consiste in circa sessanta righe di Python e implementa tutti i requisiti di un'applicazione reale basata su Claude API: una chiave memorizzata correttamente, un virtualenv, la struttura delle risposte dell'SDK, lo streaming, la catena delle eccezioni tipizzate e un'unità systemd per l'esecuzione in background.
Ho scelto questo progetto intenzionalmente. La maggior parte dei tutorial per "prime applicazioni API" richiede la creazione di un chatbot che non verrà mai riaperto. Un analizzatore di log è utile su un server fin dal primo giorno e ti costringe a gestire i due errori tipici dei principianti: la lettura corretta dell'oggetto response e il controllo della spesa. L'API fattura per token senza limiti, a meno che non vengano impostati manualmente; pertanto, il controllo dei costi è un requisito di progettazione, non un dettaglio secondario — la stessa disciplina necessaria quando passerai a eseguire Claude Code su questo stesso VPS in tmux.
Ottenere una API key dalla Console
L'accesso API è gestito nella Anthropic Console su platform.claude.com — registrarsi e creare una chiave sotto Settings → API Keys (il link della 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 — copiarla immediatamente o eliminarla e rigenerarla.
Informazioni sui costi: a partire da 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 indicato dalla Console durante la registrazione; una volta esauriti i crediti, è necessario ricaricare il conto per far sì che le richieste vadano a buon fine. Questo servizio è separato dall'abbonamento a claude.ai — un piano Pro o Max non include crediti API, e una API key non fornisce l'accesso all'app di chat. Se si deve scegliere tra abbonamento e API, l'argomento è trattato separatamente: quale piano Claude è necessario effettivamente.
Creare la chiave con lo scope limitato a un singolo progetto o server. In caso di compromissione di una chiave — evento che accadrà inevitabilmente nel tempo — è necessario poterla revocare senza interrompere il funzionamento di tutti gli altri servizi.
Non inserire la chiave in .bashrc
L'approccio riflessivo è export ANTHROPIC_API_KEY=sk-ant-... in ~/.bashrc. Da evitare. Si verificano tre problemi distinti:
- Ogni processo la eredita. Una variabile d'ambiente esportata nella shell di login si propaga a tutto ciò che viene avviato: l'app web, il crash reporter che include l'ambiente nei bug report, o la pagina
phpinfo()lasciata attiva. La superficie di esposizione della chiave diventa "tutto ciò che questo utente esegue". - L'inserimento manuale la salva in
~/.bash_history. Se esegui l'export manualmente, la chiave viene salvata in un file in chiaro per sempre e viene sincronizzata in tutti i backup della home directory. - Non è disponibile quando serve a systemd. I servizi non leggono
.bashrc, quindi il pattern fallisce quando si trasforma lo script in una unit — solitamente con un errore 401 alle 6:00.
Il pattern corretto su un server consiste nell'utilizzare un file d'ambiente dedicato con permessi 600, caricato solo dal processo necessario:
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 tramite un printf invece di un editor se vuoi evitare che la chiave finisca nei file swap dell'editor; in ogni caso, verifica con ls -l /etc/claude-explain.env che il file legga -rw------- e sia di proprietà di root. Le shell interattive ottengono la chiave tramite un wrapper (sotto), mentre systemd la ottiene tramite EnvironmentFile= — root legge il file prima di ridurre i privilegi, quindi l'utente del servizio non necessita mai dell'accesso in lettura. La chiave non appare mai nel codice, in git, nell'output di ps o nella cronologia della shell.
Install the SDK in un venv
Ubuntu 24.04 include Python 3.12 con l'applicazione di PEP 668. L'esecuzione di pip install anthropic contro l'interprete di sistema fallisce con error: externally-managed-environment. Questo errore indica che il sistema operativo funziona correttamente. Utilizzare 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 anthropicNon è necessaria l'attivazione su un server: chiamare direttamente /opt/explain/venv/bin/python utilizza 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)Due elementi in queste dodici righe definiscono il modello logico 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 stampi direttamente, otterrai il classico output tipico di chi inizia:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Non si tratta di un bug; è la repr dell'oggetto. Le risposte possono contenere diversi tipi di blocchi (testo, tool calls, thinking), quindi è necessario iterare e controllare block.type == "text" prima di accedere a .text. Implementa questo ciclo fin da subito per evitare l'errore comune di ricevere output illeggibili.
Usa l'ID modello esatto claude-opus-4-8. Gli ID di generazione attuale non includono date — non seguire l'abitudine (o i vecchi post sul blog) di aggiungere un suffisso con la data; questo genera un errore 404, spiegato di seguito.
Lo strumento effettivo: spiegazione
Ecco il programma completo — input da stdin, diagnosi in streaming in output, gestione degli errori inclusa:
#!/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())Salva il file come /opt/explain/explain.py, quindi aggiungi un wrapper che carica 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 di ambiente deve appartenere a un gruppo di cui fa parte l'utente admin — scegli una delle due opzioni invece di impostare i permessi del file a 644.)
Perché lo streaming. client.messages.stream stampa i token man mano che arrivano invece di attendere la fine della generazione completa. Questo evita i timeout HTTP su output lunghi — l'SDK rifiuta i valori max_tokens molto grandi nelle chiamate non in streaming proprio per questo motivo. Se è necessario l'oggetto completo in seguito, chiama stream.get_final_message() all'interno del blocco with.
Perché quell'ordine di eccezioni. L'SDK solleva eccezioni tipizzate, dalla più specifica alla più generica: RateLimitError indica un errore 429 e include un header retry-after che indica il tempo di attesa; APIStatusError copre altre risposte non 2xx (controlla e.status_code >= 500 per problemi lato server); APIConnectionError indica che la richiesta non ha ricevuto alcuna risposta. Prima di implementare un ciclo di retry: l'SDK gestisce già autonomamente i retry per gli errori 429 e 5xx, due volte per impostazione predefinita con exponential backoff (max_retries sul client). Quando il tuo except viene eseguito, i tentativi di retry sono esauriti — la scelta corretta in una CLI è segnalare l'errore ed uscire, non attendere e ripetere la chiamata.
Cost control
Questa sezione è separata perché l'API non ha un limite mensile predefinito oltre a quello configurato dall'utente; ogni errore si accumula silenziosamente.
max_tokens è il limite di spesa per singola chiamata. I token di output sono la voce di costo maggiore — su Opus 4.8 costano cinque volte il prezzo dell'input — e max_tokens è il limite massimo di token che il modello può generare. Un prompt errato non può generare più output di quanto consentito. Imposta il valore in base al compito: 1,500 è sufficiente per la diagnosi di un log; un compito di classificazione richiede 100. Se le risposte si interrompono a metà frase con stop_reason: "max_tokens", il limite è troppo basso — aumentalo intenzionalmente invece di impostare valori enormi di default.
Conta prima di inviare. Anche l'input ha un costo e i log sono voluminosi. L'API dispone di un endpoint di conteggio gratuito (ha i propri limiti di frequenza, separati dalla creazione di 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 inviare accidentalmente un log da 2 GB tramite lo strumento. Non usare tiktoken per questo scopo — è il tokenizer di OpenAI e sottostima i token di Claude di circa il 15–20% su testo tipico, e di più sul codice.
Scegli il modello in base al compito, non per fedeltà. A partire da luglio 2026, Opus 4.8 (claude-opus-4-8) costa $5 per milione di token di input e $25 per milione di output; Haiku 4.5 (claude-haiku-4-5) costa $1/$5 con un contesto di 200K; Sonnet 5 (claude-sonnet-5) si posiziona a metà con $3/$15, con prezzi introduttivi di $2/$10 fino al 31 agosto 2026. Nello specifico: 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 ad alto volume, il risultato è spesso indistinguibile al quinto del prezzo. Verifica i valori attuali sulla pagina dei prezzi prima di includerli in un budget fisso.
Usa i Batch per tutto ciò che può attendere. La Batches API elabora le richieste in modo asincrono al 50% dei prezzi standard; la maggior parte dei batch viene completata entro un'ora. Riassunti notturni, backfill, classificazione massiva — qualsiasi processo che non richieda l'intervento umano immediato deve usare i batch.
Usa il prompt caching per il contesto ripetuto. Se ogni chiamata invia nuovamente lo stesso sistema prompt o runbook voluminoso, impostalo come cacheable:
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 onLe scritture in cache costano circa 1.25x il prezzo dell'input, le letture in cache circa 0.1x, con un TTL di 5 minuti — quindi la seconda chiamata entro la finestra temporale compensa già la prima. Due avvertenze. Il prefisso in cache deve superare un minimo per modello — alcuni migliaia di token su Opus — quindi un system prompt breve non verrà messo in cache. Se cache_read_input_tokens rimane a zero nonostante chiamate identiche, qualcosa nel prefisso cambia ad ogni richiesta (il colpevole tipico è un timestamp).
Ricorda cosa viene conteggiato come input. System prompt, definizioni degli strumenti e — nelle conversazioni multi-turno — l'intera cronologia inviata ad ogni turno sono tutti fatturati come token di input. Un ciclo di chat che non riduce mai la cronologia aumenta i costi in modo quadratico. È necessario comprendere il calcolo completo prima di sviluppare sistemi conversazionali: come si sommano effettivamente l'uso dei token e la fatturazione di Claude.
Eseguilo sotto systemd
Il vantaggio dell'uso di un environment-file è un timer che riassume gli errori di ieri ogni mattina.
# /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 nowNota il vantaggio di EnvironmentFile=: systemd legge il file con permessi root e mode-600 prima di passare all'utente non privilegiato explain, quindi il processo riceve la variabile mentre l'utente non può leggere il file delle chiavi. Il gruppo systemd-journal concede l'accesso ai log. Testa con un systemctl start manuale e leggi journalctl -u log-digest.service — non aspettare le 06:15 per trovare un errore di battitura. Quando questo schema diventa troppo complesso per una pipeline shell, lo stesso approccio key-in-env-file può essere utilizzato direttamente in workflow n8n basati su Claude sulla stessa macchina.
Modalità di errore e stringhe visualizzate
401 con una chiave valida. L'eccezione riporta:
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: virgolette incollate nel file env (ANTHROPIC_API_KEY="sk-ant-..." — systemd rimuove le virgolette, ma il . file del tuo wrapper della shell le mantiene nel valore se sono state formattate in modo errato), spazi vuoti alla fine della riga, o una chiave revocata nella Console la settimana scorsa.
404 dovuto a un errore di battitura nel modello. La versione più comune è l'aggiunta di un suffisso della data a un ID modello attuale:
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 della generazione attuale sono esatti così come scritti — claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Copiali dalla documentazione dei modelli, mai a memoria o da un vecchio tutorial.
429 rate_limit_error. La stringa del tipo di errore è rate_limit_error e la risposta contiene un header retry-after con i secondi di attesa. L'SDK ha già effettuato due tentativi con backoff prima che l'eccezione venga visualizzata; i 429 persistenti indicano che il tasso di richieste sostenuto supera effettivamente il tuo tier — raggruppa il lavoro in batch o distribuiscilo nel tempo, non restringere il ciclo di retry.
Stampa l'oggetto, non il testo. L'output appare come [TextBlock(citations=None, text='...', type='text')]. Hai stampato response.content invece di iterare i blocchi e leggere .text da quelli in cui block.type == "text". Tutti gli esempi SDK sopra riportati lo eseguono correttamente; copia il ciclo.
error: externally-managed-environment. Hai eseguito pip install contro il Python di sistema di Ubuntu 24.04. Usa il venv — non usare mai --break-system-packages su un server critico.
Risposte troncate. response.stop_reason == "max_tokens" significa che il modello ha raggiunto il limite di output durante l'elaborazione. Il comportamento è previsto dal design; aumenta deliberatamente il limite.
Una volta che la tua prima app è funzionante, creare un agente AI con Claude trasforma quelle stesse chiamate API in un agente che utilizza tool.
FAQ
Quanto costa provare la Claude API?
Il costo è molto basso per uno strumento di questo tipo. A luglio 2026, Opus 4.8 costa $5 per un milione di input token e $25 per un milione di output token. Una diagnosi tipica dei log — circa un paio di migliaia di token in ingresso e poche centinaia in uscita — costa circa due centesimi. Con Haiku 4.5 ($1/$5) il costo è inferiore a mezzo centesimo. Un mese di digest giornalieri costa meno di un caffè. Il rischio non è il prezzo per singola chiamata; il rischio è causato da loop infiniti e max_tokens illimitati, motivo per cui entrambi i parametri vengono impostati esplicitamente in questa guida.
Esiste un piano gratuito per la Claude API?
Non esiste un piano gratuito ricorrente a luglio 2026. La documentazione dei prezzi di Anthropic indica che i nuovi utenti ricevono una piccola quantità di crediti gratuiti per testare la API — un trial una tantum, con l'importo esatto visualizzato nella Console durante la registrazione — dopo il quale è necessario ricaricare l'account. Se l'obiettivo è avere un costo marginale zero per richiesta invece della massima qualità, l'alternativa è self-host un modello open-weight con Ollama e pagare in RAM invece che in token.
Come posso proteggere la mia API key su un server?
Mai nel codice, mai in git, mai esportata da .bashrc, mai digitata in una shell dove la cronologia la salverebbe. Inseriscila in un file di proprietà root con permessi 600. Caricala per singolo processo — uno script wrapper per l'uso interattivo, EnvironmentFile= per systemd — e limita una chiave per ogni server o progetto, in modo che la revoca di una chiave compromessa sia un'operazione chirurgica e non un'amputazione. Se la chiave viene pubblicata su un sito di paste o in un commit git, revocala immediatamente nella Console; eliminare il commit non rimuove la compromissione.
Con quale modello Claude dovrei iniziare?
Inizia con claude-opus-4-8 mentre valuti se gli output sono sufficientemente validi per lo sviluppo — è necessario giudicare l'idea con la massima qualità e, per volumi amatoriali, la differenza di costo è di pochi centesimi. Una volta stabilito il prompt, esegui i tuoi input reali su claude-haiku-4-5; per riassunti, classificazione e triage dei log, è spesso efficace quanto i modelli superiori a un quinto del prezzo. Passa a Haiku o Sonnet basandoti sulle misurazioni, non per impostazione predefinita.