← Blog

Claude API Go Client Library: A Working Example

2026-10-11 · 5 min read · SubToAPI Team

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:

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.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →