SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-02

Claude API-tutorial: bouw uw eerste VPS-app

Bouw op Ubuntu 24.04 een Python-loguitlegger met Claude API, streaming en getypeerde fouten. Leer uw key beveiligen en kosten beheersen per token.

Wat u bouwt

Een opdrachtregelprogramma op een nieuwe Ubuntu 24.04 VPS waaraan u een foutmelding of een fragment uit een log doorgeeft en dat een diagnose in eenvoudig Engels retourneert: journalctl -u nginx -n 50 | explain. Het programma bestaat uit ongeveer zestig regels Python. Het behandelt alle onderdelen die een echte Claude API-toepassing nodig heeft: een correct opgeslagen key, een virtualenv, de response-objectstructuren van de SDK, streaming, de getypeerde exception-keten en een systemd-unit, zodat het zonder uw tussenkomst draait.

Ik heb dit project bewust gekozen. Bij de meeste tutorials voor een "eerste API-toepassing" bouwt u een chatbot die u daarna nooit meer opent. Een loguitlegger is vanaf de eerste dag nuttig op een server. Daarnaast dwingt het u om de twee zaken goed te doen waar beginners in de praktijk vaak fouten mee maken: het response-object correct lezen en de kosten beheersen. De API brengt kosten per token in rekening, zonder andere bovengrens dan de limieten die u zelf instelt. Kostenbeheersing is hier daarom onderdeel van het ontwerp en geen maatregel achteraf. Het gaat om dezelfde discipline die belangrijk wordt wanneer u overstapt op Claude Code uitvoeren op deze VPS in tmux.

Een API-sleutel ophalen in de Console

API-toegang wordt beheerd in de Anthropic Console op platform.claude.com. Meld u aan en maak daarna een sleutel aan via Settings → API Keys (de documentatie verwijst rechtstreeks naar platform.claude.com/settings/keys). De sleutel wordt eenmalig weergegeven, begint met sk-ant- en kan daarna niet opnieuw worden opgehaald. Kopieer de sleutel daarom direct, of verwijder deze en maak een nieuwe aan.

Wat de kosten betreft: sinds juli 2026 is er geen doorlopend gratis niveau voor de API. In de prijsdocumentatie van Anthropic staat dat nieuwe gebruikers een kleine hoeveelheid gratis tegoed ontvangen om de API te testen. De exacte hoeveelheid is de waarde die de Console tijdens het aanmelden toont. Zodra het tegoed op is, moet u het account van tegoed voorzien voordat aanvragen slagen. Dit staat los van een claude.ai-abonnement. Een Pro- of Max-abonnement bevat geen API-tegoed, en met een API-sleutel krijgt u geen toegang tot de chatapp. Als u een abonnement met de API vergelijkt, is die afweging een afzonderlijk onderwerp: welk Claude-abonnement u daadwerkelijk nodig hebt.

Maak de sleutel aan voor één project of server. Als een sleutel uitlekt, gebeurt dat op termijn bij elke sleutel. U wilt deze dan kunnen intrekken zonder al uw andere resources te onderbreken.

Houd de sleutel uit .bashrc

De voor de hand liggende stap is export ANTHROPIC_API_KEY=sk-ant-... in ~/.bashrc. Doe dat niet. Er zijn drie afzonderlijke problemen:

  • Elk proces erft de variabele. Een omgevingsvariabele die in uw aanmeldshell wordt geëxporteerd, wordt doorgegeven aan alles wat u start: de webapplicatie, de crashreporter die behulpzaam de omgeving in een bugrapport dumpt, en de phpinfo()-pagina die iemand ingeschakeld heeft laten staan. Het blootstellingsoppervlak van de sleutel wordt daarmee "alles wat deze gebruiker ooit uitvoert".
  • Als u de sleutel invoert, komt deze in ~/.bash_history terecht. Voer de exportopdracht eenmaal handmatig uit en uw sleutel staat permanent in een tekstbestand. Het bestand wordt bovendien opgenomen in elke back-up van uw home-directory.
  • De sleutel is niet beschikbaar wanneer systemd deze nodig heeft. Services lezen uw .bashrc niet. Dit patroon werkt dus niet meer zodra u het script omzet in een unit. Meestal ziet u dan om 6 uur 's ochtends een onverklaarde 401.

