Mert Özen Her Satırda Daha İleri
Yapay Zeka Claude API ile Uygulama Geliştirme — #01

Claude API'ye .NET'ten Başlamak: Console, API Key, Anthropic C# SDK ve İlk İstek

Mert Özen 15 Kas 2026 14 dk 4 görüntülenme
Claude API'ye .NET'ten Başlamak: Console, API Key, Anthropic C# SDK ve İlk İstek

Claude API'yi .NET'ten kullanmak için NuGet'teki resmî Anthropic paketinin 10 ve üstü sürümünü (bugün 13.8.0) kurar, key'i ANTHROPIC_API_KEY'de tutar ve AnthropicClient'ı singleton kaydedersin. Console'da workspace ve key tipleri, API version 2026-10-21 ile kaldırılan temperature/top_p/top_k, Models API ve Bookstore.Api için ClaudeCatalogService.

Sürüm notu: Bu yazıdaki kodlar .NET 10 (SDK 10.0.404), resmî Anthropic C# SDK'sı (NuGet Anthropic 13.8.0) ve Claude API version 2026-10-21 ile derlenmiş, birim testleri çalıştırılmıştır. Console adımları, model adları ve API davranışı 15 Kasım 2026'da platform.claude.com belgelerinden ve SDK'nın GitHub deposundan (github.com/anthropics/anthropic-sdk-csharp) kontrol edilmiştir. Bu yazıda resmî belgelerden yararlanılmıştır.

Önce Tahmin Et

Emre, sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için kitap sayfalarına iki cümlelik özet eklemek istiyor. NuGet'te "Anthropic" diye aratıyor, en üstte çıkan paketi kuruyor, internetten bulduğu bir örneği kopyalıyor ve Temperature = 0.2 satırını da "tutarlı cevap için" ekliyor. Proje derlenmiyor. Paketin sürümünü düşürüyor, bu kez derleniyor ama ilk istekte API 400 dönüyor.

Tahminin ne? Sorun paketin kendisinde mi, kopyalanan örnekte mi, yoksa temperature satırında mı? Bu yazının sonunda doğru paketi, ilk isteği, API version'ın neden önemli olduğunu ve Claude client'ını ASP.NET Core'a nasıl kaydedeceğini görmüş olacaksın. Sağlayıcıdan bağımsız temel kavramları (token, sampling, system prompt) LLM Temelleri serisinde, IChatClient üzerinden genel kullanımı .NET ile Yapay Zeka serisinde anlattık. Bu seri doğrudan Claude API'nin kendi yüzeyine odaklanıyor.

Console'da Hazırlık: Organization, Workspace ve API Key

Claude API'nin yönetim yeri Claude Console (platform.claude.com). Hesap açtığında bir organization oluşur ve her organization'ın silinemeyen, adı değiştirilemeyen bir Default Workspace'i vardır. Workspace, API key'lerini, üyeleri ve limitleri gruplamak için kullanılır; varsayılan olarak bir organization en fazla 100 workspace açabilir. Workspaces sayfasına göre kendi workspace'lerine aylık spend limit ve dakikalık istek, input token ve output token limitleri koyabilirsin. Bu limitler organization limitini aşamaz, yalnız altında tutar. Default Workspace'e spend limit konamıyor.

Bookstore için makul bir düzen: bookstore-dev ve bookstore-prod diye iki workspace, prod'da düşük bir aylık spend limit ve e-posta bildirimi. Böylece geliştirme ortamındaki bir döngü hatası prod bütçesini tüketmez.

API key'i Console'daki API keys sayfasından (platform.claude.com/settings/keys) Create key ile oluşturuyorsun. Authentication sayfası (15 Kasım 2026) üç key tipi sayıyor:

Key tipiKimin adına çalışırNe zaman
Personal keySeni, senin rol ve izinlerinleKendi geliştirme ve script'lerin
Service account keyBir service account'uCI ve production servisleri gibi paylaşılan işler
Workspace key (legacy)Hiç kimseyi; workspace'e aittirYeni entegrasyonda tercih edilmiyor

