Claude API Golang Client Implementation Guide
If you're looking to call the Claude API from Go, the short answer is: there's no official Anthropic SDK for Go (unlike Python and TypeScript), so you build a thin client around net/http yourself. It's not complicated — Claude's API is JSON over HTTPS with a handful of required headers — but there are a few details (streaming, tool use, message formatting) that trip people up if you're doing it from scratch. This article walks through a working implementation you can drop into a project today.
We'll cover the core request/response cycle, structuring the client as a reusable Go package, handling streaming responses, and dealing with retries and rate limits. At the end, we also look at when it makes sense to skip writing this client against Anthropic directly and instead point it at a gateway like SubToAPI, which exposes the same message format over a stable HTTPS API.
Minimal Go client structure
Start with a struct that holds your API key and base URL, plus a single method for sending messages.
package claude
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type Client struct {
APIKey string
BaseURL string
HTTPClient *http.Client
}
func NewClient(apiKey string) *Client {
return &Client{
APIKey: apiKey,
BaseURL: "https://api.anthropic.com/v1",
HTTPClient: &http.Client{},
}
}
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type MessagesRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
MaxTokens int `json:"max_tokens"`
Stream bool `json:"stream,omitempty"`
}
type MessagesResponse struct {
ID string `json:"id"`
Content []struct {
Type string `json:"type"`
Text string `json:"text"`
} `json:"content"`
Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
} `json:"usage"`
}
func (c *Client) CreateMessage(req MessagesRequest) (*MessagesResponse, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
httpReq, err := http.NewRequest("POST", c.BaseURL+"/messages", bytes.NewReader(body))
if err != nil {
return nil, err
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("x-api-key", c.APIKey)
httpReq.Header.Set("anthropic-version", "2023-06-01")
resp, err := c.HTTPClient.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("claude api error: %s", string(data))
}
var out MessagesResponse
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return &out, nil
}
This covers the standard case: send a message, parse the response. For most backend jobs — classification, summarization, batch processing — this is all you need.
Handling streaming in Go
Streaming is where a Go implementation gets more involved, because you need to read Server-Sent Events off the response body incrementally instead of unmarshaling a single JSON blob.
func (c *Client) StreamMessage(req MessagesRequest, onChunk func(text string)) error {
req.Stream = true
body, _ := json.Marshal(req)
httpReq, err := http.NewRequest("POST", c.BaseURL+"/messages", bytes.NewReader(body))
if err != nil {
return err
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("x-api-key", c.APIKey)
httpReq.Header.Set("anthropic-version", "2023-06-01")
resp, err := c.HTTPClient.Do(httpReq)
if err != nil {
return err
}
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "data: ") {
continue
}
payload := strings.TrimPrefix(line, "data: ")
var event struct {
Type string `json:"type"`
Delta struct {
Text string `json:"text"`
} `json:"delta"`
}
if err := json.Unmarshal([]byte(payload), &event); err != nil {
continue
}
if event.Type == "content_block_delta" {
onChunk(event.Delta.Text)
}
}
return scanner.Err()
}
The key gotcha: bufio.Scanner's default buffer size can be too small for some event payloads. If you hit "token too long" errors, raise it with scanner.Buffer().
Retries, rate limits, and timeouts
A production Go client needs backoff on 429s and 529s (overloaded), and a context-aware timeout. Wrap the request call:
func (c *Client) withRetry(ctx context.Context, fn func() (*http.Response, error)) (*http.Response, error) {
var resp *http.Response
var err error
for attempt := 0; attempt < 4; attempt++ {
resp, err = fn()
if err == nil && resp.StatusCode != 429 && resp.StatusCode != 529 {
return resp, nil
}
wait := time.Duration(math.Pow(2, float64(attempt))) * time.Second
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(wait):
}
}
return resp, err
}
Plug this in around c.HTTPClient.Do(httpReq). Also set req.Header.Set("anthropic-version", ...) once as a constant, since Anthropic deprecates old API versions and you want that in one place, not scattered across methods.
Tool use and multi-turn state
If your Go service needs Claude to call tools (function calling), the request body gains a tools field and the response may return a tool_use content block instead of plain text. Your struct needs to accommodate both:
type ContentBlock struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input json.RawMessage `json:"input,omitempty"`
}
Then switch on block.Type in your handler loop. Multi-turn conversation state (keeping the messages array growing across turns) is your responsibility — Claude's API is stateless per request, so your Go struct needs to own the conversation history and append both the assistant's reply and any tool results before the next call.
When to skip the custom client
Writing this client is a reasonable afternoon of work, and plenty of Go teams do exactly that. Where it gets tedious is maintaining it: tracking anthropic-version header changes, handling new content block types, re-implementing retry/backoff correctly, and giving non-engineers (support, ops, finance) visibility into usage and spend per key.
SubToAPI sits in front of Claude and exposes the same /v1/messages-style schema, so the Go code above works against it with only the base URL and auth header changed — Authorization: Bearer $SUBTOAPI_KEY instead of x-api-key. You get per-key usage metadata, team seats, and streaming without maintaining the operational layer yourself. Check the quickstart or messages docs if you want to point your existing Go client at it, and pricing for plan details (Solo €9, Team €19/seat, Scale €49/seat, free trial at signup).
questions
Is there an official Anthropic Go SDK? No. Anthropic maintains official SDKs for Python and TypeScript/JavaScript. For Go, you implement a client against the HTTP API directly, as shown above, or use a community-maintained package.
What headers does a Go Claude client need to set? At minimum: Content-Type: application/json, x-api-key with your API key, and anthropic-version set to the API version you're targeting. Missing or outdated anthropic-version headers are the most common source of unexpected errors.
How do I handle streaming responses in Go without a buffer overflow? Use bufio.Scanner on the response body and increase its buffer size with scanner.Buffer() before reading, since default buffer limits can be too small for longer SSE payloads from content_block_delta events.