spicyapiDocumentación
SDK
Contenido principal

SDK de Go

El SDK oficial de Go para SpicyAPI (github.com/spicyapi-ai/spicy-go) — instálalo, cotiza y lanza tu primera generación, sube un archivo local, recorre tu historial de tareas y trata todos los códigos de error.

spicy-go es el SDK oficial de Go para SpicyAPI. Pone la generación de imagen, video y audio detrás de un único *spicy.Client: adjunta tu clave de API, decodifica la envoltura de la respuesta en structs tipados, espera a una tarea con esperas crecientes acotadas, sube archivos locales en una sola llamada y le da a cada fallo un único tipo de error que puedes reconocer con errors.As.

Necesita Go 1.23 o posterior y usa solo la biblioteca estándar. Es una decisión deliberada: este paquete entra en tu grafo de dependencias, y cada módulo que arrastrase sería una cosa más que auditar.

¿Es la herramienta adecuada?

Quieres…Usa
Llamar a SpicyAPI desde tu propio código de GoEste SDK
Llamarla desde Node.js o TypeScriptEl SDK de TypeScript
Llamarla desde Python, PHP o JavaEl SDK de Python, de PHP o de Java
Generar cosas desde una terminal, sin escribir códigoLa CLI
Que un asistente de IA llame a SpicyAPI por tiEl servidor MCP y el Agent Skill
Usar un modelo de chat o de textoUn cliente de Go consolidado para los formatos de OpenAI, Anthropic o Gemini; consulta Texto y streaming

Instálalo

Go resuelve los módulos directamente desde su repositorio, así que no hay ningún registro de por medio ni nada que esperar. Este comando funciona hoy mismo:

go get github.com/spicyapi-ai/spicy-go

Después guarda tu clave en el entorno, para que no aparezca nunca ni en tu código ni en el archivo de historial de tu terminal:

export SPICY_API_KEY="sk-spicy-..."

Tu primer resultado

Pide el precio, decide, crea la tarea, espera y lee los archivos. Cotizar es gratis y no retiene nada.

client, err := spicy.New(spicy.Options{}) // lee SPICY_API_KEY del entorno
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)

// Guárdala junto a tu propio registro del trabajo antes de enviarlo: su razón de
// ser es seguir ahí después del fallo que se llevó por delante la respuesta.
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 es un marcador de posición. Saca un ID real del catálogo autenticado —client.ListModels, que es GET /api/v1/models— y nunca de memoria ni de una página antigua: los modelos se añaden, se pausan y se retiran. Construye Input a partir del inputSchema del propio modelo, que devuelve ListModels; cada modelo acepta campos distintos.

Los importes son cadenas decimales en dólares estadounidenses, nunca números en coma flotante, porque un flotante binario no puede guardar con exactitud la mayoría de los importes decimales. En esta API no hay créditos ni puntos por ninguna parte.

Un estado terminal no es un estado correcto: failed, canceled y expired también terminan la espera. Comprueba final.State.

Sáltate la primera consulta

WaitSeconds le pide al servicio que mantenga la conexión abierta hasta que la tarea termine, lo que en un modelo rápido significa que el resultado llega con el propio envío.

seconds := 30
task, err := client.CreateTask(ctx, request, key, spicy.CreateTaskOptions{WaitSeconds: &seconds})
if err != nil {
    return err
}

// Task es nil siempre que se agota la espera. Ese es el desenlace normal, no un
// error: la generación sigue en marcha, así que a partir de aquí toca sondear.
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)

Es una optimización, nunca una garantía. El código que lee task.Task sin comprobar si es nil funciona hasta la primera generación más lenta que ese margen, y entonces provoca un panic en producción.

Empieza desde un archivo local

La generación de imagen a video, la sustitución de personas y la edición de imágenes necesitan que tu material esté antes de nuestro lado. UploadFile hace en una sola llamada la subida en tres pasos —ticket, PUT al almacenamiento y confirmación— y devuelve un URI spicy://. Ese URI es lo que va en Input.

uploaded, err := client.UploadFile(ctx, "/path/to/reference.png", "")
// uploaded.URI -> "spicy://f/fil_..."

