Claude API Go Client Library: A Working Example
Anthropic doesn't publish an official Go SDK for the Claude API, which is the first thing most Go developers discover when they search for one. The good news is that you don't need one — the Claude API is plain HTTPS with JSON bodies, and a working Go client is about 60 lines of code using only the standard library.
This article walks through exactly that: a minimal but production-usable Go client for Claude, including streaming support, error handling, and a note on where a managed gateway like SubToAPI fits in if you'd rather not maintain key rotation and billing logic yourself.
Why there's no "official" Go client
Anthropic maintains SDKs for Python and TypeScript/JavaScript. Go isn't on that list, but the API itself is a straightforward REST interface — a POST to /v1/messages with a JSON payload and an x-api-key (or Authorization) header. That means Go's net/http and encoding/json packages are all you actually need. You'll see a handful of community-maintained Go wrappers on GitHub, but for most projects, owning a thin client yourself is less risky than depending on an unofficial package that might lag behind API changes.
Building the client
Start with request and response types that mirror the Messages API shape:
package claude
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type MessageRequest struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
Messages []Message `json:"messages"`
}
type MessageResponse 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"`
}
type Client struct {
apiKey string
baseURL string
http *http.Client
}
func NewClient(apiKey string) *Client {
return &Client{
apiKey: apiKey,
baseURL: "https://api.anthropic.com/v1/messages",
http: &http.Client{},
}
}
Now the method that actually sends a request:
func (c *Client) SendMessage(req MessageRequest) (*MessageResponse, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
httpReq, err := http.NewRequest("POST", c.baseURL, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
httpReq.Header.Set("x-api-key", c.apiKey)
httpReq.Header.Set("anthropic-version", "2023-06-01")
httpReq.Header.Set("content-type", "application/json")
resp, err := c.http.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 result MessageResponse
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}
Usage looks like this:
func main() {
client := NewClient("your-api-key")
resp, err := client.SendMessage(MessageRequest{
Model: "claude-3-5-sonnet-20241022",
MaxTokens: 1024,
Messages: []Message{
{Role: "user", Content: "Explain goroutines in two sentences."},
},
})
if err != nil {
panic(err)
}
for _, block := range resp.Content {
fmt.Println(block.Text)
}
}
This is a complete, dependency-free client. For most backend services, prototypes, and CLI tools, that's genuinely enough.
Adding streaming
Claude supports server-sent events for streaming responses, which matters for anything with a user-facing chat interface. In Go, you read the response body line by line and parse data: lines as JSON events:
func (c *Client) StreamMessage(req MessageRequest, onChunk func(string)) error {
req.Model = req.Model // stream field set below
body, _ := json.Marshal(struct {
MessageRequest
Stream bool `json:"stream"`
}{req, true})
httpReq, _ := http.NewRequest("POST", c.baseURL, bytes.NewBuffer(body))
httpReq.Header.Set("x-api-key", c.apiKey)
httpReq.Header.Set("anthropic-version", "2023-06-01")
httpReq.Header.Set("content-type", "application/json")
resp, err := c.http.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: ") {
var event struct {
Delta struct {
Text string `json:"text"`
} `json:"delta"`
}
payload := strings.TrimPrefix(line, "data: ")
if json.Unmarshal([]byte(payload), &event) == nil {
onChunk(event.Delta.Text)
}
}
}
return scanner.Err()
}
This handles the common case well, but production streaming code should also account for content_block_start/stop events, ping keep-alives, and reconnect logic if a connection drops mid-stream.
Error handling and retries
Two things trip up Go implementations specifically:
- Rate limits return HTTP 429 with a
retry-afterheader. WrapSendMessagewith exponential backoff rather than retrying immediately. - Context cancellation — use
http.NewRequestWithContextinstead ofhttp.NewRequestso you can enforce timeouts cleanly with Go'scontextpackage, especially in HTTP handlers where the caller's request can be cancelled.
When to use a gateway instead
Writing your own Go client is the right call when you're integrating Claude into a single service you fully control. It gets more complicated once you have multiple applications, multiple environments, and multiple people who each need their own key with visibility into usage and cost.
That's the gap SubToAPI fills: it turns your existing Claude access into a standard HTTPS API with per-application keys (sub_live_...), streaming, tool use, and usage metadata, so your Go client code stays almost identical — you just point it at a different base URL and bearer token. Seats and keys are managed from one dashboard instead of scattered across .env files.
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}'
Your Go Client struct from above works unchanged against SubToAPI — swap the baseURL and header name. Check /docs/quickstart for setup details, /docs/messages for the request schema, /docs/streaming for SSE specifics, and /pricing for plan comparisons (Solo, Team, Scale) if you're rolling this out across a team rather than a single project.
FAQ
Is there an official Claude API Go SDK?
No. Anthropic officially supports Python and TypeScript SDKs. For Go, developers either write a thin net/http-based client (as shown above) or use an unofficial community package.
Can I use the same Go client code with SubToAPI?
Yes. SubToAPI's API shape matches the Claude Messages API, so your request/response structs and HTTP logic stay the same — you only change the base URL and the bearer token used in the Authorization header.
How do I handle streaming responses in Go?
Read the HTTP response body with a bufio.Scanner, parse lines prefixed with data: as JSON, and extract the delta.text field from each event. Handle ping and content_block_stop events to avoid processing errors on non-text chunks.