Inovacc Desarrolladores

Referencia de la API

Chat con IA

Respuestas de chat de los modelos de lenguaje de la plataforma.

URL base https://ai.inovacc.dev

Descripción general

El chat con IA da a tu aplicación respuestas conversacionales de los modelos de lenguaje que Inovacc ejecuta para tu organización. El endpoint, POST /v1/chat/completions, usa el formato de red de chat-completions de OpenAI, por lo que una biblioteca cliente de OpenAI existente puede llamarlo cambiando la URL base a https://ai.inovacc.dev/v1 y agregando dos encabezados. Envías una lista de mensajes y recibes la respuesta del asistente, ya sea como un solo documento JSON o como un flujo de eventos mientras se va escribiendo.

El problema que resuelve es el acceso sin infraestructura intermedia: tu organización no tiene cuentas, claves ni contratos con proveedores de modelos. Inovacc decide qué modelo atiende cada una de tus rutas, aplica los límites y presupuestos de tu organización antes de realizar una llamada, y registra el uso de cada llamada para que puedas ver cuánto gasta cada clave y cada ruta.

Usa el chat con IA cuando la respuesta sea texto libre: un asistente, un resumen, un borrador, una extracción a JSON, una respuesta que llama a tus herramientas. Otro producto encaja mejor cuando:

  • necesitas un veredicto estructurado frente a criterios (una puntuación, una elección, un sí o un no, con confianza): usa el Motor de decisiones;
  • la respuesta debe salir de tus propios documentos, con citas: consulta una colección en la nube de Conocimiento y búsqueda vectorial;
  • necesitas vectores en lugar de texto: usa los Embeddings de IA.

Conceptos

Rutas, no modelos. Nunca nombras un modelo. A tu organización se le asignan rutas: ids opacos como r00, configurados por Inovacc solo para tu organización. Una ruta decide qué modelo responde, su techo de salida y los campos que acepta. GET /v1/models lista las rutas que tu organización puede usar, cada una con el endpoint al que sirve; el id es siempre el id de la ruta, nunca el nombre de lo que la respalda. Envía el id como el campo model; envía "auto", o deja fuera model, y responde la ruta predeterminada de tu organización. Una ruta sirve exactamente un endpoint: una ruta de chat invocada desde /v1/embeddings o /v1/run se rechaza igual que una ruta que no tienes permitida.

Campos. Los campos comunes de OpenAI siempre se aceptan: messages, model, stream, stream_options, max_tokens, max_completion_tokens, temperature, top_p, stop, las penalizaciones, logit_bias, logprobs, tools, tool_choice, response_format, seed, reasoning_effort y algunos más. Siete campos (n, service_tier, store, metadata, modalities, audio, web_search_options) se aceptan solo cuando se han concedido a tu organización. Cualquier otro campo se rechaza por su nombre. Algunas rutas aceptan un conjunto más reducido (messages, model, stream, stream_options, los dos límites de salida, temperature, top_p); en ellas, tools y los demás campos se rechazan para esa ruta.

Mensajes e imágenes. content es una cadena, o un arreglo de partes de texto y partes image_url. Una imagen es una URL data: (PNG, JPEG, WebP o GIF, en base64) o una URL https://; una ruta cuyo modelo no puede obtener una URL rechaza la forma https://. Una solicitud lleva como máximo 20 imágenes, y cada una cuenta como 1,600 tokens de entrada estimados.

Techo de salida. max_tokens y max_completion_tokens solo pueden bajar el techo que fijan la ruta y tu organización; un valor mayor se recorta, nunca se rechaza. Los tokens de razonamiento que gasta un modelo están dentro de completion_tokens.

Streaming. Con "stream": true la respuesta es un text/event-stream de eventos chat.completion.chunk de OpenAI, cada uno una línea data:, que termina con data: [DONE]. Configura stream_options.include_usage para recibir un fragmento final con los conteos de tokens.

