How to Call Anthropic Claude API from Go

Web
Initialize the Anthropic Go SDK client with your API key and call Messages.Create to send a prompt and receive a response from Claude.

The first conversation

You have a working Go service. It handles HTTP requests, talks to a database, and returns JSON. Now you want to add reasoning, summarization, or content generation. You find the official Anthropic SDK, import it, and stare at a wall of nested structs. The types look dense. The examples assume you already know how to wire context, handle streaming, and parse heterogeneous response blocks. You need a clear path from import to working request without reinventing the HTTP transport layer.

How the SDK actually talks to Claude

The SDK is a typed wrapper around a REST API. Think of it like a standardized order ticket at a busy kitchen. You do not walk into the back room and shout ingredients. You fill out a form with specific fields, hand it to the host, and wait for the chef to write back the result. The SDK handles JSON marshaling, HTTP headers, authentication, and retry logic so you can focus on the prompt and the response.

Under the hood, every call follows the same pattern. You build a request struct, attach a context, send it through an HTTP client, and receive a response struct. The SDK maps Anthropic's JSON schema directly to Go types. That means model names, token limits, and message roles are compile-time checked. You cannot accidentally send a string where a slice of content blocks belongs. The compiler catches the mismatch before the request ever leaves your machine.

The SDK handles the plumbing. You handle the prompt.

The minimal working call

Here is the smallest program that authenticates, sends a single message, and prints the text response.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/anthropics/anthropic-sdk-go"
	"github.com/anthropics/anthropic-sdk-go/option"
)

// main initializes the client and sends a single prompt to Claude.
func main() {
	// option.WithAPIKey reads the key from the argument or environment.
	// The SDK stores it in memory and attaches it to every request.
	client := anthropic.NewClient(
		option.WithAPIKey("YOUR_API_KEY"),
	)

	// context.Background() provides a base context with no deadlines.
	// Always pass context as the first argument to SDK methods.
	ctx := context.Background()

	// MessageParam defines the model, token limit, and conversation history.
	// System prompts guide behavior. User messages carry the actual query.
	resp, err := client.Messages.Create(ctx, anthropic.MessageParam{
		Model: "claude-3-5-sonnet-20241022",
		MaxTokens: 1024,
		System: []anthropic.MessageSystemContentParam{
			{Type: "text", Text: "You are a helpful assistant."},
		},
		Messages: []anthropic.MessageParam{
			{
				Role: "user",
				Content: []anthropic.MessageContentParam{
					{Type: "text", Text: "Hello, Claude!"},
				},
			},
		},
	})
	// if err != nil is verbose by design. It keeps failure paths visible.
	if err != nil {
		log.Fatal(err)
	}

	// The response contains a slice of content blocks.
	// TextBlock holds the generated string. We print it directly.
	fmt.Println(resp.Content[0].(anthropic.TextBlock).Text)
}

Walking through the request lifecycle

The program starts by creating a client. anthropic.NewClient configures the underlying HTTP transport, sets the base URL, and stores your API key. The key never leaves your process memory. The SDK attaches it to the Authorization header automatically.

Next, the context is created. context.Background() is the root. In a real service, you would derive a context from an incoming HTTP request or a background worker. The context carries deadlines, cancellation signals, and request-scoped values. The SDK reads the context before making the network call. If the context expires or gets canceled, the HTTP client aborts the request and returns immediately.

The MessageParam struct holds the payload. The Model field selects the version. MaxTokens caps the response length. The System slice sets instructions that apply to the whole conversation. The Messages slice holds the turn history. Each message has a Role and a Content slice. The content slice can hold text, images, or tool calls. The SDK marshals this entire structure into JSON and POSTs it to the /v1/messages endpoint.

When the response arrives, the SDK unmarshals it into the Message struct. The Content field is a slice of interface types. That design lets Claude return mixed media in a single response. You must check the concrete type before accessing fields. A blind type assertion will panic if the response contains an image or a tool use block instead of plain text.

