HiAPI
Blog

Tutorial

Cómo usar gpt-6.1-terra a través de la API de HiAPI: curl, Python y una solicitud que funciona

Oct 31, 2026
文章封面图:淡紫色与墨黑色两大平面色块以一条细紫色硬边相接,构图平静而对称。

Qué necesita realmente una solicitud a gpt-6.1-terra

Cada llamada a gpt-6.1-terra en HiAPI se reduce a cuatro cosas: una URL, una cabecera de autenticación, el identificador del modelo y el cuerpo de la solicitud. Si esto está bien, el resto del artículo es solo la fontanería de conexión.

El endpoint

Todas las llamadas a modelos se realizan mediante la misma URL base en HiAPI. No necesitas cambiar de host para cada modelo: el nombre del modelo en el cuerpo de la solicitud es lo que enruta la petición. Apunta tu cliente a la URL base de HiAPI y guárdala en una variable, así solo tendrás que modificarla en un lugar.

La cabecera de autenticación

La autenticación se transmite mediante un token bearer en la cabecera Authorization. Ese token es una clave de API emitida en tu cuenta de HiAPI. Nunca la codifiques directamente en los archivos fuente; léela desde una variable de entorno (por ejemplo, HIAPI_API_KEY), así el mismo código funciona en local, CI y producción.

El identificador del modelo

El cuerpo de la solicitud debe especificar el modelo de forma explícita: "model": "gpt-6.1-terra". La cadena distingue entre mayúsculas y minúsculas y debe coincidir exactamente. Un nombre de modelo incorrecto o truncado es una de las causas más habituales de rechazo de solicitudes, así que trátalo como una constante en el código, no como una cadena escrita al azar.

Los campos del cuerpo de la solicitud

Una solicitud lleva como mínimo el modelo y el contenido de entrada, es decir, el prompt o los mensajes que quieres que procese el modelo. Sobre ese núcleo puedes añadir cualquier parámetro opcional que admita el modelo (indicaciones de formato de salida, control de longitud, etc.), pero empieza con el cuerpo mínimo que produzca una respuesta válida. Un cuerpo mínimo es más fácil de depurar que uno completamente equipado y te ayuda a determinar si el fallo está en tu cuerpo de solicitud o en la fontanería de conexión.

Hay un punto estructural que conviene interiorizar pronto: algunas respuestas se devuelven de forma inline y otras como una tarea que debes consultar periódicamente. Más adelante explicaremos cómo distinguir ambas, pero desde ahora diseña tu capa de solicitudes para que el manejo de respuestas pueda alternar entre ambos modos sin reescribirse.

Una solicitud curl que funciona, analizada línea por línea

A continuación tienes un ejemplo completo de llamada que puedes pegar en la terminal y ejecutar tras exportar la clave. Usa la URL base de HiAPI y un cuerpo JSON mínimo.

curl -sS -X POST "$HIAPI_BASE_URL/v1/chat/completions" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-6.1-terra",
    "messages": [
      {"role": "user", "content": "Resume el ciclo del agua en tres frases."}
    ]
  }'

Qué hace cada flag

-X POST establece el método HTTP. Las llamadas a modelos son POST porque llevan cuerpo de solicitud.

-sS hace que curl oculte la información de progreso pero siga mostrando los errores reales. Sin -S, una solicitud fallida podría parecer un simple silencio.

-H "Authorization: Bearer $HIAPI_API_KEY" adjunta tus credenciales. El prefijo Bearer y el único espacio que lo sigue forman parte del esquema de autenticación; omitir cualquiera de los dos provoca un fallo de autenticación que parece no tener relación con la cabecera.

-H "Content-Type: application/json" indica al servidor que el cuerpo es JSON. Algunas pasarelas toleran la ausencia del tipo de contenido, pero no dependas de ello.