Encabezados de uso. Una respuesta sin streaming lleva x-route-id, x-usage-input-tokens y x-usage-output-tokens, y toda respuesta lleva x-operation-id.

Cómo funciona

Toda llamada lleva Authorization: Bearer <key> y un X-Operation-Id. Tu organización se obtiene de la clave. El id de operación es tu clave de idempotencia: de 8 a 128 letras, dígitos, _ o -. El servicio verifica, en orden: el id de operación, la clave, si ese id de operación ya se usó, el cuerpo y sus campos, la ruta, los mensajes, el tamaño de entrada estimado (una cuarta parte de los caracteres de todo el cuerpo de la solicitud, más la estimación de imágenes) y luego reserva la llamada contra las cuotas de tu organización. Solo cuando todo eso se cumple se llama a un modelo.

El id de la respuesta es siempre chatcmpl- seguido de tu id de operación, y su model es siempre el id de la ruta. Una respuesta cuyo presupuesto de salida completo se fue en razonamiento sigue siendo 200, con content vacío y finish_reason: "length"; una respuesta detenida por el filtro de seguridad del modelo es 200 con finish_reason: "content_filter".

Una respuesta exitosa sin streaming se almacena durante 24 horas bajo su id de operación. Si envías de nuevo el mismo id, recibes la respuesta almacenada, con x-idempotent-replay: true, sin una segunda llamada al modelo, un segundo cobro ni un cambio de cuota. Un error nunca se almacena, así que reintentar una llamada fallida con el mismo id la ejecuta de nuevo. Mientras una llamada sigue en ejecución, una segunda llamada con su id recibe 409 operation_in_progress.

Un stream nunca se almacena. Si el modelo falla a mitad de camino, el stream termina con un evento de error (upstream_error) y data: [DONE]; un stream que termina sin [DONE] se cortó y está incompleto. Lee delta.content en cada fragmento, incluidos el primero y el que lleva finish_reason.

Lo que se mide son tokens: de entrada y de salida, según los reporta el modelo, o la estimación cuando no hay un conteo disponible. GET /v1/usage/summary suma el uso de tu organización por clave, ruta, día u hora, y GET /v1/capabilities le dice a un agente lo que tu organización puede invocar; ninguno se cobra.

Primeros pasos

Necesitas una clave de API y al menos una ruta de chat habilitada para tu organización. Las claves se crean en la consola de Inovacc; consulta Autenticación.

  1. Lista tus rutas. Llama a GET /v1/models. Cada entrada con "endpoint": "/v1/chat/completions" es una ruta con la que puedes chatear; anota su id.
  2. Envía un primer mensaje. Llama a POST /v1/chat/completions con ese id como model, un mensaje user y un X-Operation-Id nuevo (consulta los ejemplos). El id de la respuesta termina con tu id de operación y x-usage-input-tokens muestra lo que se contó.
  3. Reinténtalo. Envía la misma solicitud con el mismo id de operación. La respuesta es idéntica y lleva x-idempotent-replay: true: nada se ejecutó dos veces.
  4. Usa streaming. Agrega "stream": true y "stream_options": {"include_usage": true} con un id de operación nuevo. Los fragmentos llegan mientras se escribe la respuesta, y el último antes de [DONE] lleva usage.
  5. Revisa el gasto. Llama a GET /v1/usage/summary?group_by=route para ver las llamadas y los tokens que acabas de generar.

Casos de uso

Un asistente de soporte. Tu widget de ayuda transmite la respuesta por streaming para que la persona la vea aparecer de inmediato. Cada turno del usuario es una llamada con un id de operación nuevo; tu servidor conserva la conversación y envía los mensajes recientes cada vez. Si la conexión se cae antes de [DONE], el widget muestra la respuesta como cortada y ofrece preguntar de nuevo.

