Claude API Golang Integration Guide
Calling Claude from a Go Service
If you're building a backend in Go and want to add Claude for text generation, summarization, or tool-calling, you have two paths: write a small HTTP client against Anthropic's REST API directly, or route through a proxy that gives you a stable key and simpler auth. This guide covers both, with working Go code for requests, streaming, and tool use.
The Claude API is a plain JSON-over-HTTPS interface, so there's no official Go SDK required — net/http and encoding/json are enough to get a working integration in under 50 lines. The friction usually shows up later: streaming parsers, retry logic, and multi-key management across environments. We'll address all three.
Setting Up the Basic Client
At minimum you need an API key and a struct that matches the Messages API shape. Here's a minimal non-streaming call:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type MessagesRequest struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
Messages []Message `json:"messages"`
}
func main() {
reqBody := MessagesRequest{
Model: "claude-3-5-sonnet-20241022",
MaxTokens: 1024,
Messages: []Message{
{Role: "user", Content: "Explain goroutines in two sentences."},
},
}
payload, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", "https://api.anthropic.com/v1/messages", bytes.NewBuffer(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-api-key", os.Getenv("ANTHROPIC_API_KEY"))
req.Header.Set("anthropic-version", "2023-06-01")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
This works, but there are a few Go-specific gotchas worth fixing early.
Timeouts and Context
The default http.Client has no timeout, which means a hung connection blocks forever. Always use context.WithTimeout and pass it through:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload))
For production services, wire this to the incoming request's context so a cancelled upstream call doesn't leave a dangling Claude request running.
Streaming Responses in Go
Claude supports server-sent events for streaming tokens as they're generated. In Go, this means reading the response body line by line instead of decoding a single JSON blob. Set "stream": true in the request body, then parse with bufio.Scanner:
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "data: ") {
continue
}
data := strings.TrimPrefix(line, "data: ")
if data == "[DONE]" {
break
}
var event map[string]any
if err := json.Unmarshal([]byte(data), &event); err == nil {
if delta, ok := event["delta"].(map[string]any); ok {
if text, ok := delta["text"].(string); ok {
fmt.Print(text)
}
}
}
}
This is the part most Go teams reinvent for every project — parsing event types, handling partial JSON chunks, and buffering correctly. If you'd rather not maintain this parser, SubToAPI exposes the same streaming contract over a stable endpoint, and the streaming docs show the exact event shapes to expect so your Go scanner logic doesn't need to change based on which backend model is running.
Tool Use (Function Calling) from Go
Tool use requires defining a JSON schema for each tool and handling the tool_use content block type in the response. In Go, model this with a generic struct since content blocks vary in shape:
type ContentBlock struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
Name string `json:"name,omitempty"`
Input json.RawMessage `json:"input,omitempty"`
ID string `json:"id,omitempty"`
}
type MessagesResponse struct {
Content []ContentBlock `json:"content"`
StopReason string `json:"stop_reason"`
}
When StopReason == "tool_use", loop over Content, find the block with Type == "tool_use", unmarshal Input into your tool's parameter struct, run the function, and send the result back as a tool_result message in the next call. This request-response loop is identical regardless of which language you're in — the Go-specific part is just getting the json.RawMessage unmarshaling right since tool inputs are untyped JSON until you know which tool was called. SubToAPI's tool use guide documents the exact block structure if you want to cross-check your struct tags.
Error Handling and Retries
Claude's API returns standard HTTP status codes: 429 for rate limits, 529 for overloaded, 400 for bad requests. A simple retry wrapper with exponential backoff covers most production needs:
func doWithRetry(req *http.Request, maxRetries int) (*http.Response, error) {
var resp *http.Response
var err error
for i := 0; i < maxRetries; i++ {
resp, err = http.DefaultClient.Do(req)
if err == nil && resp.StatusCode < 500 && resp.StatusCode != 429 {
return resp, nil
}
time.Sleep(time.Duration(math.Pow(2, float64(i))) * time.Second)
}
return resp, err
}
Note that req.Body can only be read once, so if you're retrying the same request, clone the body or rebuild the request inside the loop.
When to Use a Proxy Instead
Direct integration is fine for a single service with one key. It gets harder once you have multiple Go services, staging and production environments, and a need to see spend per service without building your own logging layer.
SubToAPI sits in front of Claude and gives each application its own sub_live_ key, with usage metadata per request and a dashboard for the team. From Go, the integration code barely changes — you point the same http.Client logic at https://api.subtoapi.app/v1/messages and swap the auth header:
req.Header.Set("Authorization", "Bearer "+os.Getenv("SUBTOAPI_KEY"))
Everything above — streaming parser, tool-use loop, retry wrapper — works unmodified. The quickstart and messages API reference cover the exact request/response shapes, and pricing runs from the free trial on signup up through Solo, Team, and Scale plans depending on seats. You can sign up and get a key in a couple of minutes if you want to test the swap without touching your client code.
Questions
Do I need the official Anthropic SDK to use Claude from Go? No. The API is plain JSON over HTTPS, so net/http and encoding/json are sufficient. There's no official Go SDK from Anthropic, but the request/response shapes are simple enough that a hand-written client is often less overhead than wrapping a generated one.
How do I handle streaming in Go without a library? Use bufio.Scanner on the response body, filter lines prefixed with data: , and unmarshal each JSON chunk individually. Watch for the [DONE] sentinel and partial chunks that arrive mid-line under slow network conditions.
What's the fastest way to test tool use from Go? Define your tool schema as a Go struct, marshal it into the tools array of your request, then branch on stop_reason == "tool_use" in the response to extract and execute the call. Testing against a stable proxy endpoint like SubToAPI's /v1/messages lets you verify the loop without worrying about model-specific response quirks during development.