-d '...' es el cuerpo de la solicitud. Las comillas simples impiden que el shell interpole el JSON. Si prefieres usar un archivo, sustituye -d '...' por -d @payload.json y coloca el JSON en un archivo que puedas lintar y comparar con diff.

Dos hábitos que ahorran tiempo

Primero, cuando haya un fallo, añade -i para imprimir la línea de estado y las cabeceras de la respuesta. Una respuesta que devuelve 200 pero con un cuerpo anómalo se lee de forma muy distinta a un 401 o un 422, y querrás ver esa diferencia de inmediato. Segundo, pasa la salida por un formateador JSON (curl ... | python -m json.tool) para que los campos anidados de la respuesta sean legibles. Ambos flags no tienen efectos secundarios y los usarás a menudo mientras ajustas la llamada.

Portar la llamada a Python

La versión en Python debe conservar la semántica exacta de la llamada curl: la misma URL, las mismas cabeceras, el mismo cuerpo y la misma forma de analizar la respuesta. Usar la biblioteca estándar mantiene las dependencias en cero y hace que el port sea evidente.

import json
import os
import urllib.request

HIAPI_BASE_URL = os.environ["HIAPI_BASE_URL"].rstrip("/")
HIAPI_API_KEY = os.environ["HIAPI_API_KEY"]

def call_terra(prompt: str, model: str = "gpt-6.1-terra") -> dict:
    body = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
    }
    req = urllib.request.Request(
        f"{HIAPI_BASE_URL}/v1/chat/completions",
        data=json.dumps(body).encode("utf-8"),
        headers={
            "Authorization": f"Bearer {HIAPI_API_KEY}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=120) as resp:
        return json.loads(resp.read().decode("utf-8"))

result = call_terra("Resume el ciclo del agua en tres frases.")
print(json.dumps(result, indent=2))

Notas sobre el port

Las cabeceras se construyen de una vez dentro del objeto de solicitud, lo que corresponde a los dos flags -H. El cuerpo es un dict de Python serializado con json.dumps, lo que es programáticamente equivalente al cuerpo curl entre comillas simples, y además elimina los problemas de comillas en el shell de Windows.

La función recibe el modelo como parámetro con valor por defecto, en lugar de repetir el literal por todas partes. Así el nombre del modelo tiene una única fuente de verdad y a la vez te permite sobrescribirlo de forma deliberada.

Aquí se omite deliberadamente el manejo de errores para mantener legible el camino feliz. En código real, envuelve la llamada a urlopen y captura la excepción de error HTTP, leyendo .read() del objeto de error para ver el mensaje del servidor. El tiempo de espera se establece de forma explícita; un bloqueo indefinido acabará colgando un worker.

Si prefieres httpx o requests, la correspondencia es uno a uno: requests.post(url, headers=..., json=body, timeout=120). El parámetro json= serializa el dict por ti. La semántica no cambia en absoluto, y eso es precisamente lo importante: la llamada curl es el contrato y Python es solo su segunda representación.

Decidir entre una llamada única y una tarea asíncrona

No todas las solicitudes son aptas para esperar. La decisión depende de cuánto tarda el trabajo y de si quien llama puede permitirse bloquearse.

Cuándo conviene una llamada única

Las solicitudes interactivas breves (resúmenes, clasificación, reescrituras pequeñas) deberían ser llamadas síncronas normales. La respuesta se devuelve inline, la analizas y devuelves. Es la ruta de código más simple y la más fácil de razonar en las pruebas. Si el usuario está esperando en la interfaz, la forma síncrona es la esperada.

Cuándo tratarla como una tarea

Cuando el trabajo es de larga duración o genera muchos artefactos, y una interrupción de la conexión implicaría rehacer un trabajo costoso, trata la llamada como asíncrona. En esos casos envías la solicitud, recibes un identificador de tarea y consultas su estado hasta que alcanza un estado terminal. El envío regresa rápido, así que tu worker o tu manejador HTTP no queda ocupado durante todo el proceso.

Una regla práctica: si te descubres configurando un tiempo de espera del cliente excepcionalmente largo para «que la llamada funcione», esa es la señal de que debes cambiar a tareas. El tiempo de espera no es un mecanismo de planificación.

Hacer que el cambio sea barato

No dividas la base de código en dos clientes sin relación entre sí. Encapsula ambas rutas detrás de una función que devuelva un resultado inmediato o un manejador de tarea, y deja que quien llama solicite el modo que quiera. Así la construcción de la solicitud (modelo, autenticación, cuerpo) se comparte y solo cambia la estrategia de espera. Esto también hace que la depuración sea coherente: cuando el cuerpo es pequeño, puedes reproducir un fallo asíncrono de forma síncrona.

Sé honesto con tu propia carga de trabajo en lugar de adivinar el comportamiento de la plataforma. Si tus solicitudes suelen completarse en segundos, mantén lo síncrono y evita la complejidad. El valor de la ruta asíncrona se manifiesta en solicitudes realmente lentas, no en todas.

Consultar el estado de finalización sin saturar la API

Un bucle de consulta periódica tiene tres responsabilidades: esperar lo suficiente para ser respetuoso, detenerse en algún momento y presentar con claridad los estados terminales. Aquí tienes una versión compacta.

import time

def poll_until_done(fetch_status, job_id, interval=3.0, timeout=600.0):
    deadline = time.monotonic() + timeout
    while True:
        status = fetch_status(job_id)
        state = status.get("status")
        if state in ("succeeded", "failed", "cancelled"):
            return status
        if time.monotonic() > deadline:
            raise TimeoutError(f"job {job_id} not finished after {timeout}s")
        time.sleep(interval)

Por qué esta estructura

fetch_status se inyecta, así el mismo bucle funciona tanto si consultas por HTTP como si consultas una cola interna. También hace que sea trivial probarlo con un objeto falso que devuelva running dos veces y luego succeeded.

La comprobación de estado es una lista blanca explícita de estados terminales, en lugar de comprobar una única cadena "done". Los estados terminales son variados (éxito, fallo, cancelación) y quieres detenerte cuando aparezca cualquiera de ellos, no girar en vacío hasta el tiempo de espera por buscar solo uno.

La fecha límite usa time.monotonic() para que los ajustes del reloj de pared no alarguen ni acorten accidentalmente la espera.

Elegir el intervalo de consulta

Empieza con unos pocos segundos y resiste el impulso de consultar en un bucle apretado. Los intervalos por debajo del segundo consumen presupuesto de solicitudes y añaden carga sin hacer que la tarea termine antes. Si esperas tareas largas, considera un retroceso suave, por ejemplo, aumentando el intervalo tras cada comprobación hasta un límite. Mantén el límite moderado; el objetivo es reducir solicitudes, no crear puntos ciegos de varios minutos.

El tiempo de espera no es opcional

Establece siempre un tiempo de espera, holgado en relación con el trabajo pero finito. Cuando se dispare, no devuelvas un resultado vacío en silencio. Lanza una excepción, registra el identificador de la tarea y deja que quien llama decida si reintentar: una tarea cuyo sondeo agotó el tiempo de espera puede seguir ejecutándose, y reenviarla a ciegas podría duplicar el trabajo.

Descargar y almacenar las salidas

Si la respuesta contiene referencias a artefactos (URL, identificadores de archivo o claves de almacenamiento), trátalas como efímeras. Pueden caducar, pueden estar limitadas al ámbito de tu cuenta y pueden no sobrevivir a un reintento. La copia duradera es la que guardas tú.

Patrón de obtención y persistencia

Cuando la tarea termina, lee la referencia del artefacto desde el estado terminal, obtenla una vez y escríbela en un destino que controles: una ruta local para scripts, un bucket de almacenamiento de objetos para cualquier uso cercano a producción. Registra el identificador de la tarea junto con el archivo almacenado para poder rastrear el artefacto hasta la solicitud que lo produjo.

def save_output(fetch_bytes, artifact_url, dest_path):
    data = fetch_bytes(artifact_url)
    with open(dest_path, "wb") as f:
        f.write(data)
    return dest_path

Prácticas para evitar la pérdida silenciosa de datos

Escribe en una ruta temporal y renombra tras el éxito, así una descarga fallida nunca deja un archivo a medio escribir que parezca válido. Si la respuesta expone el número de bytes o una suma de comprobación, verifícala. Para archivos grandes, escribe en disco en streaming en lugar de cargar toda la carga útil en memoria.

Conserva también la respuesta sin procesar de la llamada de estado final. Almacenar JSON es barato y, cuando alguien pregunta por qué un archivo parece incorrecto, un documento de estado con el nombre del modelo, la marca de tiempo y el id de la tarea es la forma más rápida de reconstruir lo ocurrido.

Por último, separa la recuperación del procesamiento. Primero descarga y almacena, luego transforma. Si analizas mientras descargas, un breve error de red parecerá un bug de formato y pasarás la tarde resolviendo el problema equivocado.

Depurar los fallos habituales

La mayoría de los fallos se agrupan en cuatro categorías. Cada una tiene una prueba rápida de aislamiento.

Errores de autenticación

Síntoma: un 401 o 403 antes de que importe nada del cuerpo de la solicitud. Comprueba que la clave se ha cargado realmente; imprime si la variable de entorno está definida, no su valor. Verifica el prefijo Bearer y el espacio que lo sigue. Confirma que el nombre de la cabecera está bien escrito y que ningún proxy la elimina. Si la misma clave funciona en curl pero no en tu aplicación, tu aplicación construye la cabecera de forma distinta; compara el dict literal de cabeceras con los flags de curl.

Cuerpo de solicitud mal formado

Síntoma: un 400 o 422 con un mensaje sobre el cuerpo. Valida tu JSON antes de enviarlo: json.dumps serializa casi cualquier cosa, incluidos dicts que no pretendías construir. Revisa comas finales pegadas desde archivos de configuración, contenido que debería ser una cadena pero se envía como lista y campos obligatorios ausentes. Reduce al cuerpo mínimo de la primera sección y vuelve a añadir campos uno a uno hasta que falle.

Nombre de modelo incorrecto

Síntoma: un rechazo que parece una solicitud incorrecta o un mensaje de modelo no encontrado. La cadena del modelo debe ser exactamente gpt-6.1-terra. Causas habituales: un valor por defecto obsoleto en una función auxiliar, una variable de entorno que contiene un valor distinto o un error tipográfico por reescribir en lugar de copiar y pegar. Mantenlo como constante de módulo y busca duplicados con grep.

Tiempo de espera agotado en el sondeo

Síntoma: tu bucle sale por tiempo de espera agotado mientras la tarea sigue viva. Primero, confirma que el identificador de tarea que consultas es el que enviaste; imprimir ambos al enviar es la comprobación más simple. Luego revisa tu lista de estados terminales: un estado que no previste hará que el bucle se ejecute para siempre. Por último, comprueba que tu intervalo y tu fecha límite encajan con la duración real de la tarea; una fecha límite ajustada para solicitudes cortas siempre se disparará en las largas. Registra la última carga útil de estado cuando se agote el tiempo de espera, así sabrás en qué estado estaba la tarea cuando la abandonaste.

Aborda estos problemas en orden (autenticación, cuerpo, modelo, sondeo), porque cada capa enmascara la inferior. Una cabecera rota hace que todos los cuerpos parezcan incorrectos.

Artículos relacionados

Ver todos los artículos →

Empieza a crear con artefactos de IA persistentes

Consigue tu API key y llama a modelos de imagen, vídeo y audio a través de una única API basada en tasks.

Leer la documentación de la API