Extracción estructurada en lote. Un trabajo nocturno lee documentos entrantes y pide un objeto JSON con response_format. Cada documento recibe un id de operación derivado de su propio id, de modo que cuando el trabajo se reinicia tras una caída, los documentos terminados regresan desde las respuestas almacenadas en segundos y no se cobran de nuevo.

Un agente que llama a tus herramientas. En una ruta que acepta tools, el modelo responde con tool_calls; tu código ejecuta la herramienta y envía el resultado de vuelta como un mensaje tool. Cada paso tiene su propio id de operación, así que un paso reintentado nunca ejecuta una herramienta dos veces del lado del modelo.

Límites y precios

LímiteValor
Cuerpo de la solicitud1 MiB por defecto; tu organización puede configurarse entre 1 KiB y 20 MiB
Imágenes por solicitud20
Costo de entrada estimado de una imagen1,600 tokens
Tokens de entrada por solicitudel tope de tu organización (400 input_too_large por encima de él)
Tokens de salida por solicitudel menor entre el techo de la ruta y el de tu organización
Tiempo hasta la primera respuesta del modelo60 segundos
Respuesta almacenada para una repetición24 horas
Una llamada mantenida como en curso5 minutos como máximo
Solicitudes por minuto y por día; tokens y costo por día y por mes; llamadas concurrentes; presupuestos diariossegún lo configurado para tu organización

Precios: a consultar. La unidad de precio son los tokens.

Errores

EstadoCódigoQué significa y qué hacer
400missing_operation_id, invalid_operation_idEnvía X-Operation-Id con 8 a 128 letras, dígitos, _ o -.
400invalid_bodyEl cuerpo no es un objeto JSON, o user no es una cadena. Corrige el cuerpo.
400invalid_messagesmessages está vacío o un mensaje está mal formado.
400unsupported_fieldUn campo de nivel superior que este endpoint no acepta; quítalo.
400unsupported_field_for_routeLa ruta acepta un conjunto más reducido de campos, o no puede obtener una URL de imagen; quita el campo o envía la imagen como data:.
400model_not_allowedEl valor de model no es un id de ruta válido. Usa un id de GET /v1/models.
400input_too_largeLa entrada supera el tope por solicitud de tu organización, o hay más de 20 imágenes. Acórtala.
401missing_credentials, invalid_credentialsEnvía una clave válida.
403key_disabled, organization_disabledLa clave o la organización fue deshabilitada; contacta a tu administrador.
403field_not_allowedUn campo que no se ha concedido a tu organización.
403route_forbiddenLa ruta no es una que tu organización pueda usar en este endpoint.
409operation_in_progressEse id de operación sigue en ejecución; espera y reintenta.
413request_too_largeEl cuerpo supera tu tope de tamaño.
429rate_limitedDemasiadas solicitudes; espera Retry-After segundos.
429quota_exceeded, concurrency_limitedSe agotó una asignación de tokens, costo o concurrencia; sin Retry-After.
429budget_exhaustedSe gastó un presupuesto diario; se restablece a las 00:00 UTC (Retry-After).
502upstream_errorEl modelo no respondió de forma utilizable; reintenta con el mismo id de operación.
503service_unavailable, route_unavailableReintenta más tarde; route_unavailable requiere que Inovacc corrija tu ruta.
504timeout_errorEl modelo no empezó a responder en 60 segundos; reintenta.

El envoltorio y cada tipo están en Errores.

Buenas prácticas

  • Un id de operación por solicitud lógica, reutilizado solo para reintentar esa misma solicitud. Un id reutilizado devuelve la primera respuesta sin importar lo que diga el cuerpo nuevo.
  • Reintenta en 502, 503, 504 y en errores de red con el mismo id de operación, con espera creciente. En 429 rate_limited y budget_exhausted, espera los segundos de Retry-After.
  • Configura max_tokens con lo que necesitas: reduce la reserva hecha contra tus cuotas y el costo de una respuesta descontrolada.
  • Vigila X-Budget-Warning: aparece cuando tu organización supera el umbral de aviso de un presupuesto, antes de que se rechacen las llamadas.
  • Usa stream para personas, no para trabajos: una respuesta con streaming no se puede repetir, una sin streaming sí.
  • Envía las imágenes como URL data: cuando no sepas si el modelo de la ruta puede obtener una URL, y mantenlas dentro del límite de 20 imágenes.
  • Nunca envíes una clave secreta a un navegador: llama al chat con IA desde tu servidor.

