SSD Nodes Learn
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-07-24

Claude API tutorial: app bouwen op VPS

Bouw een Python log-analyser op Ubuntu 24.04. Leer streaming, typed error handling en kostenbeheersing voor uw Claude API applicatie met deze gids.

Wat u gaat bouwen

Een command-line tool op een nieuwe Ubuntu 24.04 VPS. U stuurt een foutmelding of een logfragment naar de tool en ontvangt een diagnose in begrijpelijk Engels: journalctl -u nginx -n 50 | explain. De code bestaat uit ongeveer zestig regels Python. Het project behandelt alle essentiële onderdelen van een professionele Claude API-applicatie: een correct opgeslagen API-key, een virtualenv, de response-structuren van de SDK, streaming, de typed exception chain en een systemd unit voor automatische uitvoering.

Ik heb dit project bewust gekozen. De meeste tutorials voor een "eerste API-app" laten u een chatbot bouwen die u nooit meer gebruikt. Een log-analyser is vanaf de eerste dag nuttig op een server. Bovendien dwingt het u om twee cruciale zaken onder controle te krijgen: het correct uitlezen van het response-object en het beheersen van de kosten. De API factureert per token en heeft geen limiet, behalve de limieten die u zelf instelt. Kostenbeheersing is hier een essentieel onderdeel van het ontwerp, niet een achterafje — dezelfde discipline die nodig is wanneer u Claude Code draait op deze VPS in tmux.

Haal een API-key op via de Console

API-toegang wordt beheerd in de Anthropic Console op platform.claude.com. Registreer een account en maak een sleutel aan onder Settings → API Keys (de documentatie verwijst direct naar platform.claude.com/settings/keys). De sleutel wordt slechts één keer getoond, begint met sk-ant- en kan niet opnieuw worden opgehaald. Kopieer de sleutel onmiddellijk of verwijder deze om een nieuwe aan te maken.

Over de kosten: per juli 2026 is er geen gratis abonnement meer voor de API. Volgens de prijsinformatie van Anthropic ontvangen nieuwe gebruikers een klein bedrag aan gratis credits voor tests. Het exacte bedrag wordt in de Console getoond tijdens de registratie. Zodra deze credits op zijn, moet u het account bijstorten voordat verzoeken slagen. Dit staat los van een claude.ai-abonnement; een Pro- of Max-abonnement bevat geen API-credits en een API-sleutel geeft geen toegang tot de chat-app. Als u moet kiezen tussen een abonnement of de API, dan is dat een apart onderwerp: welk Claude-abonnement u daadwerkelijk nodig heeft.

Maak de sleutel aan met een beperkte scope voor één project of server. Wanneer een sleutel uitlekt — en op de lange termijn zal dit gebeuren — wilt u deze kunnen intrekken zonder andere systemen te verstoren.

Houd de key buiten .bashrc

De reflexieve methode is export ANTHROPIC_API_KEY=sk-ant-... in ~/.bashrc. Doe dit niet. Er zijn drie afzonderlijke problemen:

  • Elk proces erft deze. Een omgevingsvariabele die u exporteert in uw login shell wordt doorgegeven aan alles wat u start — de web app, de crash reporter die de omgevingsvariabelen in een bugrapport plaatst, en de phpinfo() pagina die iemand heeft ingeschakeld. Het blootgestelde oppervlak van de key wordt "alles wat deze gebruiker ooit uitvoert."
  • Typen plaatst deze in ~/.bash_history. Voer de export handmatig één keer uit en uw key staat voor altijd in een plaintext bestand en wordt gesynchroniseerd naar elke backup van uw home directory.
  • Deze is er niet wanneer systemd dit nodig heeft. Services lezen uw .bashrc niet. Dit patroon faalt precies wanneer u het script omzet naar een unit — meestal als een mysterieuze 401 om 6 a.m.

Het juiste patroon op een server is een apart omgevingsbestand met 600 permissies, dat alleen wordt geladen door het proces dat dit nodig heeft:

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/null

Gebruik tee via een printf in plaats van een editor als u de key buiten editor swap files wilt houden. Controleer in ieder geval met ls -l /etc/claude-explain.env of het bestand -rw------- leest en eigendom is van root. Interactieve shells ontvangen de key per aanroep via een wrapper (hieronder), en systemd ontvangt deze via EnvironmentFile= — root leest het bestand voordat de privileges worden verlaagd, zodat de service user nooit leesrechten nodig heeft. De key verschijnt nooit in code, in git, in ps output, of in de shell history.

