Claude API Integration with Go: A Practical Guide
Integrating the Claude API into a Go application means making authenticated HTTPS requests to Anthropic's /v1/messages endpoint, since there's no official Go SDK maintained by Anthropic. This isn't a limitation in practice — Go's standard library makes REST integration straightforward, and you can have a working chat completion or streaming call running in under 50 lines of code.
This guide covers the two realistic paths: calling Claude directly from Go with net/http, and using an API gateway like SubToAPI so your Go service talks to a stable, versioned HTTPS endpoint instead of managing raw provider auth and quirks yourself.
Why Go developers hit friction with Claude integration
Go's ecosystem favors typed structs and explicit error handling, but the Claude API responses are JSON with nested content blocks (text, tool use, tool results), which means you need to model the response shape carefully or use map[string]interface{} and lose type safety. Common friction points:
- No official Go client library, so you write and maintain your own request/response structs
- Streaming responses use server-sent events (SSE), which Go's
net/httpsupports but requires manual chunk parsing - Tool use responses interleave text and tool_use blocks in the same array, requiring a discriminated-union-style parser
- Rate limits and retries need to be handled manually unless you build a small wrapper
None of this is hard, but it's boilerplate you'll write once and then maintain forever — which is exactly the gap a hosted API gateway is designed to fill.
Direct integration: calling Claude from Go
Here's a minimal, working example using only the standard library:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type ChatRequest struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
Messages []Message `json:"messages"`
}
type ContentBlock struct {
Type string `json:"type"`
Text string `json:"text"`
}
type ChatResponse struct {
ID string `json:"id"`
Content []ContentBlock `json:"content"`
Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
} `json:"usage"`
}
func main() {
reqBody := ChatRequest{
Model: "claude-sonnet-4",
MaxTokens: 1024,
Messages: []Message{
{Role: "user", Content: "Summarize the Go garbage collector 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)
var chatResp ChatResponse
if err := json.Unmarshal(body, &chatResp); err != nil {
panic(err)
}
for _, block := range chatResp.Content {
fmt.Println(block.Text)
}
}
This works fine for a prototype. In production you'll want to add: context timeouts, exponential backoff on 429/529 responses, structured logging of Usage, and a way to distinguish per-application usage if multiple internal services share one Claude account.
Streaming responses in Go
For chat UIs or long completions, streaming avoids the "wait for the whole response" problem. Set "stream": true in the request body and read the response body as SSE chunks:
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "data: ") {
data := strings.TrimPrefix(line, "data: ")
if data == "[DONE]" {
break
}
var event map[string]interface{}
json.Unmarshal([]byte(data), &event)
fmt.Print(event["delta"])
}
}
Go's bufio.Scanner handles line-by-line reads well, but note the default buffer size can be too small for large events — increase it with scanner.Buffer() if you see truncated JSON errors.
Handling tool use and structured output
Tool use responses return content blocks with type: "tool_use" alongside or instead of type: "text". In Go, parse the array as []json.RawMessage first, then switch on a type field per block before unmarshaling into the specific struct. This two-pass parsing pattern is the cleanest way to handle Claude's mixed content arrays without losing type safety.
Simplifying integration with SubToAPI
If you're building an internal tool, an AI feature inside a Go backend, or a multi-team product where several services need Claude access with separate quotas and keys, writing and maintaining all of the above per-service adds up fast. SubToAPI turns your existing Claude access into a standard HTTPS API with per-application sub_live_... keys, so each Go service authenticates independently while usage rolls up into one dashboard.
From your Go code, the request shape is nearly identical to calling Anthropic directly — you're just pointing at a different host and using your SubToAPI key:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Summarize the Go garbage collector in two sentences."}]
}'
Because it's the same JSON contract, you can reuse the exact Go structs from the direct-integration example above — swap the host and header, nothing else changes. Streaming and tool use work the same way; see /docs/streaming and /docs/tools for request formats, and /docs/quickstart for the full setup. If you're issuing separate keys per Go microservice or per team, /signup gets you a free trial to test it against your existing codebase before switching over.
Production considerations
- Timeouts: set a
context.Contextwith a deadline on every request; Claude calls can take several seconds for longer generations - Retries: implement backoff for 429 and 5xx responses; don't retry 400-level errors blindly
- Concurrency: Go's goroutines make it easy to fan out multiple Claude calls, but respect rate limits with a worker pool or semaphore
- Observability: log
usage.input_tokensandusage.output_tokensper request so you can track cost by endpoint or team
questions
Does Anthropic provide an official Go SDK for Claude? No. Anthropic maintains official SDKs for Python and TypeScript; Go integration is done via direct HTTP calls to the REST API or through a gateway that exposes a stable HTTPS interface.
Can I stream Claude responses in a Go web server? Yes. Read the response body as SSE using bufio.Scanner, parse each data: line as JSON, and forward deltas to your client over a WebSocket or chunked HTTP response.
Is it worth using an API gateway instead of calling Claude directly from Go? If you only have one service, direct calls are simpler. If you have multiple Go services, teams, or need per-application usage tracking, a gateway like SubToAPI removes the need to build and maintain key management, quotas, and dashboards yourself — see /docs for details.