Calling Claude from Go with the Official SDK
anthropic-sdk-go is the official Go client for the Claude Messages API, giving Go services typed requests, a streaming iterator, and structured tool_use support without hand-rolled HTTP calls.
Summary
anthropic-sdk-go wraps the same Messages API used by every other official Claude SDK, but built around Go conventions: typed request and response structs, functional options for client configuration, and errors returned as values rather than exceptions.
A client is a single long-lived object you construct once per process and reuse across requests, since it holds the HTTP transport and API key.
The three things most Go services need from Claude are a plain request/response call, a streaming call for incremental output, and a tool_use loop for letting Claude call functions in your service.
All three sit on the same client.Messages surface, differing only in which method you call and how you consume the result.
This page covers all three, in the order most Go services adopt them.
Recipe
Quick-reference recipe card - copy-paste ready.
client := anthropic.NewClient(
option.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")),
)
message, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet5,
MaxTokens: 1024,
System: anthropic.String("You are a concise assistant."),
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Summarize the Go scheduler in two sentences.")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(message.Content[0].Text)When to reach for this:
- You're building a Go service (HTTP API, CLI, background worker) that needs typed access to Claude.
- You want compile-time checking on request fields instead of a raw JSON body.
- You need to stream partial output to a client, or let Claude call functions in your process via
tool_use. - You're consolidating multiple ad hoc HTTP clients into one shared, typed dependency.
Working Example
A single Go program that constructs a client, sends a streaming request, and prints tokens as they arrive.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client := anthropic.NewClient(
option.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")),
)
ctx := context.Background()
stream := client.Messages.NewStreaming(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet5,
MaxTokens: 512,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("List three benefits of Go's goroutines.")),
},
})
message := anthropic.Message{}
for stream.Next() {
event := stream.Current()
if err := message.Accumulate(event); err != nil {
log.Fatal(err)
}
switch delta := event.AsAny().(type) {
case anthropic.ContentBlockDeltaEvent:
if textDelta, ok := delta.Delta.AsAny().(anthropic.TextDelta); ok {
fmt.Print(textDelta.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
fmt.Println()
fmt.Println("stop reason:", message.StopReason)
}What this demonstrates:
client.Messages.NewStreamingreturns an iterator you drive withstream.Next()andstream.Current(), Go's standard iterator shape.message.Accumulatebuilds up the final, completeMessageas events arrive, so you get both incremental text and a finished object at the end.- Text deltas arrive as
ContentBlockDeltaEventvalues you type-switch on, matching how the underlying server-sent events are structured. stream.Err()must be checked after the loop ends, since a mid-stream failure doesn't necessarily surface inside the loop itself.
Deep Dive
How It Works
- The SDK translates every typed Go struct into the same JSON body the Messages API expects; nothing about the wire format changes because you're using Go.
client.Messages.Newissues a single blocking HTTP request and returns a fully populatedMessage.client.Messages.NewStreamingopens a server-sent events connection and exposes each event through the iterator; the connection stays open untilmessage_stopor an error.tool_useis just anotherstop_reasonon the response: when Claude decides to call a tool, the response'sContentincludes atool_useblock instead of (or alongside) text, andStopReasonis"tool_use".
Tool Use in Go
Defining a tool means describing its input schema as a typed struct, then checking the response for a tool_use block.
type WeatherInput struct {
City string `json:"city"`
}
weatherTool := anthropic.ToolParam{
Name: anthropic.String("get_weather"),
Description: anthropic.String("Get the current weather for a city."),
InputSchema: anthropic.ToolInputSchemaParam{
Type: "object",
Properties: map[string]interface{}{
"city": map[string]string{"type": "string"},
},
Required: []string{"city"},
},
}
message, err := client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet5,
MaxTokens: 512,
Tools: []anthropic.ToolUnionParam{{OfTool: &weatherTool}},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in Denver?")),
},
})
if err != nil {
log.Fatal(err)
}
for _, block := range message.Content {
if toolUse, ok := block.AsAny().(anthropic.ToolUseBlock); ok {
var input WeatherInput
if err := json.Unmarshal(toolUse.Input, &input); err != nil {
log.Fatal(err)
}
fmt.Println("Claude wants weather for:", input.City)
// Run your real lookup here, then send a tool_result message back.
}
}Client Configuration Options
| Option | Purpose |
|---|---|
option.WithAPIKey(key) | Sets the API key explicitly instead of reading ANTHROPIC_API_KEY |
option.WithBaseURL(url) | Points the client at a different base URL (rare; mostly for proxies or testing) |
option.WithHTTPClient(httpClient) | Supplies a custom *http.Client, useful for shared connection pooling or custom timeouts |
option.WithMaxRetries(n) | Overrides the SDK's default retry count for transient failures |
Go Notes
// Reuse one client across your whole service; it's safe for concurrent use.
var claudeClient = anthropic.NewClient(
option.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")),
)
func handleRequest(ctx context.Context, prompt string) (string, error) {
message, err := claudeClient.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet5,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(prompt)),
},
})
if err != nil {
return "", fmt.Errorf("claude request failed: %w", err)
}
return message.Content[0].Text, nil
}- The client is safe to share across goroutines; construct it once at startup, not per request.
- Always pass a
context.Contextthrough so request timeouts and cancellation propagate correctly under load. - Wrap errors with
%wwhen returning them up the call stack, so callers can stillerrors.Asinto the underlying SDK error type.
Gotchas
- Constructing a new client per request - creating
anthropic.NewClientinside a request handler discards connection pooling and adds latency. Fix: construct one client at startup and reuse it. - Ignoring
stream.Err()after the loop - a stream that ends early due to a network error still exits thefor stream.Next()loop normally. Fix: always checkstream.Err()after the loop, not just inside it. - Assuming
Content[0]is always text - a response withtool_useputs aToolUseBlockinContent, not a text block, and indexing blindly panics. Fix: checkblock.AsAny()'s type, or checkStopReasonbefore readingContent[0].Text. - Forgetting
MaxTokensis required - omitting it produces a request-time validation error rather than a sensible default. Fix: always set an explicitMaxTokenssized to your expected response. - Not passing a cancellable context under load - using
context.Background()everywhere means a slow or hung request can't be cancelled by an upstream timeout. Fix: thread a request-scopedcontext.Contextwith a deadline through every call. - Treating tool_use as the final answer - receiving a
tool_useblock means Claude is waiting for atool_result, not that the task is done. Fix: run the tool, then send the result back in a follow-upMessages.Newcall before treating the conversation as complete.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| OpenAI compatibility layer with an OpenAI-shaped Go client | You're migrating an existing OpenAI-integrated Go service fast and can tolerate a narrower parameter surface | You need full Messages API features like extended tool_use or the latest content block types |
Raw net/http calls to the Messages API | You need something with zero dependencies in a minimal binary | You want typed requests, retries, and streaming parsing handled for you |
| A different official SDK (Java, C#, PHP, Ruby) | Your service isn't actually written in Go | You're specifically working in a Go codebase |
FAQs
Do I need to construct a new client for every request?
No. Construct anthropic.NewClient once at startup and reuse it across requests; it's safe for concurrent use across goroutines.
How does streaming work in anthropic-sdk-go?
client.Messages.NewStreaming returns an iterator. Call stream.Next() in a loop, read stream.Current() for each event, and check stream.Err() after the loop ends to catch mid-stream failures.
How do I know when Claude wants to call a tool?
Check message.StopReason for "tool_use", then look for a ToolUseBlock inside message.Content. The block carries the tool's name and a JSON-encoded input you unmarshal into your own struct.
Does anthropic-sdk-go retry failed requests automatically?
Yes, by default it retries transient failures like rate limits and network errors a limited number of times. You can override the count with option.WithMaxRetries(n).
What happens if I forget to set MaxTokens?
The request fails validation, since MaxTokens has no implicit default. Always set it explicitly to a value sized for your expected response length.
Can I use anthropic-sdk-go with a custom HTTP client?
Yes. Pass option.WithHTTPClient(httpClient) when constructing the client to supply your own *http.Client, useful for shared connection pools or custom timeout behavior.
Is the client safe to use from multiple goroutines at once?
Yes, a single anthropic.Client is safe for concurrent use, which is why you should construct it once and share it rather than creating one per request.
How do errors surface in Go, compared to exceptions in other SDKs?
Errors return as a normal Go error value from every method, following Go convention. There's no panic or exception; check err != nil after each call the same way you would for any other Go API.
Do I need to pass context.Background() everywhere?
Only in throwaway scripts. In a real service, pass a request-scoped context.Context with a deadline or cancellation, so a slow Claude call can be cancelled the same way any other downstream call would be.
Can the same client call multiple models?
Yes. Model is a per-request field, not a client-level setting, so a single client can send requests to Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5, or Claude Haiku 4.5 as needed.
Related
- Other Official SDKs Basics - a first call in each of the five languages, including a simpler Go example
- Idiomatic Tool Use Across Go, Java, C#, PHP, and Ruby - compare this tool_use pattern against the other four SDKs
- Beyond Python and TypeScript: The Other Official Claude SDKs - the conceptual overview this article builds on
- Building a Multi-Turn Tool Use Loop - the full request/tool_result/follow-up loop, in general terms
- How Server-Sent Events Power Claude's Streaming Responses - the streaming protocol this Go example consumes
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the current official SDKs for Go, Java, C#, PHP, and Ruby. Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.