Una URL HTTPS accesible públicamente puede ir directamente en la entrada y no necesita ninguna subida. Se aceptan imágenes de hasta 10 MiB, y audio y video de hasta 90 MiB; consulta Carga y descarga de medios.

Saldo, consumo e historial

Los tres se limitan a la clave de API que hace la llamada: ni las claves hermanas de la misma cuenta ni las generaciones iniciadas en la consola web se incluyen, y esa ausencia no es un fallo.

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 repite todos los filtros en cada página y solo mueve el cursor.
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 // devuelve false para parar antes de tiempo
    }); err != nil {
    return err
}

Las fechas son días naturales en UTC, dentro de un intervalo semiabierto [from, to) de 92 días como máximo. Fija From y To en cualquier recorrido que pueda cruzar la medianoche UTC. Si los dejas vacíos, el servidor recalcula su propia ventana —to es mañana en UTC— en cada página, de modo que un recorrido que empieza a las 23:59 y continúa a las 00:01 hace dos preguntas distintas y empalma las respuestas. Nada te avisa de ello.

GetUsage sirve para cuadrar cuentas, no para seguir el progreso. Está detrás de un presupuesto de solicitudes común a toda la cuenta que comparten todas las claves, y ese limitador falla en cerrado: sondearlo puede dejar a todas las demás claves sin acceso a sus propios informes. Para seguir una tarea usa WaitForTerminal. La liquidación también puede llegar tarde, así que una cifra que leas hoy todavía puede cambiar mañana.

Errores

Los fallos llegan como *spicy.APIError, al que llegas con errors.As:

var apiErr *spicy.APIError
if errors.As(err, &apiErr) {
    log.Println(apiErr.Status, apiErr.Code, apiErr.RequestID, apiErr.RetryAfter)
}

Ramifica según Code, nunca según el mensaje: los mensajes se traducen, los códigos no. El estado HTTP 503 lo comparten tres códigos de negocio distintos, así que el estado por sí solo no basta para decidir qué hacer:

CodeQué significaQué hacer
40003los bytes subidos no coinciden con su ticketempieza de cero con un ticket nuevo
40004ningún despliegue atiende esa combinación de parámetroscambia el parámetro que nombra el mensaje
40201saldo insuficienteañade fondos
40901el precio cambió antes de que se creara la tareavuelve a cotizar y conserva la misma clave de idempotencia
503una dependencia no está disponible durante un momentoespera lo que indique RetryAfter
50301ahora mismo no hay ningún despliegue ni precio utilizableactualiza el catálogo y no insistas a lo loco
50302una generación síncrona falló aguas arriba y se reembolsóreintenta con una clave de idempotencia nueva

Todos los códigos están en Errores. Cita el RequestID cuando escribas a soporte.

Dos cosas que te ahorrarán dinero

Usa una sola clave de idempotencia por envío. Reutilízala en cada reenvío, también después de un tiempo de espera agotado. Que se pierda una respuesta no demuestra que la tarea no se haya creado: una clave nueva convierte un resultado desconocido en una segunda tarea de pago. CreateTask exige la clave justamente por eso. Consulta Idempotencia.

Una tarea que sale bien se cobra, aunque el resultado te decepcione. Quote no retiene nada, así que cotiza primero cuando el precio importe.

Lo que este paquete no hace

  • Modelos de texto. Hablan los formatos de OpenAI, Anthropic y Gemini: los clientes de Go consolidados para esos formatos ya funcionan contra https://api.spicyapi.ai/v1 con la misma clave. Consulta Texto y streaming.
  • Aplicaciones de cliente. No metas nunca esta clave dentro de un binario que distribuyas. Llama desde tu servidor.
  • Elegir un modelo o rellenar sus campos. Te entrega el catálogo y el esquema de cada modelo; tú decides qué enviar.
  • Cancelar una tarea. Cuando una tarea se acepta, se ejecuta hasta el final. Cancelar el context solo detiene tu espera.

Código fuente

El módulo tiene licencia MIT y se desarrolla en abierto en github.com/spicyapi-ai/spicy-go. Su README es el recorrido completo más corto por el cliente; esta página añade los conceptos que lo rodean.

Siguientes pasos

En esta página