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.
Search across all documentation pages
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.
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.
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:
tool_use.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.NewStreaming returns an iterator you drive with stream.Next() and stream.Current(), Go's standard iterator shape.message.Accumulate builds up the final, complete Message as events arrive, so you get both incremental text and a finished object at the end.ContentBlockDeltaEvent values 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.client.Messages.New issues a single blocking HTTP request and returns a fully populated Message.client.Messages.NewStreaming opens a server-sent events connection and exposes each event through the iterator; the connection stays open until message_stop or an error.tool_use is just another stop_reason on the response: when Claude decides to call a tool, the response's Content includes a tool_use block instead of (or alongside) text, and StopReason is "tool_use".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.
}
}| 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 |
// 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
}context.Context through so request timeouts and cancellation propagate correctly under load.%w when returning them up the call stack, so callers can still errors.As into the underlying SDK error type.anthropic.NewClient inside a request handler discards connection pooling and adds latency. Fix: construct one client at startup and reuse it.stream.Err() after the loop - a stream that ends early due to a network error still exits the for stream.Next() loop normally. Fix: always check stream.Err() after the loop, not just inside it.Content[0] is always text - a response with tool_use puts a ToolUseBlock in Content, not a text block, and indexing blindly panics. Fix: check block.AsAny()'s type, or check StopReason before reading Content[0].Text.MaxTokens is required - omitting it produces a request-time validation error rather than a sensible default. Fix: always set an explicit MaxTokens sized to your expected response.context.Background() everywhere means a slow or hung request can't be cancelled by an upstream timeout. Fix: thread a request-scoped context.Context with a deadline through every call.tool_use block means Claude is waiting for a tool_result, not that the task is done. Fix: run the tool, then send the result back in a follow-up Messages.New call before treating the conversation as complete.| 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 |
No. Construct anthropic.NewClient once at startup and reuse it across requests; it's safe for concurrent use across goroutines.
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.
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.
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).
The request fails validation, since MaxTokens has no implicit default. Always set it explicitly to a value sized for your expected response length.
Yes. Pass option.WithHTTPClient(httpClient) when constructing the client to supply your own *http.Client, useful for shared connection pools or custom timeout behavior.
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.
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.
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.
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.
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.
Reviewed by Chris St. John·Last updated Jul 19, 2026