Belge, kimliğe bağlı key'leri (personal ve service account) öneriyor: key'in sahibi organization'dan çıkarılınca key de çalışmayı bırakıyor. Bookstore.Api'nin production'daki key'i bu yüzden bir service account'a ait olmalı; Emre ayrıldığında servis durmamalı. Key oluştururken bir süre de seçiyorsun: 3 saat, 1 gün, 7 gün, 30 gün, özel bir süre ya da Never. Süre sonradan değiştirilemiyor; süresi dolan key 401 authentication_error döndürüyor. Key'i belirli bir workspace'e bağlarsan isteklerde workspace ID göndermen gerekmez. Bağlamadığın bir key ile her istekte anthropic-workspace-id header'ı gerekir.

Yerel geliştirmede key'i koda ya da appsettings.json'a yazmıyorsun. SDK'lar key'i ANTHROPIC_API_KEY ortam değişkeninden okuyor:

# PowerShell (Windows): yalnız bu oturum için
$env:ANTHROPIC_API_KEY = "<your-api-key>"

# bash / zsh
export ANTHROPIC_API_KEY="<your-api-key>"

Get started sayfası yerel geliştirme için bir seçenek daha veriyor: ant CLI ile ant auth login yapıp tarayıcıdan giriş yapmak. Bu durumda SDK'lar CLI'ın sakladığı kimlik bilgisini kendisi buluyor. Ekipte herkesin kendi key'ini taşımasından daha temiz bir yol, ama bu yazıda okurun Windows'ta da aynı adımları izleyebilmesi için ortam değişkenini kullanıyoruz.

Doğru Paketi Kurmak: Anthropic 13.x ve Paket Adı Tuzağı

NuGet'te Anthropic paketi sürüm 10'dan beri Anthropic'in resmî C# SDK'sı. Daha önce aynı paket adı, topluluk tarafından yazılmış tryAGI SDK'sı için kullanılıyordu; 3.x ve altındaki sürümler o koda ait ve paket artık tryAGI.Anthropic adıyla yayınlanıyor. Emre'nin sürümü düşürünce derlenen projesi büyük ihtimalle bu eski paketti: bambaşka bir API'ye sahip, başka bir proje. C# SDK sayfası bu uyarıyı en üstte veriyor. Kural basit: Anthropic 10 veya üstü, bugün 13.x.

dotnet new web -n Bookstore.Api -o src/Bookstore.Api --framework net10.0
dotnet add src/Bookstore.Api package Anthropic --version 13.8.0
dotnet list src/Bookstore.Api package
Project 'Bookstore.Api' has the following package references
   [net10.0]:
   Top-level Package      Requested   Resolved
   > Anthropic            13.8.0      13.8.0

13.8.0, 10 Kasım 2026'da yayımlandı. Paket net8.0, net9.0 ve netstandard2.0 hedefleriyle geliyor; .NET 10 projesinde net9.0 derlemesi kullanılıyor ve ek bir ayar gerekmiyor.

İlk İstek: AnthropicClient, MessageCreateParams ve TryPickText

En küçük örnek bir console uygulaması. Client'ı parametresiz oluşturunca key'i ortamdan alıyor:

using Anthropic;
using Anthropic.Models.Messages;

// Key'i ANTHROPIC_API_KEY ortam değişkeninden okur.
AnthropicClient client = new();

MessageCreateParams parameters = new()
{
    Model = Model.ClaudeHaiku5_5,
    MaxTokens = 300,
    Messages = [new UserMessageParam { Content = "Suggest one classic novel for a long train trip." }],
};

Message message = await client.Messages.Create(parameters);

foreach (ContentBlock block in message.Content)
{
    if (block.TryPickText(out TextBlock? text))
    {
        Console.WriteLine(text.Text);
    }
}

Console.WriteLine($"stop_reason={message.StopReason?.Raw()} input={message.Usage.InputTokens} output={message.Usage.OutputTokens}");