Autenticación

Toda llamada lleva tu clave; tu organización viene de ella. Consulta la guía de autenticación.

EncabezadoAuthorizationBearer <clave de API>
EncabezadoX-Operation-Idun id único que tú eliges, en toda llamada excepto GET

Endpoints

POST /v1/chat/completions

GET /v1/models

POST /v1/run

Ejemplo

Lenguaje

⋮
POST /v1/chat/completionsEjemplo

cURL

curl -X POST "https://ai.inovacc.dev/v1/chat/completions" \
  -H "Authorization: Bearer $INOVACC_API_KEY" \
  -H "X-Operation-Id: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "r00",
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ]
}'

TypeScript

import crypto from "node:crypto";

const body: Record<string, unknown> = {
  "model": "r00",
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ]
};

const url = "https://ai.inovacc.dev/v1/chat/completions";

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
    "X-Operation-Id": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});

console.log(response.status, await response.text());

Python

import json
import os
import urllib.request
import uuid

body = {
    "model": "r00",
    "messages": [
        {
            "role": "user",
            "content": "hello",
        },
    ],
}

request = urllib.request.Request(
    "https://ai.inovacc.dev/v1/chat/completions",
    data=json.dumps(body).encode(),
    method="POST",
    headers={
        "User-Agent": "inovacc-python-sample",
        "Authorization": "Bearer " + os.environ["INOVACC_API_KEY"],
        "X-Operation-Id": str(uuid.uuid4()),
        "Content-Type": "application/json",
    },
)

with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())

Go

package main

import (
	"crypto/rand"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

const url = "https://ai.inovacc.dev/v1/chat/completions"

const body = `{
  "model": "r00",
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ]
}`

// newUUID returns a random (version 4) UUID, from the standard library alone.
func newUUID() string {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		panic(err)
	}
	b[6] = b[6]&0x0f | 0x40
	b[8] = b[8]&0x3f | 0x80
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}

func main() {
	req, err := http.NewRequest("POST", url, strings.NewReader(body))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("INOVACC_API_KEY"))
	req.Header.Set("X-Operation-Id", newUUID())
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(res.Status, string(out))
}

Rust

// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// Cargo.toml: serde_json = "1"
// Cargo.toml: uuid = { version = "1", features = ["v4"] }

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("INOVACC_API_KEY")?;
    let body = serde_json::json!({
        "model": "r00",
        "messages": [
            {
                "role": "user",
                "content": "hello"
            }
        ]
    });
    let response = reqwest::blocking::Client::new()
        .post("https://ai.inovacc.dev/v1/chat/completions")
        .bearer_auth(api_key)
        .header("X-Operation-Id", uuid::Uuid::new_v4().to_string())
        .json(&body)
        .send()?;
    println!("{} {}", response.status(), response.text()?);
    Ok(())
}

JavaScript

import crypto from "node:crypto";

const body = {
  "model": "r00",
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ]
};

const url = "https://ai.inovacc.dev/v1/chat/completions";

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
    "X-Operation-Id": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});

console.log(response.status, await response.text());

PHP

<?php

$body = <<<'JSON'
{
  "model": "r00",
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ]
}
JSON;

function uuid4(): string
{
    $bytes = random_bytes(16);
    $bytes[6] = chr(ord($bytes[6]) & 0x0f | 0x40);
    $bytes[8] = chr(ord($bytes[8]) & 0x3f | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4));
}