Het juiste patroon op een server is een speciaal omgevingsbestand met 600-machtigingen. Alleen het proces dat de sleutel nodig heeft, laadt dit bestand:

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 vanuit een printf-opdracht in plaats van een editor als u wilt voorkomen dat de sleutel in tijdelijke editorbestanden terechtkomt. Controleer in beide gevallen met ls -l /etc/claude-explain.env of het bestand -rw------- bevat en eigendom is van root. Interactieve shells krijgen de sleutel per aanroep via een wrapper (hieronder). systemd krijgt de sleutel via EnvironmentFile=. root leest het bestand voordat het proces zijn privileges verlaagt. De servicegebruiker hoeft daardoor geen leestoegang tot het bestand te hebben. De sleutel verschijnt nooit in code, git, ps-uitvoer of de shellgeschiedenis.

De SDK in een venv installeren

Ubuntu 24.04 levert Python 3.12 met PEP 668-afdwinging. Daarom mislukt een directe pip install anthropic tegen de systeeminterpreter met error: externally-managed-environment. Dit is de verwachte werking van het besturingssysteem. 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

Activeren is op een server niet nodig. Als u /opt/explain/venv/bin/python rechtstreeks aanroept, gebruikt dit altijd de pakketten van de venv.

Eerste aanroep en de respons correct lezen

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 het belangrijkste deel van het API-model. Ten eerste leest anthropic.Anthropic() zonder argumenten de sleutel uit de omgeving. Geef de sleutel nooit door als letterlijke tekenreeks. Ten tweede is response.content een lijst met inhoudsblokken, geen tekenreeks. Als u deze rechtstreeks afdrukt, krijgt u de klassieke uitvoer voor beginners:

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

Dat is geen fout. Het is de repr van het object. Responsen kunnen meerdere bloktypen bevatten, zoals tekst, toolaanroepen en denkstappen. Loop daarom door de blokken en controleer block.type == "text" voordat u .text gebruikt. Neem deze lus vanaf de eerste dag op. Daarmee voorkomt u een hele categorie verwarring over uitvoer die er onjuist uitziet.

Gebruik exact de model-ID claude-opus-4-8. ID's van de huidige generatie bevatten geen datum. Weersta daarom de gewoonte, of de aanwijzing in een oud blogbericht, om een datumachtervoegsel toe te voegen. Dat veroorzaakt een 404, zoals hieronder wordt beschreven.

De daadwerkelijke tool: explain

Hier volgt het volledige programma. Het leest invoer van stdin, geeft de diagnose gestreamd weer en handelt fouten af:

#!/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 het op als /opt/explain/explain.py en voeg vervolgens 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 via sudo worden uitgevoerd, of het env-bestand moet een groep hebben waarvan uw beheerdersaccount lid is. Kies dit bewust in plaats van de bestandsrechten te versoepelen naar 644.)

Waarom streamen. client.messages.stream drukt tokens af zodra deze binnenkomen in plaats van gedurende de volledige generatie niets weer te geven. Ook worden HTTP-time-outs bij lange uitvoer voorkomen. De SDK weigert om precies die reden zeer grote waarden van max_tokens bij niet-gestreamde aanroepen. Als u het samengestelde object daarna nodig hebt, roept u stream.get_final_message() aan binnen het blok with.

Waarom deze volgorde van uitzonderingen. De SDK genereert getypeerde uitzonderingen, van specifiek naar algemeen: RateLimitError is een 429-fout en bevat een retry-after-header die aangeeft hoelang u moet wachten; APIStatusError betreft andere niet-2xx-responsen (controleer e.status_code >= 500 op problemen aan de serverzijde); APIConnectionError betekent dat de aanvraag helemaal geen antwoord heeft ontvangen. Voordat u een retry-lus bouwt: de SDK probeert 429- en 5xx-fouten zelf al opnieuw, standaard tweemaal met exponentiële wachttijd (max_retries op de client). Tegen de tijd dat uw except wordt uitgevoerd, zijn de nieuwe pogingen verbruikt. In een CLI rapporteert u de fout daarom en sluit u af, in plaats van te wachten en de server opnieuw te belasten.

Kostenbeheersing

Dit verdient een eigen sectie, omdat de API geen ingebouwde maandelijkse limiet heeft boven wat u configureert. Elke fout werkt hier bovendien stil door.