Burada üç şeye dikkat et. Model, SDK'daki Model enum'undan geliyor (Model.ClaudeHaiku5_5, JSON'da claude-haiku-5-5); string de verebilirsin, çünkü property ApiEnum<string, Model> tipinde ve iki yönde implicit dönüşümü var. MaxTokens zorunlu: cevabın üst sınırı, modelin hedefi değil. Cevap tek bir string değil, content içinde bir block listesi. Text, thinking, tool_use gibi farklı block tipleri gelebileceği için SDK her block'u bir union olarak veriyor ve TryPickText ile text olanı alıyorsun. content[0]'ı her zaman text sanmak, ikinci yazıda göreceğimiz ilk tuzak.

Dokümandaki örnek yanıt, basit bir "Hello, Claude" isteği için şöyle:

{
  "id": "msg_01XFDUDYJgAACzvnptvVoYEL",
  "type": "message",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "Hello!"
    }
  ],
  "model": "claude-opus-5-5",
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {
    "input_tokens": 12,
    "output_tokens": 6
  }
}

stop_reason modelin neden durduğunu, usage ise faturalanan token'ları söylüyor. İkisini de bu seride sürekli okuyacağız; ayrıntısı ikinci yazıda.

API Version ve Kaldırılan Sampling Parametreleri

Claude API'de her istek bir API version ile gider. SDK kullanıyorsan bu sürümü sen seçmiyorsun: SDK'nın major sürümü çıktığında geçerli olan API version'a sabitleniyor. C# SDK sayfasına göre 13.0.0, 2026-10-21 version'ını kullanan ilk sürüm. SDK olmadan HTTP ile çağırırsan anthropic-version: 2026-10-21 header'ını kendin gönderirsin. Yeni API version'a geçmek, SDK'nın yeni major sürümüne geçmek anlamına geliyor.

2026-10-21 ile gelen en görünür değişiklik sampling parametreleri. Migration rehberi açık: temperature, top_p ya da top_k gönderen bir istek, değer varsayılan olsa bile 400 alıyor. SDK 13'te bu üç property zaten yok; Emre'nin projesinin derlenmemesinin sebebi buydu. Claude 4.7 ve sonraki modeller bu parametrelerin yalnız varsayılan değerini kabul ediyordu. Yani LLM Temelleri serisinin sampling yazısında yerel modelle denediğimiz "temperature'ı düşür, cevap tutarlı olsun" ayarının Claude'un güncel modellerinde karşılığı yok. Rehber de bunun yerine geçen başka bir ayar olmadığını, davranışı prompt ile yönlendirmek gerektiğini söylüyor. Tutarlı format istiyorsan altıncı yazıdaki structured outputs doğru araç.

İkinci değişiklik kimlik doğrulamada. SDK 13, key'i X-Api-Key yerine Authorization: Bearer header'ında gönderiyor; API ikisini de kabul ediyor. Arada key'i X-Api-Key'den okuyan bir proxy, gateway ya da log filtresi varsa onu güncellemen gerekiyor. Üçüncüsü mesaj tipleri: v13'te MessageParam, rol başına bir class'tan (UserMessageParam, AssistantMessageParam, SystemMessageParam) oluşan bir union. v12'deki new() { Role = Role.User, ... } kodu artık derlenmiyor.

Bookstore.Api'de ClaudeCatalogService: Options, DI ve Singleton Client

Console örneği öğretici ama Bookstore.Api'de client'ı her istekte new ile oluşturmak istemiyoruz. AnthropicClient connection'ları ve retry ayarlarını içinde tutuyor; C# SDK sayfasındaki WithOptions açıklaması da aynı client'ın connection ve thread pool'larını yeniden kullandığını söylüyor. Bu yüzden dependency injection container'ına singleton olarak kaydediyoruz. Ayarlar options pattern ile geliyor:

using System.ComponentModel.DataAnnotations;

namespace Bookstore.Api.Ai;

public sealed class ClaudeOptions
{
    public const string SectionName = "Claude";

    // Boşsa SDK key'i ANTHROPIC_API_KEY ortam değişkeninden okur.
    public string? ApiKey { get; set; }

    [Required]
    public string Model { get; set; } = "claude-haiku-5-5";