Installeer de SDK in een venv

Ubuntu 24.04 bevat Python 3.12 met PEP 668-handhaving. Een directe pip install anthropic tegen de systeeminterpreter mislukt met error: externally-managed-environment. Deze foutmelding betekent dat het besturingssysteem correct functioneert — gebruik een virtualenv:

sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropic

Op een server is activering niet nodig: het direct aanroepen van /opt/explain/venv/bin/python gebruikt altijd de pakketten van de venv.

De eerste aanroep en het correct lezen van de respons

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)

Twee zaken in deze twaalf regels vormen de kern van het conceptuele model van de API. Ten eerste leest anthropic.Anthropic() zonder argumenten de sleutel uit de omgevingsvariabelen — geef deze nooit door als een string literal. Ten tweede is response.content een lijst met content blocks, geen string. Als u dit direct print, krijgt u de typische output voor beginners:

[TextBlock(citations=None, text='A systemd unit file is...', type='text')]

Dit is geen fout; dit is de repr van het object. Responses kunnen meerdere bloktypen bevatten (text, tool calls, thinking). Itereer daarom door de lijst en controleer block.type == "text" voordat u .text verwerkt. Implementeer deze loop direct; dit voorkomt een hele categorie van verwarring waarbij de output als "onleesbaar" wordt ervaren.

Gebruik de exacte model ID claude-opus-4-8. IDs van de huidige generatie bevatten geen datum. Gebruik geen datum-suffix zoals in oudere documentatie; dit veroorzaakt een 404-fout, wat hieronder wordt beschreven.

De werkelijke tool: uitleg

Hier is het volledige programma — stdin als input, gestreamde diagnose als output, fouten worden afgehandeld:

#!/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())

Sla dit op als /opt/explain/explain.py en voeg een wrapper toe die de sleutel laadt voor interactief gebruik:

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

(De wrapper moet worden uitgevoerd via sudo of het env-bestand moet een groep bevatten waar uw admin-gebruiker deel van uitmaakt — kies bewust voor één optie in plaats van de rechten naar 644 te verruimen.)

Waarom streaming. client.messages.stream print tokens zodra ze binnenkomen in plaats van te wachten tot de volledige generatie voltooid is. Dit voorkomt HTTP-timeouts bij langdurige outputs — de SDK weigert zeer grote max_tokens waarden bij non-streaming calls om precies deze reden. Als u het volledige object achteraf nodig heeft, roep dan stream.get_final_message() aan binnen het with blok.

Waarom die volgorde van uitzonderingen. De SDK geeft getypeerde exceptions, waarbij de meest specifieke eerst komt: RateLimitError is een 429-fout en bevat een retry-after header die aangeeft hoe lang u moet wachten; APIStatusError dekt andere non-2xx responses (controleer e.status_code >= 500 voor server-side problemen); APIConnectionError betekent dat de request geen enkele respons heeft ontvangen. Voordat u een retry-loop bouwt: de SDK voert zelf al retries uit voor 429s en 5xx errors, standaard twee keer met exponential backoff (max_retries op de client). Tegen de tijd dat uw except wordt uitgevoerd, zijn de retries verbruikt — de juiste aanpak in een CLI is daarom rapporteren en afsluiten, in plaats van wachten en opnieuw proberen.

Kostenbeheersing

Dit verdient een eigen sectie omdat de API geen ingebouwde maandelijkse limiet heeft buiten uw eigen configuratie, en elke fout hier de kosten ongemerkt verhoogt.

max_tokens is uw budgetplafond per aanroep. Output tokens zijn de duurste component — bij Opus 4.8 is dit vijf keer de prijs van input — en max_tokens is een harde limiet voor het aantal tokens dat het model mag genereren. Een foutieve prompt kan nooit meer output kosten dan u heeft toegestaan. Pas de grootte aan op de taak: 1.500 is voldoende voor een log-diagnose; een classificatietaak heeft 100 nodig. Als antwoorden midden in een zin stoppen met stop_reason: "max_tokens", dan heeft u de limiet te strikt ingesteld — verhoog deze bewust in plaats van standaard een zeer hoge waarde te gebruiken.

Tel voordat u verzendt. Input kost ook geld, en logs zijn omvangrijk. De API heeft een counting endpoint dat gratis te gebruiken is (dit heeft eigen rate limits, los van het aanmaken van berichten):