max_tokens is uw bestedingslimiet per aanroep. Uitvoertokens zijn de dure component. Bij Opus 4.8 kosten ze vijf keer zoveel als invoertokens. max_tokens stelt een harde limiet in voor het aantal tokens dat het model mag genereren. Een ontspoorde prompt kan daardoor niet meer uitvoer kosten dan u hebt toegestaan. Stem de limiet af op de taak: 1,500 is ruim voldoende voor een logdiagnose; voor een classificatietaak zijn 100 tokens nodig. Als antwoorden halverwege een zin stoppen met stop_reason: "max_tokens", hebt u de limiet te laag ingesteld. Verhoog deze bewust in plaats van standaard een zeer hoge waarde te gebruiken.

Tel voordat u verzendt. Ook invoer kost geld en logs bevatten veel gegevens. De API heeft een gratis counting-endpoint. Dit endpoint heeft eigen rate limits, die losstaan van het maken 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 niet tiktoken. Dat is de tokenizer van OpenAI en deze telt bij normale tekst ongeveer 15–20% minder Claude-tokens. Bij code is het verschil groter.

Kies het model per taak, niet uit loyaliteit. In juli 2026 kost Opus 4.8 (claude-opus-4-8) $5 per miljoen invoertokens en $25 per miljoen uitvoertokens. Haiku 4.5 (claude-haiku-4-5) kost $1/$5 en heeft een context van 200K. Sonnet 5 (claude-sonnet-5) zit daartussen met $3/$15. Tot en met August 31, 2026 geldt hiervoor een introductietarief van $2/$10. Concreet: een logfragment 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 uitvoerkwaliteit beoordeelt. Test daarna dezelfde prompts op Haiku. Voor eenvoudige transformaties met een hoog volume is het resultaat vaak niet te onderscheiden, terwijl de prijs een vijfde bedraagt. Controleer de actuele bedragen op de pricing page voordat u deze waarden hard-codeert in een budget.

Gebruik batches voor alles wat kan wachten. De Batches API verwerkt aanvragen asynchroon tegen 50% van de standaardprijzen. De meeste batches zijn binnen een uur voltooid. Nachtelijke samenvattingen, backfills en bulkclassificatie horen daar thuis, net als alles waarbij niemand op het resultaat hoeft te wachten.

Gebruik prompt caching voor herhaalde context. Als elke aanroep dezelfde grote systeemprompt of runbook opnieuw verzendt, markeert u deze 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

Het schrijven naar de cache kost ongeveer 1.25x de invoerprijs. Het lezen uit de cache kost ongeveer 0.1x. Dit geldt bij een TTL van 5 minuten. De tweede aanroep binnen dit venster compenseert daardoor al voor de eerste. Er zijn twee aandachtspunten. Het gecachete prefix moet de modelspecifieke minimumlengte overschrijden. Op Opus gaat het om enkele duizenden tokens. Een korte systeemprompt wordt daarom stilzwijgend helemaal niet in de cache opgeslagen. Als cache_read_input_tokens bij identieke aanroepen op nul blijft staan, verandert er bij elke aanvraag iets in uw prefix. Een timestamp is meestal de oorzaak.

Houd bij wat als invoer telt. Systeemprompts, tooldefinities en in gesprekken met meerdere beurten de volledige geschiedenis die u bij elke beurt opnieuw verzendt, worden allemaal als invoertokens in rekening gebracht. Een chatlus die de geschiedenis nooit inkort, verhoogt de kosten kwadratisch. Het is belangrijk de volledige kostenberekening te begrijpen voordat u iets conversatiegericht bouwt: hoe Claude-tokengebruik en facturering werkelijk worden berekend.

Uitvoeren onder systemd

Het voordeel van het werken met een omgevingsbestand: elke ochtend een timer die 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= u oplevert: systemd leest het bestand dat eigendom is van root en modus 600 heeft voordat het overschakelt naar de onbevoegde gebruiker explain. Het proces krijgt de variabele daardoor wel, terwijl de gebruiker het sleutelbestand niet kan lezen. De groep systemd-journal verleent toegang tot de logbestanden. Test 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 groot wordt voor een shell-pipeline, kunt u dezelfde aanpak met een sleutel in een omgevingsbestand rechtstreeks gebruiken in Claude-aangestuurde n8n-workflows op dezelfde server.

Foutmodi, met de tekenreeksen die u ziet

401 met een werkende sleutel. 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 sleutel in uw shell werkt, maar de service een 401 retourneert, heeft de service de sleutel nooit ontvangen. Houd er rekening mee dat systemd .bashrc niet leest. Controleer of EnvironmentFile= naar het juiste pad verwijst. Andere oorzaken zijn aanhalingstekens die in het env-bestand zijn geplakt (bij ANTHROPIC_API_KEY="sk-ant-..." houdt systemd de aanhalingstekens buiten de waarde, maar de . file van uw shell-wrapper houdt ze in de waarde als u afwijkende aanhalingstekens hebt gebruikt), afsluitende witruimte of een sleutel die u vorige week in de Console hebt ingetrokken.