    [Range(1, 128_000)]
    public int MaxTokens { get; set; } = 400;

    [Range(0, 10)]
    public int MaxRetries { get; set; } = 2;

    public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(60);
}
{
  "Claude": {
    "Model": "claude-haiku-5-5",
    "MaxTokens": 400,
    "MaxRetries": 2,
    "Timeout": "00:01:00"
  }
}

ApiKey property'si boş: key appsettings.json'a girmiyor. Geliştirmede ya ortam değişkeni ya da user secrets kullanılıyor; production'da ise ortam değişkeni ya da secret store (Azure Key Vault gibi). Secret yönetiminin ayrıntılarını ASP.NET Core Web API serisinde gördük. User secrets kullanacaksan komut şu; değer Claude:ApiKey olarak options'a bağlanıyor:

dotnet user-secrets set "Claude:ApiKey" "<your-api-key>" --project src/Bookstore.Api

Kayıt metodu:

using Anthropic;
using Microsoft.Extensions.Options;

namespace Bookstore.Api.Ai;

public static class ClaudeServiceCollectionExtensions
{
    public static IServiceCollection AddClaude(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddOptions<ClaudeOptions>()
            .Bind(configuration.GetSection(ClaudeOptions.SectionName))
            .ValidateDataAnnotations()
            .ValidateOnStart();

        // Uygulama boyunca tek client: connection pool ve retry ayarları ortak.
        services.AddSingleton<IAnthropicClient>(sp =>
        {
            ClaudeOptions options = sp.GetRequiredService<IOptions<ClaudeOptions>>().Value;
            return string.IsNullOrWhiteSpace(options.ApiKey)
                ? new AnthropicClient { MaxRetries = options.MaxRetries, Timeout = options.Timeout }
                : new AnthropicClient { ApiKey = options.ApiKey, MaxRetries = options.MaxRetries, Timeout = options.Timeout };
        });

        return services;
    }
}

İki dal var çünkü ApiKey property'sine null vermek yerine onu hiç set etmemek istiyoruz; set edilmezse SDK ortam değişkenine bakıyor. ValidateOnStart sayesinde MaxTokens = 0 gibi bir yanlış ayar uygulama açılırken yakalanıyor, ilk kullanıcı isteğinde değil. Client'ı IAnthropicClient interface'i ile kaydetmek de sonraki yazılarda işimize yarayacak.

Şimdi asıl service. İsteği kuran kısmı ayrı bir static method yaptık, çünkü isteğin şeklini API'ye gitmeden test etmek istiyoruz:

using Anthropic;
using Anthropic.Exceptions;
using Anthropic.Models.Messages;
using Bookstore.Api.Ai;
using Microsoft.Extensions.Options;

namespace Bookstore.Api.Catalog;

public sealed class ClaudeCatalogService(
    IAnthropicClient claude,
    IOptions<ClaudeOptions> options,
    ILogger<ClaudeCatalogService> logger)
{
    private const string SystemPrompt = """
        You write catalog summaries for Bookstore, an online bookstore.
        Write exactly two sentences in Turkish. No spoilers, no marketing superlatives.
        Use only the facts in the description; do not invent awards or sales figures.
        """;

    public static MessageCreateParams BuildRequest(Book book, ClaudeOptions settings) => new()
    {
        Model = settings.Model,
        MaxTokens = settings.MaxTokens,
        System = SystemPrompt,
        Messages =
        [
            new UserMessageParam
            {
                Content = $"Title: {book.Title}\nAuthor: {book.Author}\nDescription: {book.Description}",
            },
        ],
    };

    public async Task<BookSummary?> SummarizeAsync(Book book, CancellationToken cancellationToken)
    {
        MessageCreateParams parameters = BuildRequest(book, options.Value);

        try
        {
            Message message = await claude.Messages.Create(parameters, cancellationToken);

            // content bir block listesi; yalnız text block'larını birleştiriyoruz.
            string summary = string.Concat(message.Content
                .Select(block => block.TryPickText(out TextBlock? text) ? text.Text : string.Empty));

            logger.LogInformation(
                "Summary for {Isbn}: model={Model} stop={StopReason} input={InputTokens} output={OutputTokens}",
                book.Isbn, message.Model.Raw(), message.StopReason?.Raw(),
                message.Usage.InputTokens, message.Usage.OutputTokens);

            return new BookSummary(book.Isbn, summary.Trim(), message.Model.Raw(),
                message.Usage.InputTokens, message.Usage.OutputTokens);
        }
        catch (AnthropicApiException ex)
        {
            // Başarısız çağrı isteği çökertmez; endpoint 503 döner.
            logger.LogWarning(ex, "Claude call failed for {Isbn}", book.Isbn);
            return null;
        }
    }
}

