Claude API ile VPS üzerinde uygulama yapımı
Ubuntu 24.04 üzerinde Claude API kullanarak Python ile log açıklayıcı geliştirin. Streaming, hata yönetimi ve maliyet kontrolü ile ilk uygulamanızı kurun.
Ne inşa ediyorsunuz
Yeni bir Ubuntu 24.04 VPS üzerinde, bir hata mesajını veya bir log parçasını yönlendirdiğinizde size düz bir dille teşhis sunan bir komut satırı aracı: journalctl -u nginx -n 50 | explain. Bu araç yaklaşık altmış satır Python kodundan oluşmaktadır. Gerçek bir Claude API uygulamasının ihtiyaç duyduğu tüm bileşenleri içerir: düzgün şekilde saklanan bir anahtar, bir virtualenv, SDK yanıt yapıları, streaming, tiplendirilmiş exception zinciri ve arka planda çalışması için bir systemd unit.
Bu projeyi bilinçli olarak seçtim. Çoğu "ilk API uygulaması" eğitimi, bir daha asla açmayacağınız bir chatbot yapmanızı gerektirir. Bir log açıklayıcı, ilk günden itibaren bir sunucuda işlevsel hale gelir. Ayrıca yeni başlayanların genellikle hata yaptığı iki konuyu zorunlu kılar: yanıt nesnesini doğru okumak ve maliyeti kontrol etmek. API, sizin belirlediğiniz limitler dışında token başına ücretlendirme yapar; bu nedenle maliyet kontrolü bir sonradan ekleme değil, bir tasarım girdisidir. Bu disiplin, aynı VPS üzerinde Claude Code'u tmux ile çalıştırmaya geçtiğinizde de önem taşır.
Console üzerinden bir API key alın
API erişimi platform.claude.com adresindeki Anthropic Console üzerinden yönetilir. Kayıt olun ve ardından Settings → API Keys bölümünden bir anahtar oluşturun (dokümantasyon doğrudan platform.claude.com/settings/keys adresine yönlendirir). Anahtar yalnızca bir kez gösterilir, sk-ant- ile başlar ve tekrar görüntülenemez; anahtarı hemen kopyalayın veya silip yeniden oluşturun.
Ücretlendirme hakkında: Temmuz 2026 itibarıyla API için devam eden bir ücretsiz katman bulunmamaktadır. Anthropic'in fiyatlandırma dokümanları, yeni kullanıcıların test yapabilmeleri için küçük bir miktar ücretsiz kredi aldığını belirtmektedir; kesin miktar kayıt sırasında Console tarafından gösterilen miktardır ve kredi bittiğinde isteklerin başarılı olması için hesaba bakiye yüklenmesi gerekir. Bu durum claude.ai aboneliğinden ayrıdır; Pro veya Max planı API kredisi içermez ve bir API key sohbet uygulamasını kullanma hakkı vermez. Abonelik ile API arasında bir seçim yapıyorsanız, bu karşılaştırma ayrı bir konudur: hangi Claude planına ihtiyacınız olduğu.
Anahtarı tek bir proje veya sunucu ile sınırlandırılmış (scoped) şekilde oluşturun. Bir anahtar sızdırıldığında —ki uzun vadede bu gerçekleşecektir— diğer tüm sistemlerinizi bozmadan bu anahtarı iptal edebilmeniz gerekir.
Anahtarı .bashrc dosyasından uzak tutun
Refleksif hareket ~/.bashrc içinde export ANTHROPIC_API_KEY=sk-ant-... olarak gerçekleşir. Bunu yapmayın. Üç ayrı sorun bulunmaktadır:
- Her süreç bunu miras alır. Oturum kabuğunuzda (login shell) dışa aktarılan bir ortam değişkeni, başlattığınız her şeye yayılır — web uygulaması, hata raporuna ortam değişkenlerini ekleyen hata raporlayıcısı veya birinin etkin bıraktığı
phpinfo()sayfası. Anahtarın maruz kalma yüzeyi "bu kullanıcının çalıştırdığı her şey" haline gelir. - Yazmak,
~/.bash_historyiçine kaydedilmesine neden olur. export komutunu elle bir kez çalıştırırsanız, anahtarınız sonsuza dek düz metin bir dosyada kalır ve ev dizininizin (home directory) her yedeğine senkronize edilir. - systemd ihtiyaç duyduğunda orada değildir. Servisler
.bashrcdosyanızı okumaz; bu nedenle bu yöntem, betiği bir unit olarak yapılandırdığınızda — genellikle sabah saat 06:00'da gizemli bir 401 hatası olarak — başarısız olur.
Bir sunucu üzerindeki doğru yöntem, yalnızca ihtiyaç duyan süreç tarafından yüklenen ve 600 izinlerine sahip özel bir ortam dosyası kullanmaktır:
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/nullAnahtarın editör swap dosyalarında kalmasını istemiyorsanız, bir printf aracılığıyla tee kullanın; her iki durumda da, ls -l /etc/claude-explain.env ile -rw------- dosyasını okuduğunu ve root tarafından sahiplenildiğini doğrulayın. Etkileşimli kabuklar anahtarı bir wrapper aracılığıyla (aşağıda) her çalıştırma için alır; systemd ise anahtarı EnvironmentFile= üzerinden alır — root, yetkileri düşürmeden önce dosyayı okur, bu nedenle servis kullanıcısının dosyayı okuma yetkisine ihtiyacı kalmaz. Anahtar asla kodda, git'te, ps çıktısında veya kabuk geçmişinde (shell history) görünmez.
SDK'yı venv içine kurun
Ubuntu 24.04, PEP 668 zorunluluğu ile birlikte Python 3.12 ile gelir; bu nedenle sistem yorumlayıcısına karşı yapılan çıplak bir pip install anthropic işlemi error: externally-managed-environment hatası verir. Bu hata, işletim sisteminin beklenen çalışma biçimidir; bir virtualenv kullanın:
sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropicSunucu üzerinde aktivasyon işlemine gerek yoktur: /opt/explain/venv/bin/python komutunun doğrudan çağrılması her zaman venv paketlerini kullanır.
İlk çağrı ve yanıtın doğru okunması
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)Bu on iki satır içindeki iki unsur, API'nin çalışma mantığının temelini oluşturur. Birincisi, argüman verilmeden kullanılan anthropic.Anthropic(), anahtarı ortam değişkeninden okur; anahtarı asla bir string literal olarak iletmeyin. İkincisi, response.content bir string değil, bir içerik blokları listesidir. Doğrudan yazdırıldığında, yeni başlayanların sıkça karşılaştığı şu çıktı alınır:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Bu bir hata değildir; nesnenin repr çıktısıdır. Yanıtlar birden fazla blok türü (text, tool calls, thinking) içerebilir; bu nedenle .text içeriğine erişmeden önce döngü kurularak block.type == "text" kontrol edilmelidir. Bu döngü yapısını en baştan kurmak, "çöp veri yazdırıyor" şeklindeki karmaşayı tamamen önler.
Tam model ID'si olan claude-opus-4-8 değerini kullanın. Mevcut nesil ID'leri tarih içermez; sonuna tarih eki eklemenizi söyleyen alışkanlıklardan (veya eski blog yazılarından) kaçının; bu işlem aşağıda açıklanan 404 hatasına yol açar.
Gerçek araç: açıklama
İşte programın tamamı — stdin girişi, akış şeklinde (streamed) teşhis çıktısı ve işlenmiş hatalar:
#!/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())Dosyayı /opt/explain/explain.py olarak kaydedin, ardından etkileşimli kullanım için anahtarı yükleyen bir sarmalayıcı (wrapper) ekleyin:
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(Sarmalayıcının sudo üzerinden çalıştırılması gerekir veya env dosyasının yönetici kullanıcınızın dahil olduğu bir gruba ait olması gerekir — dosya izinlerini 644 yaparak esnetmek yerine bu iki yöntemden birini bilinçli olarak seçin.)
Neden akış (streaming). client.messages.stream, tüm üretimin tamamlanmasını beklemek yerine tokenları geldikleri anda yazdırır. Bu yöntem, uzun çıktılarda HTTP zaman aşımı (timeout) sorunlarını önler — SDK, tam olarak bu sebeple, akış olmayan çağrılarda çok büyük max_tokens değerlerini reddedecektir. Eğer sonrasında birleştirilmiş nesneye ihtiyacınız varsa, with bloğu içinde stream.get_final_message() fonksiyonunu çağırın.
Neden bu hata sıralaması. SDK, en spesifik olandan başlayarak türlendirilmiş istisnalar (exceptions) fırlatır: RateLimitError bir 429 hatasıdır ve ne kadar beklenmesi gerektiğini belirten bir retry-after başlığı içerir; APIStatusError diğer 2xx dışı yanıtları kapsar (sunucu tarafı sorunlar için e.status_code >= 500 kontrol edilmelidir); APIConnectionError ise isteğin hiçbir yanıt alamadığı anlamına gelir. Bir yeniden deneme döngüsü (retry loop) oluşturmadan önce: SDK, 429 ve 5xx hatalarını zaten kendisi yeniden dener; varsayılan olarak üstel geri çekilme (exponential backoff) ile iki kez (istemci tarafında max_retries) dener. except çalıştığı sırada yeniden denemeler tamamlanmış olur — bu nedenle bir CLI için doğru yaklaşım, beklemek ve sistemi zorlamak değil, hatayı raporlayıp çıkmaktır.
Maliyet kontrolü
API'nin yapılandırılan değerler dışında yerleşik bir aylık üst sınırı yoktur ve yapılan her hata maliyeti sessizce artırır; bu nedenle bu konu ayrı bir bölüm gerektirir.
max_tokens, çağrı başına harcama tavanınızdır. Çıktı tokenları en maliyetli kısımdır; Opus 4.8 üzerinde giriş fiyatının beş katıdır. max_tokens, modelin üretebileceği token sayısı için kesin bir sınırdır. Kontrolsüz bir istem, izin verilenden fazla çıktı maliyeti oluşturamaz. Değeri işe göre belirleyin: log diyagnostiği için 1,500 yeterlidir; sınıflandırma görevleri için 100 gereklidir. Yanıtlar stop_reason: "max_tokens" nedeniyle cümle ortasında kesiliyorsa, sınırı çok dar belirlemişsiniz demektir; varsayılan olarak çok yüksek değerler vermek yerine sınırı bilinçli olarak artırın.
Göndermeden önce sayın. Giriş tokenları da maliyet oluşturur ve log dosyaları hacimlidir. API'nin ücretsiz bir sayım uç noktası (endpoint) vardır (mesaj oluşturmadan ayrı rate limitleri vardır):
count = client.messages.count_tokens(
model="claude-opus-4-8",
messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)Bu uç noktanın, 2 GB boyutundaki bir log dosyasının yanlışlıkla araca aktarılmasını önlemek için kullanın. Bu işlem için tiktoken kullanmayın; bu OpenAI'ın tokenizer'ıdır ve tipik metinlerde Claude tokenlarını yaklaşık %15–20, kodlarda ise daha fazla eksik sayar.
Modeli sadakate göre değil, göreve göre seçin. Temmuz 2026 itibarıyla, Opus 4.8 (claude-opus-4-8) milyon giriş tokenı başına 5$, milyon çıktı tokenı başına 25$ maliyetle çalışır; Haiku 4.5 (claude-haiku-4-5) 200K bağlam ile 1$/5$ fiyatındadır; Sonnet 5 (claude-sonnet-5) ise 3$/15$ fiyatındadır ve 31 Ağustos 2026'ya kadar 2$/10$ tanıtım fiyatı uygulanmaktadır. Somut örnek: 500 tokenlık bir yanıt ile birlikte 2,000 tokenlık bir log kesiti, Opus üzerinde yaklaşık 0,0225$, Haiku üzerinde ise 0,0045$ tutar. Çıktı kalitesini değerlendirirken Opus ile başlayın, ardından aynı istemleri Haiku üzerinde deneyin; yüksek hacimli, basit dönüşümler için Haiku, fiyatın beşte biriyle genellikle ayırt edilemez sonuçlar verir. Herhangi bir değeri bütçeye sabitlemeden önce güncel rakamları fiyatlandırma sayfasından doğrulayın.
Bekleyebilecek her şey için Batches kullanın. Batches API, istekleri standart fiyatların %50'si ile asenkron olarak işler ve çoğu batch bir saat içinde tamamlanır. Gece özetleri, geçmiş verilerin doldurulması, toplu sınıflandırma gibi bir insanın beklemediği tüm işler bu yöntemle yapılmalıdır.
Tekrarlanan bağlamlar için Prompt caching kullanın. Eğer her çağrı aynı büyük sistem istemini veya kullanım kılavuzunu tekrar gönderiyorsa, bunu önbelleklenebilir olarak işaretleyin:
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Önbellek yazma maliyeti giriş fiyatının yaklaşık 1.25x'i, 5 dakikalık TTL ile önbellek okuma maliyeti ise yaklaşık 0.1x'idir; bu sayede pencere içindeki ikinci çağrı, ilkinin maliyetini zaten karşılar. İki önemli nokta vardır. Önbelleğe alınan önek (prefix), modele özgü bir minimum değeri geçmelidir (Opus üzerinde birkaç bin token); kısa bir sistem istemi sessizce hiç önbelleğe alınmayabilir. Ayrıca, özdeş çağrılarda cache_read_input_tokens değeri sıfır kalıyorsa, önekiniz her istekte değişiyor demektir (genellikle zaman damgası buna sebep olur).
Nelerin giriş olarak sayıldığını unutmayın. Sistem istemleri, araç tanımlamaları ve çok turlu konuşmalarda her turda yeniden gönderilen tüm geçmiş, giriş tokenı olarak faturalandırılır. Geçmişi hiç budamayan bir sohbet döngüsü, maliyetinin karesel olarak artmasına neden olur. Herhangi bir konuşma yapısı kurmadan önce şu konuyu anlamak önemlidir: Claude token kullanımı ve faturalandırmanın gerçekte nasıl hesaplandığı.
systemd altında çalıştırın
environment-file disiplininin sonucu: her sabah dünün hatalarını özetleyen bir zamanlayıcı.
# /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 nowEnvironmentFile='nın sağladığı avantaj şudur: systemd, root yetkili ve mode-600 modundaki dosyayı, yetkisiz explain kullanıcısına geçmeden önce okur. Böylece süreç değişkeni alır ancak kullanıcı anahtar dosyasını okuyamaz. systemd-journal grubu log erişimi sağlar. Manuel bir systemctl start ile test edin ve journalctl -u log-digest.service içeriğini okuyun; bir yazım hatası bulmak için 06:15'i beklemeyin. Bu desen bir shell pipeline yapısından daha karmaşık hale geldiğinde, aynı env-file yaklaşımı aynı makine üzerindeki Claude destekli n8n iş akışlarına doğrudan aktarılabilir.
Hata modları ve göreceğiniz dizgeler
Çalışan bir anahtar için 401 hatası. İstisna şu şekildedir:
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}Anahtar shell üzerinde çalışıyor ancak servis 401 hatası veriyorsa, servis anahtarı hiç almamış demektir; systemd'nin .bashrc okumadığını unutmayın; EnvironmentFile= kısmının doğru yolu işaret ettiğini kontrol edin. Diğer nedenler: env dosyasına yapıştırılan tırnak işaretleri (ANTHROPIC_API_KEY="sk-ant-..." — systemd tırnak işaretlerini dışarıda bırakır, ancak shell wrapper'ınızdaki . file hatalı tırnak kullanımı nedeniyle bunları değerin içinde tutar), sondaki boşluklar veya geçen hafta Console üzerinden iptal ettiğiniz bir anahtar.
Model yazım hatasından kaynaklanan 404 hatası. Bunun en yaygın versiyonu, mevcut bir model ID'sine tarih soneki eklenmesidir:
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-4-8-20260115'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}Mevcut nesil ID'leri yazıldığı gibi tamdır — claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Bunları modeller dokümantasyonundan kopyalayın; asla hafızadan veya eski bir eğitimden kopyalamayın.
429 rate_limit_error. Hata türü dizgisi rate_limit_error şeklindedir ve yanıt, bekleme süresini içeren bir retry-after başlığı taşır. SDK, siz istisnayı görmeden önce bekleme süresiyle (backoff) birlikte zaten iki kez yeniden deneme yapmıştır; bu nedenle sürekli 429 hataları, sürdürülebilir hızınızın gerçekten limitinizi aştığı anlamına gelir — işleri toplu halde (batch) yapın veya zamana yayın, yeniden deneme döngüsünü sıkılaştırmayın.
Metin yerine nesne yazdırılıyor. Çıktı [TextBlock(citations=None, text='...', type='text')] gibi görünür. Bloklar üzerinde iterasyon yapıp block.type == "text" olan bloklardan .text okumak yerine response.content yazdırdınız. Yukarıdaki tüm SDK örnekleri bunu doğru şekilde yapar; döngüyü kopyalayın.
error: externally-managed-environment. Ubuntu 24.04'ün sistem Python'ı üzerinde pip install çalıştırdınız. venv kullanın — önemli bir sunucuda asla --break-system-packages kullanmayın.
Kesilmiş yanıtlar. response.stop_reason == "max_tokens", modelin düşünme aşamasındayken çıktı sınırına ulaştığı anlamına gelir. Sistem tasarlandığı gibi çalışmaktadır; sınırı bilinçli olarak yükseltin.
İlk uygulamanız çalıştığında, Claude ile bir AI agent oluşturma bu API çağrılarını araç kullanan bir agent'a dönüştürür.
FAQ
Claude API kullanım maliyeti nedir?
Bu tür bir araç için maliyet oldukça düşüktür. Temmuz 2026 itibarıyla, Opus 4.8 için her bir milyon input token başına 5$, her bir milyon output token başına ise 25$ ücret alınmaktadır. Tipik bir log teşhisi —birkaç bin input token ve birkaç yüz output token— yaklaşık iki cent tutar; Haiku 4.5 ($1/$5) modelinde ise maliyet yarım centın altındadır. Günlük özetler içeren bir aylık kullanım, bir kahve fiyatından daha az maliyetlidir. Risk, çağrı başına maliyet değil; sınırsız döngüler ve sınırsız max_tokens durumudur. Bu nedenle bu kılavuzda her iki parametre de açıkça ayarlanmaktadır.
Claude API için ücretsiz bir katman var mı?
Temmuz 2026 itibarıyla devam eden bir ücretsiz katman bulunmamaktadır. Anthropic fiyatlandırma dokümantasyonuna göre, yeni kullanıcılar API'yi test etmek için küçük bir miktar ücretsiz kredi alırlar. Kayıt sırasında Console üzerinde gösterilen bu tek seferlik deneme süresinden sonra hesaba bakiye yüklenmelidir. Hedefiniz en üst düzey kalite yerine istek başına sıfır marjinal maliyet ise, alternatif olarak Ollama ile açık ağırlıklı bir modeli kendi sunucunuzda çalıştırmak ve token yerine RAM maliyeti ödemektir.
API anahtarımı sunucuda nasıl güvenli tutarım?
Anahtarı asla kod içerisinde, asla git içerisinde, asla .bashrc üzerinden export ederek ve geçmiş kaydı tutan shell ortamlarında yazarak kullanmayın. Anahtarı, 600 izinlerine sahip ve root yetkisindeki bir dosyaya koyun. Anahtarı işlem bazlı yükleyin; etkileşimli kullanım için bir wrapper script, EnvironmentFile= için ise systemd kullanın. Sızan bir anahtarı iptal etme işleminin bir amputasyon değil, basit bir müdahale olması için her sunucu veya proje için ayrı bir anahtar tanımlayın. Anahtar bir paste sitesine veya git commit'ine düşerse, Console üzerinden derhal iptal edin; commit'i silmek sızıntıyı gidermez.
Hangi Claude modeli ile başlamalıyım?
Çıktıların geliştirme yapmak için yeterli olup olmadığını değerlendirirken claude-opus-4-8 ile başlayın. Fikri tam kalitede değerlendirmek gerekir ve hobi düzeyindeki kullanımda maliyet farkı sadece birkaç centtir. Prompt yapısı oturduktan sonra, gerçek girdilerinizi claude-haiku-4-5 üzerinde tekrar çalıştırın; özetleme, sınıflandırma ve log ayrıştırma işlemleri için bu model, fiyatın beşte biri maliyetine benzer performans sunar. Haiku veya Sonnet modellerine varsayılan olarak değil, ölçümlere dayalı olarak geçiş yapın.