404 door een typefout in de modelnaam. De meest voorkomende variant hiervan is dat u een datumsuffix toevoegt aan een actuele 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'}

ID's van de huidige generatie zijn exact zoals geschreven: claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Kopieer ze uit de documentatie van de modellen, en gebruik nooit uw geheugen of een oude tutorial.

429 rate_limit_error. De tekenreeks voor het fouttype is rate_limit_error. De response bevat een header retry-after 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 dus dat uw aanhoudende snelheid werkelijk hoger is dan uw tier toestaat. Verwerk het werk in batches of spreid het. Maak de retry-lus niet korter.

Het object wordt afgedrukt in plaats van de tekst. De uitvoer ziet eruit als [TextBlock(citations=None, text='...', type='text')]. U hebt response.content afgedrukt in plaats van de blokken te doorlopen en .text te lezen uit de blokken waarvoor block.type == "text" geldt. Elk SDK-voorbeeld hierboven doet dit correct. Kopieer de lus.

error: externally-managed-environment. U hebt pip install uitgevoerd met de systeem-Python van Ubuntu 24.04. Gebruik de venv. Gebruik nooit --break-system-packages op een server die u belangrijk vindt.

Afgekapt antwoord. response.stop_reason == "max_tokens" betekent dat het model midden in zijn redenering de uitvoerlimiet heeft bereikt. Dit werkt zoals ontworpen. Verhoog de limiet bewust.

Zodra uw eerste app werkt, verandert een AI-agent bouwen met Claude dezelfde API-aanroepen in een agent die tools gebruikt.

FAQ

Wat kost het om de Claude API uit te proberen?

Voor een tool als deze zijn de kosten echt laag. Per juli 2026 kost Opus 4.8 $5 per miljoen invoertokens en $25 per miljoen uitvoertokens. Een typische loganalyse, met een paar duizend tokens invoer en enkele honderden uitvoertokens, kost daardoor ongeveer twee cent. Met Haiku 4.5 ($1/$5) is dat minder dan een halve cent. Een maand met dagelijkse samenvattingen kost minder dan een kop koffie. Het risico zit niet in de prijs per aanroep, maar in onbegrensde lussen en onbegrensde max_tokens. Daarom worden beide in deze handleiding expliciet ingesteld.

Is er een gratis gebruiksniveau voor de Claude API?

Nee, per juli 2026 is er geen doorlopend gratis gebruiksniveau. Volgens de prijsdocumentatie van Anthropic krijgen nieuwe gebruikers een kleine hoeveelheid gratis tegoed om de API te testen. Dit is een eenmalige proefperiode. Het exacte bedrag wordt bij het aanmelden in de Console weergegeven. Daarna moet u het account financieren. Als u geen marginale kosten per aanvraag wilt, in plaats van de hoogste modelkwaliteit, kunt u een model met open gewichten zelf hosten met Ollama en in RAM betalen in plaats van in tokens.

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

Sla deze nooit op in code of git en exporteer deze nooit vanuit .bashrc. Typ de sleutel ook nooit in een shell waarvan de geschiedenis deze bewaart. Plaats de sleutel in een bestand dat eigendom is van root en 600-machtigingen heeft. Laad de sleutel per proces: met een wrapper-script voor interactief gebruik en met EnvironmentFile= voor systemd. Gebruik per server of project één afzonderlijke sleutel, zodat u een uitgelekte sleutel gericht kunt intrekken. Als de sleutel ooit op een site voor het plakken van tekst of in een git-commit terechtkomt, trekt u deze onmiddellijk in via de Console. Door de commit te verwijderen, wordt de sleutel niet opnieuw geheim.

Met welk Claude-model kan ik het beste beginnen?

Begin met claude-opus-4-8 wanneer u beoordeelt of de uitvoer goed genoeg is om erop voort te bouwen. U wilt het idee eerst met de hoogste kwaliteit beoordelen. Bij hobbygebruik is het kostenverschil enkele centen. Zodra de prompt is vastgesteld, voert u uw echte invoer opnieuw uit met claude-haiku-4-5. Voor samenvattingen, classificatie en logtriage is dit model vaak even goed voor een vijfde van de prijs. Stap op basis van metingen over naar Haiku of Sonnet, niet standaard.