System prompt'u İngilizce yazdık ama cevabı Türkçe istiyoruz; bu, koddaki string'lerin İngilizce kalması kuralımızla da uyumlu. "Yalnız açıklamadaki bilgileri kullan" satırı, modelin ödül ya da satış rakamı uydurmasını zorlaştırıyor. Hata durumunda AnthropicApiException yakalanıp loglanıyor ve null dönülüyor. Bu, SDK'nın bütün API hatalarının base class'ı; 400 için AnthropicBadRequestException, 429 için AnthropicRateLimitException gibi alt tipler var. Hangi hatanın nasıl ele alınacağını son yazıda ayrıntılı göreceğiz.

Endpoint ve Program.cs:

namespace Bookstore.Api.Catalog;

public static class CatalogEndpoints
{
    public static IEndpointRouteBuilder MapCatalogEndpoints(this IEndpointRouteBuilder app)
    {
        app.MapGet("/api/books/{isbn}/summary", async Task<IResult> (
            string isbn, IBookRepository books, ClaudeCatalogService catalog, CancellationToken ct) =>
        {
            Book? book = books.FindByIsbn(isbn);
            if (book is null)
            {
                return TypedResults.NotFound();
            }

            BookSummary? summary = await catalog.SummarizeAsync(book, ct);
            return summary is null
                ? TypedResults.Problem("The summary service is not available right now.", statusCode: StatusCodes.Status503ServiceUnavailable)
                : TypedResults.Ok(summary);
        });

        return app;
    }
}
using Bookstore.Api.Ai;
using Bookstore.Api.Catalog;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddProblemDetails();
builder.Services.AddClaude(builder.Configuration);
builder.Services.AddSingleton<IBookRepository, InMemoryBookRepository>();
builder.Services.AddScoped<ClaudeCatalogService>();

WebApplication app = builder.Build();

app.UseExceptionHandler();
app.MapCatalogEndpoints();

if (app.Environment.IsDevelopment())
{
    app.MapModelEndpoints();
}

app.Run();

Service'i scoped kaydettik, client singleton. Service'in kendisi state tutmuyor; scoped olması, ileride isteğe bağlı bilgiler (oturumdaki müşteri gibi) eklediğimizde işimizi kolaylaştıracak.

İsteği API'ye Göndermeden Görmek

SDK'nın Params class'ları gönderilecek gövdeyi RawBodyData property'sinde tutuyor. Bunu serileştirince ağa çıkmadan, API'ye gidecek JSON'u görebiliyorsun. Dune için BuildRequest çıktısı:

{
  "model": "claude-haiku-5-5",
  "max_tokens": 400,
  "system": "You write catalog summaries for Bookstore, an online bookstore.\nWrite exactly two sentences in Turkish. No spoilers, no marketing superlatives.\nUse only the facts in the description; do not invent awards or sales figures.",
  "messages": [
    {
      "role": "user",
      "content": "Title: Dune\nAuthor: Frank Herbert\nDescription: On the desert planet Arrakis, young Paul Atreides is caught in a struggle over the spice that controls the galaxy."
    }
  ]
}

Gövdede temperature yok, system üst seviyede bir alan, messages içinde tek bir user mesajı var. Aynı kontrolü teste de koyduk:

using System.Text.Json;
using Anthropic.Models.Messages;
using Bookstore.Api.Ai;
using Bookstore.Api.Catalog;

namespace Bookstore.Api.Tests;