Context is the lifeline. Pass it down or the request hangs forever.

Building a production-ready wrapper

Real applications need error handling, type safety, and reusable logic. Here is a helper function that wraps the SDK call, validates the response type, and returns a clean string or a wrapped error.

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/anthropics/anthropic-sdk-go"
)

// AskClaude sends a prompt and returns the text response or an error.
func AskClaude(ctx context.Context, client *anthropic.Client, prompt string) (string, error) {
	// MaxTokens limits output length. Adjust based on your use case.
	// System prompt sets baseline behavior for this call.
	resp, err := client.Messages.Create(ctx, anthropic.MessageParam{
		Model: "claude-3-5-sonnet-20241022",
		MaxTokens: 2048,
		System: []anthropic.MessageSystemContentParam{
			{Type: "text", Text: "Answer concisely."},
		},
		Messages: []anthropic.MessageParam{
			{
				Role: "user",
				Content: []anthropic.MessageContentParam{
					{Type: "text", Text: prompt},
				},
			},
		},
	})
	// Wrap SDK errors so callers can distinguish network failures from API errors.
	if err != nil {
		return "", fmt.Errorf("claude request failed: %w", err)
	}

	// Check slice bounds before accessing the first content block.
	if len(resp.Content) == 0 {
		return "", errors.New("empty response from claude")
	}

	// Type switch handles mixed content safely.
	// TextBlock contains the generated string. Other types are ignored.
	switch block := resp.Content[0].(type) {
	case anthropic.TextBlock:
		return block.Text, nil
	default:
		return "", fmt.Errorf("unexpected content type: %T", block)
	}
}

The function accepts context.Context as the first parameter. That matches the Go community convention for long-lived or network-bound operations. Callers can attach deadlines or cancellation channels before invoking it. The error handling wraps the original error with fmt.Errorf and %w. That preserves the error chain for debugging while adding context. The type switch replaces the fragile type assertion. It explicitly handles the TextBlock case and rejects everything else with a clear message.

Type assertions are brittle. Check the block type before you cast.

Where things break

The SDK abstracts HTTP, but the network is still unreliable. You will encounter timeouts, rate limits, and malformed responses. The compiler catches structural mistakes early. If you pass a string where the SDK expects a slice of content parameters, you get cannot use "hello" (untyped string constant) as []anthropic.MessageContentParam value in struct literal. If you forget to import the SDK package, the compiler rejects the file with undefined: anthropic. These are helpful. Runtime failures require different handling.

Context cancellation is the most common silent failure. If you derive a context with a deadline and the model takes longer than expected, the SDK returns context deadline exceeded. If you cancel the context from another goroutine, you get context canceled. Both are normal. You should catch them and decide whether to retry or fail fast. Do not treat them as bugs. They are flow control.

Rate limits trigger HTTP 429 responses. The SDK translates them into typed errors. If you hammer the API without backing off, your quota drains and your requests get rejected. Implement exponential backoff or use a queue to smooth traffic spikes. The worst goroutine bug is the one that never logs. Always record rate limit hits and retry attempts.

Streaming responses behave differently. The non-streaming Create method blocks until the full response arrives. That works for short answers. Long generations or interactive terminals need client.Messages.Stream. The stream returns a channel that yields chunks as they arrive. You must range over the channel and handle io.EOF when the stream closes. Forgetting to drain the channel leaks goroutines and holds open HTTP connections.

Rate limits are real. Back off gracefully or your quota vanishes.

Picking the right approach

Use the official anthropic-go-sdk when you want type safety, automatic retries, and minimal boilerplate. Use a raw http.NewRequest when you need custom headers, non-standard routing, or want to avoid a third-party dependency. Use the streaming client when your UI or CLI must display tokens as they arrive. Use a mock interface when you are writing unit tests and cannot afford real API calls. Use plain sequential code when you do not need concurrency: the simplest thing that works is usually the right thing.

Pick the tool that matches your latency budget.

Where to go next