count = client.messages.count_tokens(
    model="claude-opus-4-8",
    messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)

Gebruik dit om te voorkomen dat u per ongeluk een log van 2 GB door de tool stuurt. Gebruik hiervoor geen tiktoken — dit is de tokenizer van OpenAI, en deze onderschat de Claude tokens met ongeveer 15–20% bij typische tekst, en nog meer bij code.

Kies het model per taak, niet op basis van voorkeur. Sinds juli 2026 kost Opus 4.8 (claude-opus-4-8) $5 per miljoen input tokens en $25 per miljoen output; Haiku 4.5 (claude-haiku-4-5) kost $1/$5 met een context van 200K; Sonnet 5 (claude-sonnet-5) zit daar tussen met $3/$15, met een introductieprijs van $2/$10 tot en met 31 augustus 2026. Concreet: een log-fragment van 2.000 tokens met een antwoord van 500 tokens kost ongeveer $0,0225 op Opus en $0,0045 op Haiku. Begin met Opus terwijl u de kwaliteit van de output beoordeelt, en probeer vervolgens dezelfde prompts op Haiku — voor eenvoudige transformaties met een hoog volume is het resultaat vaak ononderscheidbaar tegen een vijfde van de prijs. Controleer de huidige cijfers op de pricing page voordat u dit hardcodeert in een budget.

Gebruik Batches voor alles wat kan wachten. De Batches API verwerkt verzoeken asynchroon tegen 50% van de standaardprijzen, en de meeste batches zijn binnen een uur voltooid. Dagelijkse samenvattingen, backfills, bulk classificatie — alles waarbij geen mens op antwoord wacht, hoort in Batches.

Prompt caching voor herhaalde context. Als elke aanroep dezelfde grote system prompt of runbook opnieuw verzendt, markeer deze dan als 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 on

Cache writes kosten ongeveer 1,25x de inputprijs, cache reads ongeveer 0,1x, met een TTL van 5 minuten — de tweede aanroep binnen dit venster betaalt dus al voor de eerste. Er zijn twee aandachtspunten. De gecachte prefix moet voldoen aan een minimum per model — enkele duizenden tokens op Opus — dus een korte system prompt wordt niet automatisch gecached. En als cache_read_input_tokens nul blijft bij identieke aanroepen, verandert er iets in uw prefix bij elke aanvraag (een tijdstempel is vaak de oorzaak).

Houd rekening met wat als input telt. System prompts, tool definitions, en — bij gesprekken met meerdere beurten — de volledige geschiedenis die u elke beurt opnieuw verzendt, worden allemaal gefactureerd als input tokens. Een chat-loop die de geschiedenis nooit inkort, zorgt voor een exponentiële stijging van de kosten. Het is belangrijk om de volledige berekening te begrijpen voordat u iets bouwt voor conversaties: hoe het Claude tokenverbruik en de facturering daadwerkelijk worden opgebouwd.

Uitvoeren onder systemd

Het voordeel van de environment-file methode: een timer die elke ochtend de fouten van gisteren samenvat.

# /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.target
sudo 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 now

Let op wat EnvironmentFile= oplevert: systemd leest het bestand met root-eigendom en mode-600 voordat de privileges worden verlaagd naar de onbevoegde explain gebruiker. Hierdoor krijgt het proces de variabele, terwijl de gebruiker de sleutelbestand niet kan lezen. De systemd-journal groep geeft toegang tot de logs. Test dit met een handmatige systemctl start en lees journalctl -u log-digest.service — wacht niet tot 06:15 om een typefout te ontdekken. Wanneer dit patroon te complex wordt voor een shell pipeline, kan dezelfde methode met de sleutel in een environment-file direct worden gebruikt voor Claude-gestuurde n8n workflows op dezelfde machine.

Foutmodi, met de strings die u zult zien

401 bij een werkende key. De uitzondering luidt:

anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}

Als de key werkt in uw shell maar de service een 401 geeft, heeft de service de key nooit ontvangen — houd er rekening mee dat systemd .bashrc niet leest; controleer of EnvironmentFile= naar het juiste pad wijst. Andere oorzaken: aanhalingstekens die in het env-bestand zijn geplakt (ANTHROPIC_API_KEY="sk-ant-..." — systemd verwijdert de aanhalingstekens, maar de . file van uw shell-wrapper behoudt ze in de waarde als u onjuist heeft geciteerd), spaties aan het einde van de regel, of een key die u vorige week in de Console heeft ingetrokken.

