Claude API в Go: запросы через net/http с обработкой SSE
Клиент для Claude и GPT на стандартной библиотеке Go: обычный запрос, разбор ошибок, потоковый ответ через SSE и параллельные запросы с ограничением.
3 мин · · разработка
Если бэкенд написан на Go, официальный SDK для работы с Claude API не обязателен: хватает пакетов net/http и encoding/json из стандартной библиотеки. Ниже — рабочие примеры для Zukko: обычный запрос, разбор ошибок, потоковый ответ (SSE) и параллельные запросы через горутины. Всё идёт через OpenAI-совместимый эндпоинт, поэтому тот же код работает и с Claude, и с GPT, и с GLM — меняется только поле model.
Базовый запрос к Claude API на Go
Ключ берём из переменной окружения ZUKKO_API_KEY, адрес — https://api.zukko.pro/v1/chat/completions.
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
context.WithTimeout ограничивает весь запрос: если ответ не пришёл за 60 секунд, client.Do вернёт ошибку context deadline exceeded, и зависший запрос не заблокирует обработчик. Для длинных ответов увеличьте таймаут. Поля usage.prompt_tokens и usage.completion_tokens — это то, за что вы платите; их удобно писать в лог.
Обработка ошибок
Шлюз возвращает понятные коды, и от кода зависит, есть ли смысл повторять запрос. Удобно завести свой тип ошибки:
type APIError struct {
Status int
Body string
}
func (e *APIError) Error() string {
switch e.Status {
case 401:
return "неверный API-ключ (401)"
case 402:
return "недостаточно средств на балансе (402)"
case 403:
return "модель не разрешена для этого ключа (403)"
case 413:В функции ask замените fmt.Errorf("API %d: %s", ...) на &APIError{Status: resp.StatusCode, Body: string(data)}, а снаружи проверяйте тип через errors.As. Код 403 означает, что модель не входит в список разрешённых для ключа: список задаётся при создании ключа в кабинете. Тело ошибки приходит в формате {"error": {"message": ..., "code": ...}}.
Стриминг ответа через SSE
При "stream": true сервер присылает ответ кусками в формате Server-Sent Events: строки вида data: {...}, разделённые пустыми строками, в конце — data: [DONE]. Последний чанк может прийти со статистикой usage и пустым массивом choices, поэтому длину массива нужно проверять.
package main
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"Частая ошибка — сравнивать всю строку с [DONE]: на самом деле приходит data: [DONE], поэтому сначала отрезаем префикс. Буфер сканера увеличен до 1 МБ, чтобы длинное событие не оборвало чтение с ошибкой token too long.
Параллельные запросы через горутины
Go удобен для пакетной обработки: каждая задача — отдельная горутина. Но запускать сотню запросов разом не стоит — получите 429. Ограничьте число одновременных запросов семафором на буферизированном канале. Замените main из первого примера на этот код и добавьте "sync" в import:
func main() {
apiKey := os.Getenv("ZUKKO_API_KEY")
client := &http.Client{Timeout: 90 * time.Second}
ctx := context.Background()
prompts := []string{
"Что такое горутина?",
"Что такое канал в Go?",
"Чем буферизированный канал отличается от небуферизированного?",
}
results := make([]string, len(prompts)) // порядок ответов совпадает с порядком вопросов
sem := make(chan struct{}, 3) // не больше 3 запросов одновременно
var wg sync.WaitGroupКаждая горутина пишет в свой элемент среза, поэтому мьютекс не нужен, а результаты не перемешиваются. Размер семафора подбирайте опытным путём: начните с 3–5 и увеличивайте, пока не появятся ответы 429.
Стоимость запроса из заголовков ответа
В обычных (не потоковых) ответах шлюз добавляет заголовок X-Zukko-Cost-Micros — стоимость запроса в микродолларах (1 000 000 = $1) — и X-Fetch-Balance-Cents с остатком баланса. Подробно — в разделе о лимитах ключей и кэше. Внутри ask после проверки статуса (нужен импорт strconv):
if micros, err := strconv.ParseInt(resp.Header.Get("X-Zukko-Cost-Micros"), 10, 64); err == nil {
log.Printf("стоимость запроса: $%.6f", float64(micros)/1e6)
}Так сервис может сам считать расход и останавливать пакетную обработку до того, как баланс закончится.
Частые вопросы
Где взять API-ключ?
Создайте ключ в кабинете Zukko и сохраните его в переменную окружения ZUKKO_API_KEY. Полный ключ показывается один раз — при создании. Для каждого сервиса удобно заводить отдельный ключ с дневным лимитом.
Почему 404 на /v1/chat/completions?
Проверьте адрес: полный путь — https://api.zukko.pro/v1/chat/completions. Если вы собираете URL из базового адреса, база для OpenAI-формата — https://api.zukko.pro/v1. Справка по формату запроса — в документации Chat Completions.
Как ограничить длину ответа?
Полем max_tokens в теле запроса. Модель остановится на этом лимите, даже если не закончила мысль, — в таком случае finish_reason в ответе будет length.
Какие модели можно указать?
Любую из доступных: claude-sonnet-5, claude-opus-5-5, gpt-5.6-sol, gpt-5.6-luna, glm-5.3, glm-5.3-flash. Актуальный список — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете. Какая модель лучше подходит для вашей задачи, проверьте на своих данных: прогоните 20–30 типовых запросов через две-три модели и сравните результат и стоимость.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Свой прокси к нейросети на Node.js: не показывать API-ключ во фронтендеНебольшой сервер на Express между вашим фронтендом и API нейросети: ключ остаётся на сервере, а модели, длина запроса и частота ограничены.
- DeepSeek API на Python: запросы, стриминг, JSON и обработка ошибокГотовый код для DeepSeek V4.1 Flash и V4 Pro на OpenAI SDK: запрос, стриминг, разбор JSON и аккуратная обработка ошибок.
- Промпт-инжиниринг: примеры и 12 готовых шаблонов для работыДвенадцать шаблонов промптов для рабочих задач: что в них написано, почему именно так и сколько стоит один вызов по API.