public sealed class ClaudeCatalogServiceTests
{
    private static readonly Book Dune = new InMemoryBookRepository().FindByIsbn("9780441172719")!;

    [Fact]
    public void BuildRequest_UsesConfiguredModelAndLimit()
    {
        MessageCreateParams request = ClaudeCatalogService.BuildRequest(Dune, new ClaudeOptions { Model = "claude-sonnet-5-5", MaxTokens = 300 });

        Assert.Equal("claude-sonnet-5-5", request.Model.Raw());
        Assert.Equal(300, request.MaxTokens);
        Assert.Single(request.Messages);
    }

    [Fact]
    public void BuildRequest_SendsNoSamplingParameters()
    {
        string body = JsonSerializer.Serialize(ClaudeCatalogService.BuildRequest(Dune, new ClaudeOptions()).RawBodyData);

        // 2026-10-21 API version'ı temperature, top_p ve top_k'ya 400 döner.
        Assert.DoesNotContain("temperature", body);
        Assert.DoesNotContain("top_p", body);
        Assert.DoesNotContain("top_k", body);
    }
}
dotnet build
dotnet test --no-build
  Bookstore.Api -> src/Bookstore.Api/bin/Debug/net10.0/Bookstore.Api.dll
  Bookstore.Api.Tests -> tests/Bookstore.Api.Tests/bin/Debug/net10.0/Bookstore.Api.Tests.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Passed!  - Failed:     0, Passed:     2, Skipped:     0, Total:     2, Duration: 70 ms - Bookstore.Api.Tests.dll (net10.0)

Models API ile Hangi Modeller Açık?

Model adlarını koda sabitlemeden önce key'inin hangi modellere eriştiğini görmek iyi bir alışkanlık. Models API (GET /v1/models) her model için id, display_name, max_input_tokens (context window), max_tokens (çıktı sınırı), lifecycle (active, deprecated, retired) ve bir capabilities object'i dönüyor. Models overview sayfasına göre capabilities içinde effort seviyeleri, thinking tipleri, structured outputs ve server tool desteği gibi bilgiler var. Üçüncü yazıda bu alanları model seçimi için kullanacağız. Development'ta açılan küçük bir endpoint:

using Anthropic;
using Anthropic.Models.Models;

namespace Bookstore.Api.Ai;

public static class ModelEndpoints
{
    public sealed record ModelRow(string Id, string DisplayName, long? MaxInputTokens, long? MaxTokens, string Lifecycle);

    // Yalnız Development'ta açılır: bu key'in kullanabildiği modelleri listeler.
    public static IEndpointRouteBuilder MapModelEndpoints(this IEndpointRouteBuilder app)
    {
        app.MapGet("/api/ai/models", async (IAnthropicClient claude, CancellationToken ct) =>
        {
            List<ModelRow> rows = [];
            ModelListPage page = await claude.Models.List(cancellationToken: ct);
            await foreach (ModelInfo model in page.Paginate(ct))
            {
                rows.Add(new ModelRow(model.ID, model.DisplayName, model.MaxInputTokens, model.MaxTokens, model.Lifecycle.Raw()));
            }

            return TypedResults.Ok(rows);
        });

        return app;
    }
}

Liste varsayılan olarak active ve deprecated modelleri içeriyor, retired modeller yalnız açıkça istenirse geliyor. Belge, model ailesini id'den çıkarmak yerine line alanını (opus, sonnet gibi) okumayı öneriyor. 15 Kasım 2026 itibarıyla models overview sayfasındaki güncel modeller Claude Fable 5.2, Opus 5.5, Sonnet 5.5 ve Haiku 5.5; dördünün de context window'u 1M token, senkron Messages API'de çıktı sınırı 128K token. Bu yazıda Haiku 5.5'i seçtik: iki cümlelik özet gibi kısa ve sık bir iş için belgenin tarifiyle örtüşüyor.

Üretimde Dikkat

Key'in sahipliği. Production key'i bir kişiye değil bir service account'a ait olsun, ayrı bir workspace'e bağlansın ve o workspace'e spend limit konsun. Rate limits sayfasına göre organization'lar bir usage tier'a yerleşiyor ve her tier'ın aylık bir spend cap'i var (Start 500, Build 1.000, Scale 200.000 USD). Cap'e ulaşınca istekler ayın sonuna kadar 429 dönüyor ve bu 429'da retry-after header'ı yok; SDK'nın otomatik retry'ları da işe yaramıyor. Bu yüzden spend bildirimini açık tut.

Timeout ve retry. SDK varsayılan olarak 10 dakika timeout ve 2 retry kullanıyor; retry yalnız bağlantı hataları, 408, 409, 429 ve 5xx için yapılıyor. İki cümlelik bir özet için 10 dakika beklemek anlamsız, bu yüzden options'ta 60 saniye verdik. Kullanıcı sayfayı kapatırsa CancellationToken isteği iptal ediyor; endpoint'in token'ı service'e geçirmesi bu yüzden önemli.

Token sayıları log'da. usage.input_tokens ve usage.output_tokens değerlerini her istekte logluyoruz. Fatura bu sayılardan çıkıyor ve maliyet takibini son yazıda bu log'ların üzerine kuracağız. Kitap açıklamaları uzadıkça input token'ı da artıyor; Console'daki Usage sayfası ile log'ları ara ara karşılaştır.

Cache. Aynı kitabın özeti her sayfa açılışında yeniden üretilmemeli. Özet değişmeyen bir veri; üretildikten sonra veritabanına ya da cache'e yazılmalı. Bunu sekizinci yazıda Message Batches ile toplu üretim yaparak çözeceğiz.

Sık Düşülen Tuzaklar

Yanlış NuGet paketi. Anthropic 3.x ve altı topluluk SDK'sı; resmî SDK 10 ve üstü. Bir örnek kodun derlenmemesi çoğu zaman paket karışıklığından geliyor.

Client'ı her istekte oluşturmak. Her new AnthropicClient() kendi HTTP altyapısını kuruyor. Container'a singleton olarak kaydet, ayar değiştirmen gerekirse WithOptions kullan.

Key'i appsettings.json'a commit'lemek. Repo public olmasa bile key bir gün bir log'a, bir ekran paylaşımına ya da bir fork'a düşer. Key ortam değişkeninde ya da secret store'da durmalı; sızdığından şüphelendiğin key'i Console'dan Disable et ya da sil.

Eski örnekleri kopyalamak. temperature, top_p, top_k ve Role = Role.User içeren örnekler 2026-10-21 öncesine ait. Kopyaladığın kodun hangi SDK sürümü için yazıldığına bak.

Model adını uydurmak. claude-haiku-5-5 gibi güncel model ID'leri tarih eki taşımıyor; models overview sayfası 4.6 kuşağından itibaren tarihsiz ID'lerin kendisinin sabit bir snapshot olduğunu söylüyor. Eski modellerdeki gibi bir tarih eki eklersen var olmayan bir model ID'si göndermiş olursun.

Küçük Bir Deneme

★☆☆ Snapshot'taki projeyi derle ve testleri çalıştır. BuildRequest'e StopSequences = ["\n\n"] ekleyip RawBodyData çıktısında stop_sequences alanının nasıl göründüğüne bak.

★★☆ ClaudeOptions'a Model için bir izin listesi ekle (claude-haiku-5-5, claude-sonnet-5-5) ve listede olmayan bir değerle uygulamanın açılışta hata verdiğini gör. İpucu: Validate ile özel bir kural.

★★★ /api/ai/models endpoint'ini genişlet: her model için capabilities.effort altındaki desteklenen seviyeleri ve capabilities.structured_outputs.supported değerini de döndür. SDK'da bu alanların C# karşılıklarını derleyici hatalarıyla bul.

Bu yazıda client'ı kurduk ve tek bir istek gönderdik. İkinci yazıda Messages API'nin içine giriyoruz: çok turlu konuşma, system mesajları, text dışındaki content block'ları (PDF dahil), her stop_reason değerinde ne yapılacağı ve usage alanlarının tek tek ne anlattığı.