404 door een typefout in het model. De meest voorkomende variant hiervan is het toevoegen van een datum-suffix aan een huidig model ID:

anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-4-8-20260115'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}

IDs van de huidige generatie zijn exact zoals geschreven — claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Kopieer deze uit de documentatie van de modellen, nooit vanuit het geheugen of een oude tutorial.

429 rate_limit_error. De fouttype-string is rate_limit_error en de response bevat een retry-after header met het aantal seconden dat u moet wachten. De SDK heeft al twee keer opnieuw geprobeerd met backoff voordat u de uitzondering ziet; aanhoudende 429-fouten betekenen dat uw constante rate uw tier daadwerkelijk overschrijdt — groepeer de taken of spreid ze uit, pas de retry-loop niet aan om deze te versnellen.

Het print het object, niet de tekst. De output ziet eruit als [TextBlock(citations=None, text='...', type='text')]. U heeft response.content geprint in plaats van de blokken te itereren en .text te lezen uit de blokken waar block.type == "text". Elk SDK-voorbeeld hierboven doet dit correct; kopieer de loop.

error: externally-managed-environment. U heeft pip install uitgevoerd op de system Python van Ubuntu 24.04. Gebruik de venv — gebruik nooit --break-system-packages op een server die belangrijk is.

Afgekorte antwoorden. response.stop_reason == "max_tokens" betekent dat het model uw output-limiet midden in de verwerking heeft bereikt. Dit is het beoogde gedrag; verhoog de limiet bewust.

Zodra uw eerste app werkt, maakt het bouwen van een AI agent met Claude van diezelfde API-calls een agent die tools gebruikt.

FAQ

Wat zijn de kosten voor het uitproberen van de Claude API?

Zeer laag voor een tool als deze. Sinds juli 2026 kost Opus 4.8 $5 per miljoen input tokens en $25 per miljoen output. Een typische log-diagnose — enkele duizenden tokens input en enkele honderden tokens output — kost ongeveer twee cent. Bij Haiku 4.5 ($1/$5) is dit minder dan een halve cent. Een maand aan dagelijkse samenvattingen kost minder dan een kop koffie. Het risico zit niet in de prijs per aanroep, maar in onbeperkte loops en onbeperkte max_tokens. Daarom worden beide in deze handleiding expliciet ingesteld.

Is er een gratis niveau voor de Claude API?

Per juli 2026 is er geen doorlopend gratis niveau. De prijsdocumentatie van Anthropic vermeldt dat nieuwe gebruikers een klein bedrag aan gratis credits ontvangen om de API te testen. Dit is een eenmalige proefperiode; het exacte bedrag wordt in de Console getoond bij registratie. Daarna moet het account worden bijgestort. Als u een marginale kostprijs van nul per verzoek wilt in plaats van de hoogste kwaliteit, dan is het alternatief een open-weight model zelf hosten met Ollama waarbij u betaalt in RAM in plaats van tokens.

Hoe houd ik mijn API-key veilig op een server?

Nooit in code, nooit in git, nooit geëxporteerd uit .bashrc, en nooit getypt in een shell waar de geschiedenis de sleutel bewaart. Plaats de sleutel in een bestand met root-eigendom en 600 permissies. Laad de sleutel per proces: gebruik een wrapper-script voor interactief gebruik en EnvironmentFile= voor systemd. Gebruik één sleutel per server of project, zodat het intrekken van een gelekte sleutel een precisie-ingreep is in plaats van een amputatie. Als de sleutel ooit op een paste-site of in een git-commit verschijnt, trek deze dan onmiddellijk in via de Console; het verwijderen van de commit heft het lek niet op.

Met welk Claude-model moet ik beginnen?

Begin met claude-opus-4-8 terwijl u evalueert of de outputs van voldoende kwaliteit zijn om op voort te bouwen. U wilt het concept beoordelen op volledige kwaliteit; bij een hobby-volume is het kostenverschil slechts enkele centen. Zodra de prompt definitief is, voert u uw werkelijke inputs opnieuw uit op claude-haiku-4-5. Voor samenvattingen, classificatie en log-triage is dit model vaak even goed voor een vijfde van de prijs. Stap over naar Haiku of Sonnet op basis van metingen, niet op basis van standaardinstellingen.