Documentación de la API
La API de Proyecto IA es compatible con la de OpenAI. Si ya usas su SDK o cualquier biblioteca que hable ese protocolo, solo tienes que cambiar la URL base y la clave.
URL base
Todas las rutas cuelgan de:
https://151-115-80-103.sslip.io/v1
Las peticiones y respuestas van en JSON con codificación UTF-8. Las rutas /health y /metrics existen para supervisión y no requieren clave.
Autenticación
Cada petición lleva tu clave en la cabecera Authorization con el esquema Bearer. Las claves empiezan por sk-ia- y se muestran una sola vez al crearlas: guardamos únicamente un resumen SHA-256, así que si la pierdes hay que revocarla y emitir otra.
Authorization: Bearer sk-ia-TU_CLAVE
No incluyas la clave en código que llegue al navegador ni la subas a un repositorio. Guárdala en una variable de entorno del servidor.
Listar modelos
GET /v1/models
Devuelve los modelos disponibles en tu plan. Los identificadores cambian con cada versión publicada; consúltalos antes de fijarlos en tu configuración.
curl https://151-115-80-103.sslip.io/v1/models \
-H "Authorization: Bearer $CLAVE"
Respuesta:
{
"object": "list",
"data": [
{ "id": "qwen2.5:7b", "object": "model", "owned_by": "Proyecto IA" },
{ "id": "gemma3:4b", "object": "model", "owned_by": "Proyecto IA" }
]
}
Chat
POST /v1/chat/completions
Recibe una conversación y devuelve la siguiente respuesta del modelo. Si no indicas model, se usa el modelo por defecto de tu plan.
- model
- Identificador del modelo, tal como lo devuelve
/v1/models. - messages
- Lista de mensajes con
role(system,useroassistant) ycontent. - max_tokens
- Máximo de tokens de salida. Con modelos que razonan internamente, un valor bajo puede devolver texto vacío.
- temperature
- De 0 a 2. Valores bajos dan respuestas más deterministas.
- stream
truepara recibir la respuesta por fragmentos (ver más abajo).
curl https://151-115-80-103.sslip.io/v1/chat/completions \
-H "Authorization: Bearer $CLAVE" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5:7b",
"messages": [
{ "role": "system", "content": "Responde en español, de forma breve." },
{ "role": "user", "content": "Resume en una frase qué es la fotosíntesis." }
],
"max_tokens": 200
}'
Respuesta:
{
"id": "chatcmpl-7f3a",
"object": "chat.completion",
"created": 1762080000,
"model": "qwen2.5:7b",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "La fotosíntesis es el proceso por el que las plantas convierten luz, agua y CO2 en azúcares y oxígeno."
},
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 31, "completion_tokens": 27, "total_tokens": 58 }
}
Chat con streaming
Con "stream": true la respuesta llega como eventos server-sent events: una línea data: por fragmento y una última línea data: [DONE]. Usa -N en curl para que no almacene la salida.
curl -N https://151-115-80-103.sslip.io/v1/chat/completions \
-H "Authorization: Bearer $CLAVE" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5:7b",
"stream": true,
"stream_options": { "include_usage": true },
"messages": [{ "role": "user", "content": "Cuenta hasta cinco en español." }]
}'
Fragmentos recibidos:
data: {"id":"chatcmpl-7f3b","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"Uno"},"finish_reason":null}]}
data: {"id":"chatcmpl-7f3b","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":", dos"},"finish_reason":null}]}
data: {"id":"chatcmpl-7f3b","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":14,"completion_tokens":11,"total_tokens":25}}
data: [DONE]
El campo usage llega en el último fragmento si pides stream_options.include_usage; es lo que se contabiliza para la factura.
Python
Instala el SDK oficial de OpenAI (pip install openai) y apunta base_url a nuestra API.
import os
from openai import OpenAI
cliente = OpenAI(
base_url="https://151-115-80-103.sslip.io/v1",
api_key=os.environ["CLAVE_IA"],
)
respuesta = cliente.chat.completions.create(
model="qwen2.5:7b",
messages=[
{"role": "system", "content": "Eres un asistente que responde en español."},
{"role": "user", "content": "¿Qué plazo tiene una pyme para presentar el IVA trimestral?"},
],
max_tokens=300,
)
print(respuesta.choices[0].message.content)
Con streaming:
flujo = cliente.chat.completions.create(
model="qwen2.5:7b",
messages=[{"role": "user", "content": "Escribe un saludo breve."}],
stream=True,
)
for fragmento in flujo:
texto = fragmento.choices[0].delta.content if fragmento.choices else None
if texto:
print(texto, end="", flush=True)
print()
JavaScript
Con el paquete openai en Node 18 o superior. Nunca ejecutes este código en el navegador: la clave quedaría expuesta.
import OpenAI from "openai";
const cliente = new OpenAI({
baseURL: "https://151-115-80-103.sslip.io/v1",
apiKey: process.env.CLAVE_IA,
});
const respuesta = await cliente.chat.completions.create({
model: "qwen2.5:7b",
messages: [{ role: "user", content: "Explica en dos frases qué es un contrato de encargo de tratamiento." }],
max_tokens: 200,
});
console.log(respuesta.choices[0].message.content);
Sin dependencias, con fetch:
const res = await fetch("https://151-115-80-103.sslip.io/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CLAVE_IA}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "qwen2.5:7b",
messages: [{ role: "user", content: "Hola, ¿qué puedes hacer?" }],
}),
});
const datos = await res.json();
console.log(datos.choices[0].message.content);
Errores
Los errores usan el mismo formato que OpenAI:
{ "error": { "message": "Clave inválida o revocada.", "type": "authentication_error" } }
| Código | Significado | Qué hacer |
|---|---|---|
401 | Clave ausente, mal formada, inválida o revocada. | Comprueba la cabecera Authorization y que la clave sigue activa. |
429 | Has superado las peticiones por minuto de tu clave. La respuesta incluye la cabecera Retry-After con los segundos de espera. | Espera lo que indica Retry-After y reintenta con retroceso exponencial. Si lo necesitas de forma habitual, pide un límite mayor. |
503 | Ningún backend de inferencia responde en este momento. | Reintenta pasados unos segundos. Si persiste, escríbenos a erspagos@gmail.com. |
400 | Petición mal formada: JSON inválido, mensajes vacíos o modelo inexistente. | Revisa el cuerpo de la petición con los ejemplos de esta página. |
Límites
- Peticiones por minuto: 60 por clave por defecto. Cada plan puede fijar un límite distinto; en despliegue privado no hay límite.
- Tiempo máximo por petición: 120 segundos. Para generaciones largas usa streaming.
- Contexto: depende del modelo; entre 4.096 y 32.768 tokens. Lo indicamos en la ficha de cada modelo.
- Cuota de tokens: los planes mensuales incluyen una cuota; el exceso se factura al precio por millón de tokens de la página de precios.
Residencia de datos
- Toda la inferencia se ejecuta en servidores situados en la Unión Europea. No hay transferencias internacionales de datos.
- El contenido de tus peticiones no se almacena una vez servida la respuesta y nunca se usa para entrenar modelos, salvo contrato explícito contigo.
- Registramos solo lo necesario para facturar y operar: identificador de clave, fecha, modelo, tokens de entrada y salida y latencia.
- Firmamos contrato de encargo de tratamiento (artículo 28 del RGPD) con cada cliente. Pídelo en erspagos@gmail.com.
- Si tu aplicación conversa con usuarios finales, el Reglamento de IA exige indicarles que hablan con una IA. Es tu responsabilidad como integrador; te ayudamos con el texto si lo necesitas.