$curl = curl_init('https://ai.inovacc.dev/v1/chat/completions');
curl_setopt_array($curl, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('INOVACC_API_KEY'),
        'X-Operation-Id: ' . uuid4(),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => $body,
]);

$response = curl_exec($curl);
echo curl_getinfo($curl, CURLINFO_HTTP_CODE), ' ', $response, "\n";
curl_close($curl);

Ruby

require "net/http"
require "securerandom"
require "uri"

uri = URI('https://ai.inovacc.dev/v1/chat/completions')
body = <<~'JSON'
{
  "model": "r00",
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ]
}
JSON

request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('INOVACC_API_KEY')}"
request["X-Operation-Id"] = SecureRandom.uuid
request["Content-Type"] = "application/json"
request.body = body

response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
  http.request(request)
end

puts "#{response.code} #{response.body}"

Java

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.UUID;

public class Main {
    public static void main(String[] args) throws Exception {
        String apiKey = System.getenv("INOVACC_API_KEY");
        String body = "{" +
                "\"model\": \"r00\"," +
                "\"messages\": [" +
                "{" +
                "\"role\": \"user\"," +
                "\"content\": \"hello\"" +
                "}" +
                "]" +
                "}";

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://ai.inovacc.dev/v1/chat/completions"))
                .header("Authorization", "Bearer " + apiKey)
                .header("X-Operation-Id", UUID.randomUUID().toString())
                .header("Content-Type", "application/json")
                .method("POST", HttpRequest.BodyPublishers.ofString(body))
                .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
                .send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.statusCode() + " " + response.body());
    }
}

C#

using System.Text;

var body = "{" +
    "\"model\": \"r00\"," +
    "\"messages\": [" +
    "{" +
    "\"role\": \"user\"," +
    "\"content\": \"hello\"" +
    "}" +
    "]" +
    "}";

var url = "https://ai.inovacc.dev/v1/chat/completions";
var apiKey = Environment.GetEnvironmentVariable("INOVACC_API_KEY");

using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Add("Authorization", $"Bearer {apiKey}");
request.Headers.Add("X-Operation-Id", Guid.NewGuid().ToString());
request.Content = new StringContent(body, Encoding.UTF8, "application/json");

using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");

Kotlin

import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.util.UUID

fun main() {
    val apiKey = System.getenv("INOVACC_API_KEY")
    val body = "{" +
        "\"model\": \"r00\"," +
        "\"messages\": [" +
        "{" +
        "\"role\": \"user\"," +
        "\"content\": \"hello\"" +
        "}" +
        "]" +
        "}"

    val request = HttpRequest.newBuilder()
        .uri(URI.create("https://ai.inovacc.dev/v1/chat/completions"))
        .header("Authorization", "Bearer " + apiKey)
        .header("X-Operation-Id", UUID.randomUUID().toString())
        .header("Content-Type", "application/json")
        .method("POST", HttpRequest.BodyPublishers.ofString(body))
        .build()

    val response = HttpClient.newHttpClient()
        .send(request, HttpResponse.BodyHandlers.ofString())
    println("${response.statusCode()} ${response.body()}")
}

Swift

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

let body = #"""
{
  "model": "r00",
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ]
}
"""#

let apiKey = ProcessInfo.processInfo.environment["INOVACC_API_KEY"] ?? ""

let url = URL(string: "https://ai.inovacc.dev/v1/chat/completions")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue(UUID().uuidString, forHTTPHeaderField: "X-Operation-Id")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = Data(body.utf8)

let (data, response) = try await URLSession.shared.data(for: request)
let status = (response as? HTTPURLResponse)?.statusCode ?? 0
print(status, String(decoding: data, as: UTF8.self))
Detalles de la fuente

Entrada del catálogo

Id
identity/component-taxonomy/capabilities#ai.chat
Repositorio
identity
Ruta
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82

Ejemplo

Id
identity/component-taxonomy/quickstart#ai.chat
Repositorio
identity
Ruta
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82
Actualizado el 2026-10-10.