← Blog

Claude API Streaming Response in Golang: A Guide

2026-10-16 · 4 min read · SubToAPI Team

Streaming responses from the Claude API in Go means consuming a server-sent events (SSE) stream over HTTP and parsing each data: line as it arrives, rather than waiting for the full response body. Go doesn't have a built-in SSE client, so you typically read the response body with bufio.Scanner or bufio.Reader, split on newlines, and decode each JSON chunk as Claude sends it.

This matters because without streaming, your Go service blocks until Claude finishes generating the entire response — which can take 10-30 seconds for longer completions. Streaming lets you forward tokens to a frontend, a CLI, or a websocket connection in near real-time, which is essential for any chat-like UI or long-form generation task.

How Claude's streaming format works

Claude's API streams responses as SSE events. Each event has a type field such as message_start, content_block_delta, message_delta, and message_stop. The actual text tokens arrive inside content_block_delta events, nested under delta.text.

A raw stream looks like this:

event: message_start
data: {"type":"message_start","message":{"id":"msg_123","model":"claude-3-5-sonnet-20241022","usage":{"input_tokens":10}}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" world"}}

event: message_stop
data: {"type":"message_stop"}

Your Go code needs to parse each data: line, ignore the event: lines if you don't need them, and accumulate or forward the delta.text fragments.

Setting up the HTTP request

You need "stream": true in your request body and the response must be read incrementally, not buffered with io.ReadAll.

package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
)

type deltaEvent struct {
	Type  string `json:"type"`
	Delta struct {
		Text string `json:"text"`
	} `json:"delta"`
}

func streamClaude(apiKey, prompt string) error {
	body := map[string]any{
		"model":      "claude-3-5-sonnet-20241022",
		"max_tokens": 1024,
		"stream":     true,
		"messages": []map[string]string{
			{"role": "user", "content": prompt},
		},
	}

	payload, _ := json.Marshal(body)
	req, err := http.NewRequest("POST", "https://api.anthropic.com/v1/messages", bytes.NewReader(payload))
	if err != nil {
		return err
	}

	req.Header.Set("x-api-key", apiKey)
	req.Header.Set("anthropic-version", "2023-06-01")
	req.Header.Set("content-type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	scanner := bufio.NewScanner(resp.Body)
	// Default scanner buffer may be too small for large lines
	buf := make([]byte, 0, 64*1024)
	scanner.Buffer(buf, 1024*1024)

	for scanner.Scan() {
		line := scanner.Text()
		if !strings.HasPrefix(line, "data: ") {
			continue
		}
		raw := strings.TrimPrefix(line, "data: ")

		var evt deltaEvent
		if err := json.Unmarshal([]byte(raw), &evt); err != nil {
			continue
		}
		if evt.Type == "content_block_delta" {
			fmt.Print(evt.Delta.Text)
		}
	}
	return scanner.Err()
}

A few things to watch out for:

Forwarding the stream to your own clients

If you're building a Go backend that proxies Claude's stream to a browser or mobile client, the common pattern is to re-emit SSE events on your own HTTP response using http.Flusher:

func handleChat(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "streaming unsupported", http.StatusInternalServerError)
		return
	}

	// ... inside your token loop:
	fmt.Fprintf(w, "data: %s\n\n", token)
	flusher.Flush()
}

This gives you a clean separation: your Go service talks to Claude over one SSE connection and re-streams tokens to the browser over a second SSE connection, which is the standard pattern for chat UIs built with Go backends.

Handling errors mid-stream

Streams can fail partway through — a dropped connection, a rate limit hit after the first few tokens, or a malformed JSON chunk. Always check scanner.Err() after the loop ends, and consider tracking whether you received a message_stop event before the stream closed. If you didn't, treat it as an incomplete response and retry with backoff rather than assuming the generation finished cleanly.

Skipping the plumbing with SubToAPI

If you're maintaining this SSE parsing, buffer management, and retry logic across several Go services, it adds up. SubToAPI turns your Claude access into a standard HTTPS API with application keys (sub_live_...), so your Go code hits one consistent streaming endpoint with the same request/response shape regardless of which underlying model or account is used. You get usage metadata per request and team seats for managing multiple services, without rewriting your scanner logic for every client. Check the streaming docs and quickstart to see the request format, or start a trial at signup.

questions

Does the Claude API support streaming natively, or do I need a special client for Go? Claude's API streams via standard SSE over HTTPS. Go has no built-in SSE client, but bufio.Scanner combined with a larger buffer size is enough to parse the data: lines manually — no third-party library required.

Why does my Go stream appear to cut off early? This is usually a scanner buffer limit. The default 64KB buffer can truncate long lines. Call scanner.Buffer() with a bigger capacity, and verify you're checking for the message_stop event rather than just closing on EOF.

Can I use goroutines to process streamed tokens concurrently? You can forward tokens to a channel and process them in a separate goroutine, but keep the scanning loop itself single-threaded since SSE lines must be read in order. Use a buffered channel so slow consumers don't block the read loop.

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 →