18 KiB
Claude API — C#
Note: The C# SDK is the official Anthropic SDK for C#. Tool use is supported via the Messages API with a beta
BetaToolRunnerfor automatic tool execution loops. The SDK also supports Microsoft.Extensions.AI IChatClient integration with function invocation and Managed Agents (beta).
Namespace Reference
Types are organized by namespace. If a type you need isn't shown in an example below, locate it via this table first — don't block on fetching SDK source over the network.
using |
Contains |
|---|---|
Anthropic |
AnthropicClient, top-level options |
Anthropic.Models.Messages |
non-beta request/response types — MessageCreateParams, Model, Role, ContentBlock, TextBlock, ToolUseBlock, ToolResultBlockParam, Tool* (tool definition classes) |
Anthropic.Models.Beta.Messages |
beta-endpoint equivalents — MessageCreateParams, BetaMessage, BetaTool*, Speed, BetaRequestMcpServerUrlDefinition, context-editing/compaction configs |
Anthropic.Models.Beta |
shared beta constants |
Anthropic.Models.Beta.Files |
Files API types |
Anthropic.Models.Messages.Batches |
Batch API types |
Anthropic.Helpers.Beta |
BetaToolRunner, beta helper utilities |
Anthropic.Exceptions |
AnthropicApiException, AnthropicRateLimitException, Anthropic5xxException, etc. — see shared/error-codes.md |
Anthropic.Bedrock / Anthropic.Vertex / Anthropic.Foundry / Anthropic.Aws |
platform clients (separate NuGet packages): AnthropicBedrockMantleClient, AnthropicFoundryClient, AnthropicAwsClient |
client.Messages.* uses non-beta types; client.Beta.Messages.* uses the Anthropic.Models.Beta.Messages types. Both namespaces define a MessageCreateParams — pick the one matching the client path you call.
Key types per feature
Write from this table instead of reflecting the SDK assembly. Endpoint column tells you whether to use client.Messages.* or client.Beta.Messages.*.
| Feature | Endpoint | Key C# types (namespace per table above) |
|---|---|---|
| User profiles | beta | client.Beta.UserProfiles.Create(...) / .Retrieve(id) / .List(). Pass the returned profile id on the beta messages call. Requires a beta header — check the SDK's beta-headers reference for the current flag. |
| Agent Skills | beta | BetaContainerParams (with Skills = [new BetaSkillParams { ... }]), BetaCodeExecutionTool20250825. Betas = ["code-execution-2025-08-25", "skills-2025-10-02"]. Download the output via client.Beta.Files.Download(fileId). |
| Advisor tool | beta | BetaAdvisorTool20260301 — may not be in all SDK releases yet |
| Cache diagnostics | beta | Diagnostics = new() { PreviousMessageID = … }, BetaCacheControlEphemeral, BetaContentBlockParam |
| Context editing | beta | ContextManagement = new BetaContextManagementConfig { Edits = [new BetaClearToolUses20250919Edit()] }. Betas = ["context-management-2025-06-27"] (not compact-2026-01-12 — that's for BetaCompact20260112Edit). |
| Memory tool | non-beta | Tools = [new ToolUnion(new MemoryTool20250818())] |
| Programmatic tool calling | non-beta | CodeExecutionTool20260120, ToolResultBlockParam, ContentBlockParam |
| Task budgets | beta | BetaOutputConfig with TaskBudget = new BetaTokenTaskBudget { ... } |
| Tool search | non-beta | new ToolUnion(new ToolSearchToolRegex20251119 { Type = ToolSearchToolRegex20251119Type.ToolSearchToolRegex20251119 }) — Type must be set explicitly. |
| Web search | non-beta | new ToolUnion(new WebSearchTool20260209()) — the latest variant with dynamic filtering (Opus 4.8/4.7/4.6 + Sonnet 4.6). For older models or Vertex, use WebSearchTool20250305() |
Discovering type and member names
If a type or member you need isn't in the tables above, strings ~/.nuget/packages/anthropic/*/lib/*/Anthropic.dll | grep -i <term> is fast and sufficient for locating class and property names. Do not escalate to a dotnet run reflection probe to dump members precisely — the first compile is slow enough to be backgrounded in many environments, trapping you in a polling loop. Instead, write Program.cs using the names strings | grep found; if a member name is wrong the compiler error (error CS1061: 'X' does not contain a definition for 'Y') points at it in a few seconds, faster than any reflection probe.
Note that strings will not surface wire-format snake_case field names (output_tokens, stop_reason) — those are stored in the DLL differently. C# properties are the PascalCase equivalent of the wire field (response.Usage.OutputTokens, response.StopReason). If you know the wire field name from the docs, write the PascalCase property and compile; do not probe for the snake_case string.
Minimal working skeleton
Write a plain Program.cs body — using statements followed by top-level statements, as below. Do not add a #!/usr/bin/env dotnet shebang or #:package Anthropic@* directive: those are .NET file-based-app syntax and fail with CS1024: Preprocessor directive expected when the file is compiled via an existing .csproj. The standard project setup (per the C# quickstart: dotnet new console → dotnet add package Anthropic → edit Program.cs → dotnet run) provides the .csproj and package reference.
Start from this — it compiles as-is. Fill in the feature-specific fields; do not spend turns running reflection or XML-doc inspection to discover type names first.
using System;
using Anthropic;
using Anthropic.Models.Messages; // or Anthropic.Models.Beta.Messages for beta endpoints
AnthropicClient client = new();
var message = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus4_8,
MaxTokens = 1024,
Messages = [ new() { Role = Role.User, Content = "Hello, Claude" } ],
});
Console.WriteLine(message);
For beta features (anything behind an anthropic-beta header), use the beta client path and namespace — same overall shape:
using System;
using Anthropic;
using Anthropic.Models.Beta.Messages;
AnthropicClient client = new();
var response = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = "{{OPUS_ID}}",
MaxTokens = 4096,
Betas = ["<beta-flag>"],
Messages = [ new() { Role = Role.User, Content = "…" } ],
// Tools = new BetaToolUnion[] { new BetaSomeTool { … } }, // for tool features
});
Console.WriteLine(response);
If a type name the feature needs isn't in this file, write it following the naming pattern in the Namespace Reference above and fix from compiler output — producing a Program.cs and iterating beats researching.
Common C# compile errors
- CS8803 (top-level statements must precede type declarations): put any
record/class/structdefinitions after the last top-level statement, at the end of the file. A record defined abovevar client = new AnthropicClient()will not compile. await foreachon aTask<…Page>:client.Models.List()returns aTask<ModelListPage>, which is not directly async-enumerable. Await it first, then iterate:var page = await client.Models.List(); foreach (var m in page.Items) {…}. For auto-pagination, check whether the page type exposesAutoPagingEachAsync()or similar before reaching forawait foreach.
Installation
dotnet add package Anthropic
Client Initialization
using Anthropic;
// Default (uses ANTHROPIC_API_KEY env var)
AnthropicClient client = new();
// Explicit API key (use environment variables — never hardcode keys)
AnthropicClient client = new() {
ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
};
Basic Message Request
using Anthropic.Models.Messages;
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_8,
MaxTokens = 16000,
Messages = [new() { Role = Role.User, Content = "What is the capital of France?" }]
};
var response = await client.Messages.Create(parameters);
// ContentBlock is a union wrapper. .Value unwraps to the variant object,
// then OfType<T> filters to the type you want. Or use the TryPick* idiom
// shown in the Thinking section below.
foreach (var text in response.Content.Select(b => b.Value).OfType<TextBlock>())
{
Console.WriteLine(text.Text);
}
Thinking
Adaptive thinking is the recommended mode for Claude 4.6+ models. Claude decides dynamically when and how much to think.
Fable 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6: Use adaptive thinking (below).
new ThinkingConfigEnabled { BudgetTokens = N }is removed on Fable 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6. Older models: Usenew ThinkingConfigEnabled { BudgetTokens = N }(budget must be <MaxTokens, min 1024).
using Anthropic.Models.Messages;
var response = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus4_8,
MaxTokens = 16000,
// ThinkingConfigParam? implicitly converts from the concrete variant classes —
// no wrapper needed.
// display opt-in: default is omitted (empty thinking text) on Fable 5 / Mythos 5 / Opus 4.8 / 4.7
Thinking = new ThinkingConfigAdaptive { Display = Display.Summarized },
Messages =
[
new() { Role = Role.User, Content = "Solve: 27 * 453" },
],
});
// ThinkingBlock(s) precede TextBlock in Content. TryPick* narrows the union.
foreach (var block in response.Content)
{
if (block.TryPickThinking(out ThinkingBlock? t))
{
Console.WriteLine($"[thinking] {t.Thinking}");
}
else if (block.TryPickText(out TextBlock? text))
{
Console.WriteLine(text.Text);
}
}
Alternative to TryPick*: .Select(b => b.Value).OfType<ThinkingBlock>() (same LINQ pattern as the Basic Message example).
Context Editing / Compaction (Beta)
Beta-namespace prefix is inconsistent (source-verified against src/Anthropic/Models/Beta/Messages/*.cs @ 12.9.0). No prefix: MessageCreateParams, MessageCountTokensParams, Role, Speed. Everything else has the Beta prefix: BetaMessageParam, BetaMessage, BetaContentBlock, BetaToolUseBlock, all block param types. The unprefixed Role WILL collide with Anthropic.Models.Messages.Role if you import both namespaces (CS0104). Safest: import only Beta; if mixing, alias the beta Role:
using Anthropic.Models.Beta.Messages;
using NonBeta = Anthropic.Models.Messages; // only if you also need non-beta types
// Now: MessageCreateParams, BetaMessageParam, Role (beta's), NonBeta.Role (if needed)
BetaMessage.Content is IReadOnlyList<BetaContentBlock> — a 15-variant discriminated union. Narrow with TryPick*. Response BetaContentBlock is NOT assignable to param BetaContentBlockParam — there's no .ToParam() in C#. Round-trip by converting each block:
using Anthropic.Models.Beta.Messages;
var betaParams = new MessageCreateParams // no Beta prefix — see unprefixed list above
{
Model = Model.ClaudeOpus4_8,
MaxTokens = 16000,
Betas = ["compact-2026-01-12"],
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()],
},
Messages = messages,
};
BetaMessage resp = await client.Beta.Messages.Create(betaParams);
foreach (BetaContentBlock block in resp.Content)
{
if (block.TryPickCompaction(out BetaCompactionBlock? compaction))
{
// Content is nullable — compaction can fail server-side
Console.WriteLine($"compaction summary: {compaction.Content}");
}
}
// Context-edit metadata lives on a separate nullable field
if (resp.ContextManagement is { } ctx)
{
foreach (var edit in ctx.AppliedEdits)
Console.WriteLine($"cleared {edit.ClearedInputTokens} tokens");
}
// ROUND-TRIP: BetaMessageParam.Content is BetaMessageParamContent (a string|list
// union). It implicit-converts from List<BetaContentBlockParam>, NOT from the
// response's IReadOnlyList<BetaContentBlock>. Convert each block:
List<BetaContentBlockParam> paramBlocks = [];
foreach (var b in resp.Content)
{
if (b.TryPickText(out var t)) paramBlocks.Add(new BetaTextBlockParam { Text = t.Text });
else if (b.TryPickCompaction(out var c)) paramBlocks.Add(new BetaCompactionBlockParam { Content = c.Content });
// ... other variants as needed
}
messages.Add(new BetaMessageParam { Role = Role.Assistant, Content = paramBlocks });
All 15 BetaContentBlock.TryPick* variants: Text, Thinking, RedactedThinking, ToolUse, ServerToolUse, WebSearchToolResult, WebFetchToolResult, CodeExecutionToolResult, BashCodeExecutionToolResult, TextEditorCodeExecutionToolResult, ToolSearchToolResult, McpToolUse, McpToolResult, ContainerUpload, Compaction.
BetaToolUseBlock.Input is IReadOnlyDictionary<string, JsonElement> — index by key then call the JsonElement extractor:
if (block.TryPickToolUse(out BetaToolUseBlock? tu))
{
int a = tu.Input["a"].GetInt32();
string s = tu.Input["name"].GetString()!;
}
Effort Parameter
Effort is nested under OutputConfig, NOT a top-level property. ApiEnum<string, Effort> has an implicit conversion from the enum, so assign Effort.High directly.
OutputConfig = new OutputConfig { Effort = Effort.High },
Values: Effort.Low, Effort.Medium, Effort.High, Effort.Max. Combine with Thinking = new ThinkingConfigAdaptive() for cost-quality control.
Prompt Caching
System takes MessageCreateParamsSystem? — a union of string or List<TextBlockParam>. There is no SystemTextBlockParam; use plain TextBlockParam. The implicit conversion needs the concrete List<TextBlockParam> type (array literals won't convert). For placement patterns and the silent-invalidator audit checklist, see shared/prompt-caching.md.
System = new List<TextBlockParam> {
new() {
Text = longSystemPrompt,
CacheControl = new CacheControlEphemeral(), // auto-sets Type = "ephemeral"
},
},
Optional Ttl on CacheControlEphemeral: new() { Ttl = Ttl.Ttl1h } or Ttl.Ttl5m. CacheControl also exists on Tool.CacheControl and top-level MessageCreateParams.CacheControl.
Verify hits via response.Usage.CacheCreationInputTokens / response.Usage.CacheReadInputTokens.
Token Counting
MessageTokensCount result = await client.Messages.CountTokens(new MessageCountTokensParams {
Model = Model.ClaudeOpus4_8,
Messages = [new() { Role = Role.User, Content = "Hello" }],
});
long tokens = result.InputTokens;
MessageCountTokensParams.Tools uses a different union type (MessageCountTokensTool) than MessageCreateParams.Tools (ToolUnion) — if you're passing tools, the compiler will tell you when it matters.
PDF / Document Input
DocumentBlockParam takes a DocumentBlockParamSource union: Base64PdfSource / UrlPdfSource / PlainTextSource / ContentBlockSource. Base64PdfSource auto-sets MediaType = "application/pdf" and Type = "base64".
new MessageParam {
Role = Role.User,
Content = new List<ContentBlockParam> {
new DocumentBlockParam { Source = new Base64PdfSource { Data = base64String } },
new TextBlockParam { Text = "Summarize this PDF" },
},
}
Fast Mode (Beta)
var response = await client.Beta.Messages.Create(new MessageCreateParams {
Model = "{{OPUS_ID}}", MaxTokens = 4096,
Speed = Speed.Fast,
Betas = ["fast-mode-2026-02-01"],
Messages = [...],
});
Models API
var page = await client.Models.List();
foreach (var m in page.Items) { Console.WriteLine(m.ID); }
var one = await client.Models.Retrieve("{{OPUS_ID}}");
Long Output (128k) + Prefill
Set MaxTokens = 128000 on client.Messages and use the streaming path (see streaming.md). On Claude 4+ models, 128k output is native — no output-128k-* beta header or beta namespace is needed.
Prefilling the assistant message (putting a trailing Role.Assistant message in the input) is not supported on {{FABLE_NAME}}, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6 — requests return a 400. Use OutputConfig.Format (structured outputs) instead.
Stop Details
When StopReason is "refusal", the response includes structured StopDetails:
if (response.StopReason == "refusal" && response.StopDetails is { } details)
{
Console.WriteLine($"Category: {details.Category}");
Console.WriteLine($"Explanation: {details.Explanation}");
}
Refusal fallbacks ({{FABLE_NAME}}) — opt in by default. Fallbacks are opt-in: without them a refused request simply stops. In {{FABLE_ID}} code, include Fallbacks = [new(Model.ClaudeOpus4_8)] plus the AnthropicBeta.ServerSideFallback2026_06_01 beta on the beta messages call by default — a policy decline is re-served by the fallback model inside the same call. Not available on Amazon Bedrock, Vertex AI, or Microsoft Foundry — use the client-side handler there: new AnthropicClient { Handlers = [new BetaRefusalFallbackHandler { Fallbacks = [new(Model.ClaudeOpus4_8)] }] } (namespace Anthropic.Helpers), with per-conversation state via BetaFallbackState.Create() scoped with using (fallbackState.Use()) { ... }. Full semantics (billing, sticky routing, streaming) and a runnable example: shared/model-migration.md → Migrating to {{FABLE_NAME}} → refusal stop reason, and the C# SDK repo's examples/ (WebFetch via shared/live-sources.md).
Managed Agents (Beta)
The C# SDK supports Managed Agents via client.Beta.Agents, client.Beta.Sessions, client.Beta.Environments, and related namespaces. See shared/managed-agents-overview.md for the architecture and curl/managed-agents.md for the wire-level reference.