Errores
Consulta cada código de negocio, su estado HTTP, cómo resolverlo y cuándo conviene reintentar.
Cuando una llamada falla, la respuesta indica dos cosas: un número (code) que identifica el tipo de problema y una frase (msg) para que una persona la lea. Esta página enumera los números, sus significados y si reintentar puede ayudar. Si solo necesitas resolver un síntoma rápidamente, empieza por Solución de problemas.
Formato de la respuesta
Las respuestas JSON de la API nativa de tareas utilizan este formato: los mismos cuatro campos alrededor de cada respuesta. Interpreta las respuestas compatibles de /v1 y SSE según sus propios protocolos:
{ "code": 200, "msg": "success", "data": { }, "request_id": "req_…" }codees un código de negocio, no un estado HTTP.200significa éxito.- En caso de fallo,
datano aparece, en lugar de sernull: recibescode,msgyrequest_id. La única excepción: algunos fallos de la biblioteca de recursos añadendata.reason. msges para personas: su redacción y su idioma pueden cambiar (véase más abajo). Nunca bases una condición en ese texto; usacode.request_idtambién se devuelve en el encabezadoX-Request-Id. Indícalo al informar de un problema.
El estado HTTP también se establece correctamente. Puedes basar las condiciones en cualquiera de los dos, pero solo en uno: sus valores difieren para códigos concretos, como saldo insuficiente, que es code: 40201 con HTTP 402. Mezclarlos acabará dejando algún caso sin cubrir.
Recomendamos utilizar code. 404 solo puede indicar «no encontrado»; 40201 indica que el saldo disponible es insuficiente.
No todas las respuestas son JSON
Una respuesta que no se puede interpretar como JSON, por ejemplo una página de texto o HTML de nuestra red de borde, no tiene code. Trátala según su estado HTTP, registra el inicio del cuerpo y no reintentes un 403 a ciegas: podría ser un bloqueo regional que ningún reintento cambiará.
Idioma de los mensajes de error
El texto de error para personas puede llegar en el idioma que elijas. El predeterminado es inglés. Solo estos tres lugares siguen esa elección:
msgen el formato de respuesta;errorMessagede una tarea fallida, tanto enrecordInfocomo en callbacks de webhook, con una excepción indicada a continuación;error.messageen los objetos de error de endpoints compatibles, tanto con el formato de OpenAI o Anthropic como con el de Google Gemini (/v1beta).
Códigos de idioma admitidos: en, ja, ko, de, fr, es, pt-BR, ru, it, pl.
La excepción: si el propio modelo proporcionó un motivo concreto de fallo, como una imagen de referencia que no pudo leer o una restricción de derechos de autor sobre audio generado, errorMessage incluye ese motivo en inglés, sin detalles internos como nombres de servicios, hosts, URL e ID de solicitudes, para que veas qué falló realmente. Los motivos sobre el estado de cuenta o facturación nunca se transmiten; recibes nuestro mensaje. Content-Language sigue describiendo msg, y errorCode siempre es el valor que debes usar para las condiciones.
Elegir el idioma
Se utiliza la primera opción aplicable:
- El encabezado de solicitud
Accept-Language, solo para esa solicitud. Se aplica la ponderación HTTP estándar: elegimos el idioma reconocido con el mayor valorq. Las variantes regionales se asignan a su idioma:de-DEcuenta comodeypt-PTcomopt-BR. No se ofrece chino, por lo que sus etiquetas no coinciden con ningún idioma y se prueba el siguiente candidato.*significa sin preferencia. - El ajuste de tu cuenta, en Configuración → Idioma de errores de API de la consola. Abarca todas las solicitudes con una clave de esa cuenta que no incluyan un
Accept-Languagereconocible. La mayoría de los SDK de servidor y curl no envían ese encabezado de forma predeterminada, por lo que es la opción usada por la mayoría de las integraciones. Los callbacks de webhook no tienen un encabezado de solicitud para elegir el idioma y siempre siguen el ajuste de cuenta. - En los demás casos, inglés.
Las respuestas de error incluyen un encabezado Content-Language con el idioma en el que se escribió realmente el mensaje.
Solo cambia el texto para personas
code, errorCode, los campos type y code compatibles con OpenAI, los campos code y status compatibles con Gemini y todos los nombres de campos permanecen iguales en cualquier idioma. Basa las condiciones en códigos y nunca interpretes la redacción; registra code y request_id en logs y alertas.
Ejemplo
El mismo 40201, saldo insuficiente, llega en inglés de forma predeterminada:
HTTP/1.1 402 Payment Required
Content-Language: en
{ "code": 40201, "msg": "Insufficient balance", "request_id": "req_…" }Añade un encabezado Accept-Language: ja; el resto es la misma llamada de creación de tarea de la guía de inicio:
curl -X POST https://api.spicyapi.ai/api/v1/jobs/createTask \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
-H "Accept-Language: ja" \
--data "$TASK_PAYLOAD"Ahora msg está en japonés; code sigue siendo 40201:
HTTP/1.1 402 Payment Required
Content-Language: ja
{ "code": 40201, "msg": "残高が不足しています", "request_id": "req_…" }Códigos generales
code | HTTP | Significado | ¿Reintentar? |
|---|---|---|---|
200 | 200 | Éxito | — |
400 | 400 | Solicitud mal formada o parámetros no válidos | No. Corrige y vuelve a enviar |
40003 | 400 | El tamaño, tipo o contenido de un archivo subido no coincide con lo declarado al solicitar la URL de subida | No. Solicita una URL nueva con el contentType y tamaño correctos, vuelve a subir y confirma |
40004 | 400 | La solicitud es válida, pero este modelo no puede servir esa combinación de ajustes en este momento | No. Cambia el ajuste indicado en msg, como resolución o relación de aspecto, o elige otro modelo. Véase más abajo |
401 | 401 | Falló la autenticación; msg indica cuál de tres casos | No. Corrige el encabezado o la clave |
403 | 403 | No permitido. En una llamada con clave válida, la cuenta está restringida; véase más abajo. Un correo no verificado devuelve 40310 | No |
404 | 404 | No existe o no te pertenece | No |
409 | 409 | Conflicto con el estado actual | A veces; véase más abajo |
40901 | 409 | La cotización venció, cambió la solicitud o cambió el precio | Cotiza y confirma otra vez; recupera las solicitudes aceptadas con la clave de idempotencia original |
413 | 413 | El cuerpo supera el tamaño máximo que acepta el servidor | No. Reduce el cuerpo o sube los medios mediante el flujo de archivos |
429 | 429 | Demasiadas solicitudes | Normalmente sí. Espera según Retry-After. Uno de los casos 429 no tiene que ver con la velocidad; consulta Límites de solicitudes |
500 | 500 | Algo falló de nuestro lado | Sí. Espera y reintenta |
503 | 503 | Un servicio auxiliar, como cotizaciones, almacenamiento de imágenes en línea o subidas, no está disponible temporalmente | Sí. Espera lo indicado en Retry-After, si aparece, y reintenta; en createTask, con el mismo Idempotency-Key. Si persiste sin Retry-After, sigue msg, por ejemplo usa una URL HTTPS pública en lugar de una imagen en línea |
503 y 50301 comparten estado HTTP, pero son códigos de negocio distintos: 503 se refiere a un servicio auxiliar y 50301 al propio modelo.
Distinguir los tres casos de 401
Tres errores distintos de autenticación devuelven 401 y se resuelven de formas diferentes. El code permanece en 401 en todos; msg permite distinguirlos.
msg en inglés | Qué ocurrió realmente | Qué cambiar |
|---|---|---|
Missing credentials: send your API key in the Authorization header as "Bearer sk-spicy-..." (en endpoints compatibles /v1 y /v1beta: …as "Authorization: Bearer sk-spicy-...", x-api-key, or x-goog-api-key) | La solicitud llegó sin ninguna credencial | Tu cliente está omitiendo el encabezado. Comprueba el interceptor, el proxy y si la variable de entorno se cargó en el proceso en ejecución |
API key format is not recognized: keys begin with sk-spicy- | Llegó una credencial, pero no es nuestra | Pegaste un valor de sesión de consola o la clave de otro proveedor. Las claves de SpicyAPI empiezan por sk-spicy-; echo "${SPICY_API_KEY:0:9}" debe mostrar exactamente ese prefijo |
Credentials are invalid or expired | La clave tiene nuestro formato, pero no existe, fue revocada o venció | Comprueba la clave en la página de claves de API; si está revocada o vencida, crea otra. Reintentar nunca convertirá la respuesta en un 200 |
El caso intermedio se decide sin consultar la base de datos; por eso puede indicar algo que el tercero no: esa cadena nunca pudo funcionar. El tercer mensaje es deliberadamente idéntico para una clave inexistente, revocada o vencida, de modo que no permite investigar qué claves existen.
Aquí se muestran las cadenas en inglés; msg sigue la selección de idioma anterior y su redacción no es un contrato. Basa las condiciones en code y trata el mensaje como algo que lees, muestras o registras, no como un patrón que debes comparar.
La cuenta no puede llamar a la API
La clave es válida en ambos casos siguientes; es la cuenta la que todavía no puede llamar a la API.
40310 significa que no se ha verificado el correo. El mensaje es Verify your account email to run models and call the API…:
- Abre la consola. Un aviso superior indica que el correo no está verificado y permite enviar de nuevo el mensaje de verificación.
- Sigue el enlace del correo de verificación; revisa también la carpeta de spam.
- Reintenta la misma llamada. No hace falta una clave nueva.
La consola sigue funcionando mientras la API está bloqueada, así que todavía puedes consultar tu historial.
Un 403 simple con This account cannot call the API right now indica que la cuenta está restringida. Contacta con soporte e incluye el request_id.
Saldo y límites de gasto
code | HTTP | Significado | Qué hacer |
|---|---|---|---|
40201 | 402 | El saldo no cubre el costo estimado | Añade fondos en Facturación dentro de la consola; consulta Configuración de la cuenta. Comprueba GET /api/v1/chat/credit |
40202 | 402 | Se alcanzó un límite de gasto | Lee msg para identificarlo. Límite de clave (This key has reached its …): auméntalo en claves de API o espera al reinicio. Límite de equipo (Workspace … budget has been reached): pide a un propietario, administrador o responsable de presupuesto que lo aumente en la página del equipo. Límite de plataforma (Platform quota for today is exhausted…): no puedes cambiarlo; reintenta después de las 00:00 UTC. Los diarios se reinician a las 00:00 UTC y los mensuales el día 1 a las 00:00 UTC; el total nunca se reinicia |
Ninguno se resuelve reintentando: los saldos y límites no cambian porque vuelvas a preguntar. Consulta Límites de gasto.
Permisos
code | HTTP | Significado | Qué hacer |
|---|---|---|---|
40301 | 403 | Esta clave no puede llamar a este modelo | Amplía la lista de modelos permitidos de la clave en la consola |
40302 | 403 | La dirección de origen no está en la lista de IP permitidas de la clave | Añádela o elimina la restricción de IP de esa clave |
40303 | 403 | El servicio no se ofrece en esta región | No se puede eludir. Consulta dónde operamos |
40310 | 403 | El correo de la cuenta todavía no está verificado y no puede ejecutar modelos | Abre el enlace de verificación del correo de registro o envía otro desde la consola. La clave funciona en cuanto se verifica la dirección, sin cambios de tu lado |
Ninguno de estos errores se resuelve reintentando.
Códigos de motivo de la biblioteca de recursos
Los fallos en los endpoints de la biblioteca de recursos (/api/v1/assets…) pueden añadir al envoltorio habitual un campo legible por máquina, data.reason:
{ "code": 400, "msg": "…", "data": { "reason": "asset_library_locked" }, "request_id": "req_…" }code, el estado HTTP, msg y el errorCode de la tarea son los mismos con o sin motivo, así que tu gestión actual sigue funcionando. El motivo aparece solo en los endpoints de la biblioteca de recursos, nunca en otros endpoints nativos, y la capa compatible con OpenAI y las demás capas de compatibilidad conservan sus propias formas de error. La lista puede crecer: trata un valor desconocido como un motivo ausente.
data.reason | HTTP | Significado | Qué hacer |
|---|---|---|---|
asset_library_locked | 400 | La biblioteca de recursos se abre con la primera recarga | Recarga. Los recursos ya guardados siguen siendo utilizables |
asset_library_full | 400 | La cuota de almacenamiento (elementos o bytes) está llena | Elimina recursos o sube de plan; GET /api/v1/assets/usage muestra el siguiente |
asset_file_too_large | 400 | El archivo supera el límite por archivo del plan | Usa un archivo más pequeño o sube de plan |
asset_daily_limit | 429 | Se alcanzó el número diario de recursos nuevos | Espera hasta las 00:00 UTC y respeta Retry-After |
asset_storage_full | 503 | El almacenamiento de toda la plataforma está lleno | Vuelve a intentarlo más tarde |
asset_permission_denied | 403 | Tu rol en el equipo no permite este cambio | Pide a un propietario o administrador del equipo, o usa una clave cuyo creador tenga un rol que lo permita |
asset_workspace_forbidden | 403 | Esta clave no llega a esa biblioteca, por ejemplo porque el miembro que creó la clave de equipo ha salido del equipo | Crea una clave nueva en el espacio de trabajo que necesites |
asset_entity_name_taken | 409 | La biblioteca ya tiene un personaje con ese nombre (sin distinguir mayúsculas). Los personajes se nombran en la página Assets de la web, así que la API de solo lectura, por ahora, no lo devuelve | Elige otro nombre |
Disponibilidad de los modelos
code | HTTP | Significado | ¿Reintentar? |
|---|---|---|---|
50301 | 503 | El modelo no está disponible en este momento | Sí. Reintenta más tarde o elige otro modelo |
50302 | 503 | Una solicitud de texto, con o sin streaming, falló en el modelo antes de entregar una respuesta; se devolvió la retención | Sí. Vuelve a enviar con un Idempotency-Key nuevo |
40004 | 400 | El modelo está funcionando, pero no con estos ajustes | No. Cambia el ajuste indicado en msg |
50301 significa que el modelo no pudo aceptar la tarea al enviarla: no está publicado, no tiene precios activos o no tiene capacidad de generación disponible. Los problemas temporales de capacidad pueden resolverse con reintentos limitados. Un modelo no disponible o un precio activo ausente no se arreglan repitiendo solicitudes. Actualiza el catálogo y pide confirmación al usuario antes de cambiar de modelo.
40004 y 50301 son deliberadamente distintos. 50301 significa que el modelo no puede aceptar ninguna tarea en este momento; esperar podría ayudar. 40004 significa que funciona, pero no puede servir esta combinación de ajustes; msg indica el ajuste entre paréntesis, por ejemplo This model cannot serve this parameter combination right now (…). Esperar no ayudará; cambiar un valor sí. Ninguno crea una tarea ni retiene dinero.
Los fallos de generación posteriores a la aceptación aparecen como state: failed
Un fallo de generación después de que createTask devuelva un ID aparece en recordInfo como state: "failed" con errorCode / errorMessage; normalmente se libera toda la retención, salvo determinados rechazos de contenido que la página del modelo indique expresamente como cobrables. La misma situación que 40004, descubierta solo después de aceptar, aparece como errorCode: "unsupported_combination". No añadas condiciones para códigos de negocio síncronos que no figuren en esta página.
Códigos de fallo de tareas
Un fallo posterior a la aceptación aparece en errorCode de recordInfo. El conjunto es cerrado: aquí nunca aparecen diagnósticos internos, por lo que debes tratar cualquier valor fuera de la lista como upstream_failed.
errorCode | Significado | ¿Reintentar? |
|---|---|---|
invalid_request | No se aceptaron los parámetros | No. Corrígelos y vuelve a enviar |
unsupported_combination | La combinación no se admite en este momento | No. Elige otros ajustes |
content_rejected | El modelo seleccionado rechazó el contenido. La propia SpicyAPI no revisa las solicitudes; consulta Contenido y responsabilidad de uso | No. Cambia el prompt o elige otro modelo |
rate_limited | El servicio de generación estaba ocupado | Sí. Espera y reintenta |
upstream_unavailable | La generación no está disponible temporalmente | Sí. Reintenta en un momento |
invalid_asset | Un archivo de referencia ya no está disponible | No mediante un reintento. retry reutiliza la entrada original; vuelve a subir el archivo y crea una tarea con la URI nueva |
generation_failed | La generación comenzó y no produjo nada | Tal vez. Las mismas entradas normalmente fallarán del mismo modo |
timeout | La tarea llegó a su plazo límite sin terminar | Tal vez. Prueba una duración más corta o una resolución menor |
upstream_failed | Categoría general para cualquier otro fallo | Sí. Espera y reintenta |
Las tareas fallidas o vencidas normalmente liberan toda la retención. Algunos rechazos de contenido se cobran al precio aceptado únicamente si la página del modelo informa expresamente de esa excepción. Consulta settled y cost para conocer el cargo final. errorMessage es para personas: explica el motivo, puede citar en inglés la explicación del modelo (véase arriba) y su redacción cambia; basa las condiciones solo en errorCode. Aquí «reintentar» significa enviar una tarea nueva, que se cobra de nuevo si termina correctamente.
Qué errores cuestan dinero
El estado de la tarea y el de la conexión son hechos diferentes.
Las creaciones rechazadas expresamente, las tareas vencidas y los registros históricos cancelados no tienen cargo de generación. Las fallidas normalmente son gratuitas, salvo determinados rechazos de contenido que la página del modelo indique expresamente como cobrables. Un tiempo de espera agotado o una desconexión del cliente no es un fallo de tarea. Las tareas aceptadas no pueden cancelarse: recupera la original y revisa su estado final y liquidación. Un stream de texto desconectado puede cobrarse si ya se registró un uso válido.
| Situación | ¿Se cobra? |
|---|---|
La API rechaza expresamente createTask sin crear una tarea, por ejemplo 40201, 40202, 40004 o 50301 | No. La creación y la retención comparten una transacción; un error del gateway o la falta de respuesta no demuestra un rechazo |
| El envío agota el tiempo, se cierra una pestaña o se pierde la conexión | Todavía no se sabe. Recupera la aceptación con la clave de idempotencia original; no lo trates como un reintento gratuito |
createTask devuelve 202 y la tarea termina en failed / expired | Normalmente no se cobra. Ciertos resultados failed / content_rejected se cobran al precio aceptado si la página del modelo lo indica expresamente. Lee settled y cost |
Una tarea histórica está en canceled | No. Es un estado de compatibilidad, no una acción actualmente disponible |
createTask devuelve 202 y la tarea termina en succeeded | Sí, se liquida el uso real con el límite de la retención aceptada. Los fondos no utilizados se liberan; no se cobra después nada por encima de esa retención |
Cualquier error de recordInfo, chat/credit o common/* | No. Esos endpoints nunca se cobran |
Limitación de solicitudes (429) o deduplicación mediante clave de idempotencia | No. El primero nunca se aceptó; el segundo devuelve la tarea original, cobrada una vez |
La liquidación o liberación se completa automáticamente, incluso tras interrumpirse un proceso. Los fallos ordinarios liberan toda la retención; determinados rechazos de contenido indicados expresamente en la página del modelo se cobran al precio aceptado. Consulta la liquidación y el saldo para verificar el resultado; véase Facturación.
Cuándo conviene reintentar un 409
| Caso | ¿Reintentar? |
|---|---|
Conflicto concurrente poco frecuente de createTask con la misma clave y el mismo contenido | Sí. Espera unos cientos de milisegundos y reintenta con la misma clave. Consulta Idempotencia |
| La misma clave ya se utilizó con otro contenido o clave de API | No. Detente y corrige a qué solicitud pertenece la clave; reintentar no cambia el conflicto |
Se siguen procesando imágenes en línea o la confirmación de una subida; puede aparecer Retry-After | Sí. Espera según Retry-After o un par de segundos y vuelve a enviar con el mismo Idempotency-Key |
| Cambiaron los precios o la configuración de tu cuenta durante la solicitud | Sí. Cotiza de nuevo si usas cotizaciones y vuelve a enviar |
| El archivo generado sigue copiándose | Sí. Espera unos segundos y solicita de nuevo el enlace de descarga |
jobs/retry sobre una tarea que no terminó o no se liquidó | Sí, más tarde. Espera a que la tarea de origen alcance un estado final y settled sea true; después reintenta |
jobs/retry sobre una tarea correcta o cuya entrada ya se eliminó por retención | No. Crea una tarea nueva con createTask |
| Venció una URL de subida o un archivo subido | No. Vuelve a subir el archivo |
Implementar los reintentos
No reintentes a ciegas
400, 401, 40004, 40201 y otros errores similares de entrada o autorización devuelven lo mismo por muchos reintentos que hagas. Cada intento marcado «No» es un desperdicio y consume tu propio límite de solicitudes.
Usa espera exponencial con variación aleatoria para que un lote de fallos no vuelva a intentarlo al mismo instante:
// 409 is operation-specific and must be handled at the call site.
const RETRYABLE = new Set([429, 500, 503, 50301]);
async function callWithRetry(path, init, { attempts = 4 } = {}) {
for (let i = 0; i < attempts; i++) {
const res = await fetch(`https://api.spicyapi.ai/api/v1${path}`, init);
// A page from the edge network is not our JSON envelope
if (!res.headers.get('content-type')?.includes('json')) {
const text = await res.text();
throw new Error(`HTTP ${res.status}, not JSON: ${text.slice(0, 200)}`);
}
const body = await res.json();
if (body.code === 200) return body.data;
if (!RETRYABLE.has(body.code)) {
throw new Error(`${body.code} ${body.msg} (request_id=${body.request_id})`);
}
// When the server says how long to wait, trust it
const retryAfter = Number(res.headers.get('retry-after'));
const backoff = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: 2 ** i * 1000 + Math.random() * 500;
await new Promise((r) => setTimeout(r, backoff));
}
throw new Error('out of retries');
}import random, time, requests
# 409 is operation-specific and must be handled at the call site.
RETRYABLE = {429, 500, 503, 50301}
BASE = "https://api.spicyapi.ai/api/v1"
def call_with_retry(method: str, path: str, attempts: int = 4, **kwargs) -> dict:
for i in range(attempts):
res = requests.request(method, f"{BASE}{path}", **kwargs)
# A page from the edge network is not our JSON envelope
try:
body = res.json()
except ValueError:
raise RuntimeError(f"HTTP {res.status_code}, not JSON: {res.text[:200]}")
if body["code"] == 200:
return body["data"]
if body["code"] not in RETRYABLE:
raise RuntimeError(f'{body["code"]} {body["msg"]} (request_id={body.get("request_id")})')
# When the server says how long to wait, trust it
retry_after = res.headers.get("retry-after")
backoff = float(retry_after) if retry_after else 2**i + random.random() * 0.5
time.sleep(backoff)
raise RuntimeError("out of retries")package main
import (
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"strconv"
"time"
)
// retryable lists the codes worth another attempt. Anything else returns
// the same answer however many times you ask — and each ask eats your own
// rate limit.
// Whether the generation itself failed is read from recordInfo's state.
var retryable = map[int]bool{429: true, 500: true, 503: true, 50301: true}
type envelope struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
RequestID string `json:"request_id"`
}
func callWithRetry(newReq func() (*http.Request, error), attempts int, out any) error {
for i := 0; i < attempts; i++ {
req, err := newReq()
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
// Transport-level failure: the request may well have arrived.
// On createTask, send the same Idempotency-Key or this retry
// turns into a second generation and a second charge.
time.Sleep(backoff(i, ""))
continue
}
var env envelope
decErr := json.NewDecoder(res.Body).Decode(&env)
io.Copy(io.Discard, res.Body)
res.Body.Close()
if decErr != nil {
// Not our JSON envelope, for example a page from the edge network.
return fmt.Errorf("HTTP %d, response is not JSON: %w", res.StatusCode, decErr)
}
if env.Code == 200 {
return json.Unmarshal(env.Data, out)
}
if !retryable[env.Code] {
return fmt.Errorf("%d %s (request_id=%s)", env.Code, env.Msg, env.RequestID)
}
// When the server says how long to wait, trust it
time.Sleep(backoff(i, res.Header.Get("Retry-After")))
}
return fmt.Errorf("out of retries")
}
// backoff trusts Retry-After when present, otherwise backs off
// exponentially with jitter so a batch of failures does not come back
// as one thundering herd.
func backoff(attempt int, retryAfter string) time.Duration {
if s, err := strconv.Atoi(retryAfter); err == nil && s > 0 {
return time.Duration(s) * time.Second
}
base := time.Duration(1<<attempt) * time.Second
return base + time.Duration(rand.Intn(500))*time.Millisecond
}Al reintentar createTask, envía siempre el mismo Idempotency-Key; de lo contrario, «la tarea sí tuvo éxito, solo perdimos la respuesta» se convierte en una generación duplicada y un cargo duplicado.
Depuración
Si un error no tiene sentido, sigue esta lista:
request_id: con él podemos encontrar la solicitud exacta.code: consulta las tablas anteriores.- Logs de la consola: pega el ID de tarea o
request_iden el buscador de Logs para abrirla y ver estado, costo, latencia, clave que la envió, código de error y motivo. Los parámetros y resultados solo se muestran mientras se conservan y, dentro de un equipo, solo a roles autorizados a leer contenido.
El msg de 500 siempre es un mensaje genérico de «servicio temporalmente no disponible», sin detalles internos: nunca aparecen estructuras internas, nombres de componentes ni trazas de pila en una respuesta. Solo se pueden investigar con request_id.
Para contactarnos, abre una conversación en Soporte dentro de la consola o escribe a support@spicyapi.ai. Incluye el request_id o ID de tarea, la hora en UTC y el code. Nunca envíes una clave de API.
OpenAPI e índice de endpoints
Contrato OpenAPI 3.1 descargable, todos los endpoints públicos y sus guías, encabezados de solicitud compartidos y origen de producción.
Límites de solicitudes
Cómo funcionan los límites de lectura y escritura, qué consume cada uno, qué otros límites puedes alcanzar y por qué la concurrencia es independiente.

