Go SDK
SpicyAPI 공식 Go SDK(github.com/spicyapi-ai/spicy-go) 안내입니다. 설치부터 견적과 첫 생성, 로컬 파일 업로드, 작업 기록 조회, 모든 오류 코드 처리까지 다룹니다.
spicy-go는 SpicyAPI의 공식 Go SDK입니다. 이미지·영상·오디오 생성을 *spicy.Client 하나 뒤에 놓습니다. API 키를 대신 붙여 주고, 응답 봉투를 타입이 있는 구조체로 디코딩하고, 상한이 있는 백오프로 작업을 기다리고, 로컬 파일을 한 번의 호출로 업로드하고, 모든 실패에 errors.As로 받아 낼 수 있는 단일 오류 타입을 줍니다.
Go 1.23 이상이 필요하고 표준 라이브러리만 씁니다. 일부러 그렇게 만들었습니다. 이 패키지는 내 의존성 그래프 안으로 들어가고, 이 패키지가 끌고 들어오는 모듈 하나하나가 내가 감사해야 할 대상을 하나씩 늘리기 때문입니다.
나에게 맞는 도구인가요?
| 하고 싶은 일 | 쓸 도구 |
|---|---|
| 내 Go 코드에서 SpicyAPI 호출하기 | 이 SDK |
| Node.js나 TypeScript에서 호출하기 | TypeScript SDK |
| Python, PHP, Java에서 호출하기 | Python, PHP, Java SDK |
| 코드를 짜지 않고 터미널에서 생성하기 | CLI |
| AI 어시스턴트가 대신 SpicyAPI를 호출하게 하기 | MCP 서버와 Agent Skill |
| 채팅·텍스트 모델 쓰기 | OpenAI, Anthropic, Gemini 형식을 지원하는 기존 Go 클라이언트. 텍스트와 스트리밍을 보세요 |
설치
Go는 모듈을 저장소에서 바로 해석하므로 레지스트리 단계도, 기다릴 것도 없습니다. 이 명령은 오늘 바로 작동합니다.
go get github.com/spicyapi-ai/spicy-go그런 다음 키를 환경 변수에 넣으세요. 그래야 코드에도, 셸 기록 파일에도 키가 남지 않습니다.
export SPICY_API_KEY="sk-spicy-..."첫 결과
가격을 묻고, 판단하고, 작업을 만들고, 기다리고, 파일을 읽습니다. 견적은 무료이며 아무것도 예약하지 않습니다.
client, err := spicy.New(spicy.Options{}) // 환경 변수에서 SPICY_API_KEY를 읽습니다
if err != nil {
return err
}
request := spicy.CreateTaskInput{
Model: "MODEL_ID_FROM_CATALOG",
Input: map[string]any{"prompt": "a lantern in fog"},
}
quote, err := client.Quote(ctx, request)
if err != nil {
return err
}
log.Printf("this will cost at most %s", quote.MaxCharge)
// 보내기 전에 이 키를 내 쪽 작업 기록 옆에 저장해 두세요. 이 키의 존재 이유가
// 바로 응답을 잃어버린 그 장애 뒤에도 남아 있는 것입니다.
key, err := spicy.NewIdempotencyKey()
if err != nil {
return err
}
request.Model = quote.Model
request.QuoteID = quote.QuoteID
request.ExpectedCost = quote.EstimatedCost
task, err := client.CreateTask(ctx, request, key, spicy.CreateTaskOptions{})
if err != nil {
return err
}
final, err := client.WaitForTerminal(ctx, task.TaskID, spicy.WaitOptions{})
if err != nil {
return err
}
for _, asset := range final.ReadyAssets() {
log.Println(asset.URL)
}MODEL_ID_FROM_CATALOG는 자리표시자입니다. 실제 ID는 인증된 카탈로그에서 가져오세요. client.ListModels이고, HTTP로는 GET /api/v1/models입니다. 기억이나 오래된 페이지에서 가져오면 안 됩니다. 모델은 계속 추가되고, 멈추고, 퇴역합니다. Input은 그 모델 자신의 inputSchema를 보고 구성하세요. ListModels가 돌려줍니다. 모델마다 받는 필드가 다릅니다.
금액은 부동소수점이 아니라 미국 달러를 적은 십진 문자열입니다. 이진 부동소수점은 대부분의 십진 금액을 정확히 담지 못하기 때문입니다. 이 API 어디에도 크레딧이나 포인트는 없습니다.
종료 상태라고 해서 성공한 것은 아닙니다. failed, canceled, expired 모두 기다림을 끝냅니다. final.State를 확인하세요.
첫 폴링 건너뛰기
WaitSeconds는 작업이 끝날 때까지 연결을 열어 두도록 요청합니다. 빠른 모델이라면 제출과 동시에 결과가 도착한다는 뜻입니다.
seconds := 30
task, err := client.CreateTask(ctx, request, key, spicy.CreateTaskOptions{WaitSeconds: &seconds})
if err != nil {
return err
}
// 대기 시간이 다하면 Task는 nil입니다. 오류가 아니라 흔히 있는 결과입니다.
// 생성이 아직 돌고 있다는 뜻이므로 여기서부터 폴링하세요.
final := task.Task
if final == nil {
final, err = client.WaitForTerminal(ctx, task.TaskID, spicy.WaitOptions{})
if err != nil {
return err
}
}
log.Println(final.State, final.Cost)이것은 최적화일 뿐 보장이 아닙니다. nil 검사 없이 task.Task를 읽는 코드는 주어진 시간보다 느린 첫 생성이 나올 때까지는 잘 돌아가다가, 그 순간 프로덕션에서 패닉을 냅니다.
로컬 파일에서 시작하기
이미지-투-비디오, 피사체 교체, 이미지 편집은 모두 내 소재가 먼저 이쪽에 올라와 있어야 합니다. UploadFile은 티켓 발급, 스토리지로 PUT, 커밋이라는 세 단계 업로드를 한 번의 호출로 끝내고 spicy:// URI를 돌려줍니다. Input에 넣는 값이 바로 그 URI입니다.
uploaded, err := client.UploadFile(ctx, "/path/to/reference.png", "")
// uploaded.URI -> "spicy://f/fil_..."공개적으로 접근할 수 있는 HTTPS 주소라면 업로드 없이 입력에 바로 넣어도 됩니다. 이미지는 10 MiB까지, 오디오와 영상은 90 MiB까지 받습니다. 미디어를 보세요.
잔액, 사용량, 기록
셋 다 호출한 그 API 키만을 범위로 삼습니다. 같은 계정의 다른 키, 그리고 웹 콘솔에서 시작한 생성은 포함되지 않으며, 빠져 있는 것이 정상입니다.
balance, err := client.GetBalance(ctx)
if err != nil {
return err
}
log.Printf("%s available, %s held by accepted tasks", balance.Available, balance.Held)
usage, err := client.GetUsage(ctx, spicy.UsageOptions{From: "2026-09-01", To: "2026-09-08"})
if err != nil {
return err
}
log.Printf("%d calls, %s settled over [%s,%s)", usage.TotalCalls, usage.TotalSpend, usage.From, usage.To)
// EachTask는 모든 페이지에 필터를 그대로 다시 적용하고 커서만 옮깁니다.
if err := client.EachTask(ctx, spicy.ListTasksOptions{From: "2026-09-01", To: "2026-09-08"},
func(task spicy.TaskListItem) bool {
log.Println(task.TaskID, task.State, task.Cost)
return true // 일찍 멈추려면 false를 돌려주세요
}); err != nil {
return err
}날짜는 UTC 달력 날짜이며, 최대 92일까지의 반열린 구간 [from, to)를 이룹니다. UTC 자정을 넘길 수 있는 순회에서는 From과 To를 반드시 고정하세요. 비워 두면 서버가 페이지마다 자기 구간을 다시 계산하고, 그때 to는 UTC 기준 내일이 됩니다. 그래서 23:59에 시작해 00:01에 이어지는 순회는 서로 다른 두 질문을 던져 놓고 그 답을 이어 붙이게 됩니다. 이 사실을 알려 주는 것은 아무것도 없습니다.
GetUsage는 장부를 맞춰 보기 위한 것이지 진행 상황을 확인하기 위한 것이 아닙니다. 모든 키가 함께 쓰는 계정 단위 예산 뒤에 있고, 그 제한기는 막히는 쪽으로 실패합니다. 이걸 폴링하면 다른 키 전부가 자기 사용량 조회조차 못 하게 될 수 있습니다. 작업을 따라가려면 WaitForTerminal을 쓰세요. 정산이 늦게 반영되는 경우도 있어서, 오늘 읽은 수치가 내일 달라질 수 있습니다.
오류
실패는 *spicy.APIError로 돌아오며, errors.As로 꺼낼 수 있습니다.
var apiErr *spicy.APIError
if errors.As(err, &apiErr) {
log.Println(apiErr.Status, apiErr.Code, apiErr.RequestID, apiErr.RetryAfter)
}분기는 언제나 Code로 하고, 메시지로는 절대 하지 마세요. 메시지는 번역되지만 코드는 번역되지 않습니다. HTTP 503은 서로 다른 비즈니스 코드 세 개가 함께 씁니다. 그래서 상태만으로는 무엇을 해야 할지 정할 수 없습니다.
| 코드 | 의미 | 대응 |
|---|---|---|
40003 | 업로드한 바이트가 티켓과 맞지 않습니다 | 새 티켓을 받아 처음부터 다시 하세요 |
40004 | 그 파라미터 조합을 처리할 배포가 없습니다 | 메시지에 이름이 나온 파라미터를 바꾸세요 |
40201 | 잔액이 모자랍니다 | 충전하세요 |
40901 | 작업이 만들어지기 전에 가격이 바뀌었습니다 | 견적을 다시 받되 멱등 키는 그대로 두세요 |
503 | 의존하는 구성 요소를 잠시 쓸 수 없습니다 | RetryAfter만큼 기다린 뒤 다시 시도하세요 |
50301 | 지금 쓸 수 있는 배포나 가격이 없습니다 | 카탈로그를 새로 읽으세요. 몰아치듯 재시도하지 마세요 |
50302 | 동기 생성이 상위에서 실패했고 환불됐습니다 | 새 멱등 키로 다시 시도하세요 |
모든 코드는 오류에 정리돼 있습니다. 지원팀에 문의할 때는 RequestID를 함께 알려 주세요.
돈을 아껴 주는 두 가지
제출 하나에 멱등 키 하나를 유지하세요. 다시 보낼 때는 시간이 초과된 뒤라도 그 키를 그대로 다시 쓰세요. 응답을 받지 못했다는 사실이 작업이 만들어지지 않았다는 증거는 아닙니다. 새 키를 쓰면 결과를 모르는 상태가 돈이 드는 두 번째 작업으로 바뀝니다. CreateTask가 키를 필수로 받는 이유가 바로 이것입니다. 멱등성을 보세요.
성공한 작업은 결과가 마음에 들지 않아도 과금됩니다. Quote는 아무것도 예약하지 않으므로, 가격이 중요하다면 먼저 견적을 받으세요.
이 패키지가 하지 않는 일
- 텍스트 모델. 텍스트 모델은 OpenAI, Anthropic, Gemini의 통신 형식을 씁니다. 그 형식을 지원하는 기존 Go 클라이언트가 같은 키로
https://api.spicyapi.ai/v1에 대고 이미 그대로 작동합니다. 텍스트와 스트리밍을 보세요. - 클라이언트 쪽 애플리케이션. 배포하는 바이너리 안에 이 키를 담지 마세요. 내 서버에서 호출하세요.
- 모델 고르기와 필드 채우기. SDK는 카탈로그와 모델별 스키마를 건네줄 뿐이고, 무엇을 보낼지는 직접 정합니다.
- 작업 취소. 작업이 접수되면 끝까지 실행됩니다.
context를 취소해도 멈추는 것은 내 쪽의 기다림뿐입니다.
소스
이 모듈은 MIT 라이선스이고 **github.com/spicyapi-ai/spicy-go**에서 공개적으로 개발됩니다. 저장소의 README는 이 클라이언트를 가장 짧게 한 바퀴 둘러보는 문서이고, 이 페이지는 그 주변 개념을 채워 줍니다.

