Inovacc Desarrolladores

Referencia de la API

Motor de decisión

Decisiones estructuradas por criterios, en un nivel de velocidad y precisión que tú eliges.

URL base https://ai.inovacc.dev

Descripción general

El motor de decisiones responde preguntas estructuradas sobre un contenido con veredictos estructurados. Envías un estado (aquello que se va a juzgar: un texto, un registro, un objeto JSON) y un conjunto de preguntas, cada una de un tipo conocido; recibes una respuesta por pregunta, con la etiqueta o puntuación elegida, las probabilidades detrás de ella y una confianza. El endpoint es POST /v1/run en https://ai.inovacc.dev, invocado en una ruta de decisión de tu organización.

El problema que resuelve es convertir un juicio en datos sobre los que un programa pueda actuar. Preguntarle a un modelo de chat "¿esto es bueno?" devuelve prosa que luego tienes que analizar y que no puedes comparar de una llamada a otra. El motor de decisiones devuelve siempre la misma forma, de modo que puedes almacenarla, aplicarle umbrales y auditarla.

Úsalo para clasificación, puntuación, triaje y verificaciones frente a criterios: si este ticket necesita a una persona, cuál de cinco categorías encaja, qué tan bien cumple esta respuesta con el estándar. Cuando necesites prosa generada, usa el Chat con IA; cuando el veredicto deba apoyarse en tus propios documentos, recupera primero los pasajes con Conocimiento y búsqueda vectorial y colócalos en el estado.

Conceptos

Rutas de decisión. Como en toda la IA de Inovacc, llamas a una ruta, nunca a un modelo. GET /v1/models lista tus rutas; una entrada cuyo endpoint es /v1/run es una ruta de ejecución, y las que Inovacc configuró como rutas de decisión responden con la forma que se describe más abajo.

Preguntas y sus tipos. questions es un objeto de 1 a 64 entradas, con claves que son tus propios ids (letras, dígitos, _, . o -, hasta 100 caracteres). Cada pregunta tiene un type, normalmente instructions, y criteria cuya forma queda fijada por el tipo:

TipoPidecriteria
noulsí o noun objeto {"true": "...", "false": "..."}
choiceuna etiqueta entre variasun objeto {"label": "description", ...}
scoreun nivel en una escalaun arreglo de niveles, del más bajo al más alto

Las formas son estrictas: una cadena de criterios como "1-5" o "yes|no" se rechaza.

Respuestas. Cada respuesta lleva lo que produjo su tipo: el choice o el score con probabilities, una legend y una confidence. Una respuesta noul lleva su probabilidad p en lugar de una confianza.

Niveles. Una ruta de decisión sirve uno de tres niveles: fast, standard o precise. Todos responden con una sola forma, y el engine.tier de la respuesta indica cuál respondió. La confianza no es comparable entre niveles: cada nivel reporta en su propia escala, así que configura un umbral por nivel.

Escalamiento. Con escalate, las preguntas respondidas por debajo de un umbral de confianza se vuelven a hacer, solas, en el siguiente nivel más fuerte (fast, luego standard, luego precise), y la respuesta más fuerte reemplaza a la más débil. Los valores predeterminados son fast 0.40, standard 0.80 y precise 0.55; puedes pasar min_confidence (un número, o uno por nivel) y max_tier. La confianza de una respuesta noul es la distancia de p a un lanzamiento de moneda, |2p - 1|.

Cómo funciona

Toda llamada lleva Authorization: Bearer <key> y un X-Operation-Id. Tu organización se obtiene de la clave. El cuerpo toma solo model, input y escalate. En una ruta de decisión input es {"state", "questions", "images"?}: state es cualquier valor JSON no nulo, images tiene como máximo 16 entradas. Cualquier otro campo dentro de input se rechaza con invalid_decision_input, y la respuesta nunca lo repite.

La respuesta es:

{"model": "<route id>", "result": {"answers": {...}, "usage": {"input_tokens", "output_tokens"}}, "state": "Completed", "engine": {"tier", "latency_ms"}}

y sus encabezados llevan x-route-id, x-usage-input-tokens y x-usage-output-tokens.

Con escalamiento, cada respuesta también indica qué tier la respondió y, cuando fue escalada, escalated_from lista los niveles inferiores y la confianza que dio cada uno. Un objeto escalation reporta cada intento, cuántas preguntas se hicieron y cuántas volvieron por debajo del umbral, y cuántas respuestas dio cada nivel al final. Cada intento es su propia llamada, reservada contra tus cuotas y medida en su propio nivel. Si un intento falla, el escalamiento se detiene, se conservan las respuestas anteriores, y la solicitud sigue siendo 200.

No hay streaming ni un tiempo límite de 60 segundos en este endpoint. Una respuesta exitosa se almacena durante 24 horas bajo su id de operación: el mismo id devuelve la misma respuesta combinada con x-idempotent-replay: true y no llama a nada. Lo que se mide son los tokens, al precio de cada nivel que respondió.

Primeros pasos

Necesitas una clave de API y una ruta de decisión habilitada para tu organización (Autenticación).

  1. Encuentra tu ruta de decisión con GET /v1/models: una entrada cuyo endpoint sea /v1/run.
  2. Haz una pregunta noul. POST /v1/run con tu ruta como model, un texto corto como state y una pregunta {"type":"noul","instructions":"...","criteria":{"true":"...","false":"..."}} (consulta los ejemplos). La respuesta tiene el id de tu pregunta bajo result.answers, con su probabilidad p.
  3. Agrega una pregunta score con tres niveles a la misma llamada. Ambas respuestas regresan en una sola respuesta; engine.tier nombra el nivel.
  4. Escala. Envía la llamada de nuevo con un nuevo id de operación y "escalate": true. La respuesta ahora lleva tier por respuesta y un objeto escalation que lista los intentos.

Casos de uso

Triaje de tickets. Cada ticket de soporte entrante es el state; una pregunta choice elige el equipo, una pregunta noul pregunta si debe responder una persona, una pregunta score califica la urgencia. Las respuestas se almacenan con el ticket, y un ticket cuya confianza esté por debajo de tu umbral pasa a una persona.

Verificaciones de calidad sobre texto generado. Antes de enviar el borrador de un asistente, una llamada de decisión lo puntúa frente a tu estándar ("Por debajo del estándar", "En el estándar", "Por encima del estándar") y verifica algunas reglas noul. Con escalate, solo los borradores sobre los que el nivel rápido tuvo dudas pagan por el nivel preciso.

Moderación de contenido con imágenes. El texto de una publicación y hasta 16 imágenes se juzgan frente a las preguntas de tu política; la confianza por pregunta decide entre publicar, rechazar y enviar a revisión.

Límites y precios

LímiteValor
Preguntas por llamada1 a 64
Id de pregunta1 a 100 caracteres: letras, dígitos, _, ., -
Imágenes por llamada16
Cuerpo de la solicitud1 MiB por defecto; tu organización puede configurarse entre 1 KiB y 20 MiB
Tokens de entrada por solicitudel tope de tu organización
Respuesta almacenada para una repetición24 horas
Solicitudes, tokens, costo, concurrencia, presupuestos diariossegún lo configurado para tu organización

Precios: a consultar. La unidad de precio son los tokens, al precio del nivel que respondió.

Errores

EstadoCódigoQué significa y qué hacer
400invalid_inputFalta input o no es un objeto JSON.
400invalid_decision_inputinput no es {state, questions, images?} o una pregunta está mal formada; revisa los tipos y los ids.
400invalid_escalateescalate tiene una clave o un valor que no admite, o la ruta no es una ruta de decisión.
400unsupported_fieldUn campo de nivel superior distinto de model, input, escalate.
400model_not_allowed, input_too_largeUsa un id de ruta válido; acorta el estado.
400missing_operation_id, invalid_operation_idEnvía un X-Operation-Id válido.
401missing_credentials, invalid_credentialsEnvía una clave válida.
403route_forbiddenLa ruta no es tuya, o no es una ruta de ejecución.
409operation_in_progressEse id de operación sigue en ejecución.
413request_too_largeEl cuerpo supera tu tope de tamaño.
429rate_limited, quota_exceeded, concurrency_limited, budget_exhaustedConsulta Errores; espera Retry-After cuando se indique.
502upstream_errorFalló el nivel, o se rechazó la forma de los criterios; revisa los criterios y luego reintenta.
503service_unavailableNo se puede acceder a las decisiones; reintenta más tarde.

Buenas prácticas

  • Escribe los criterios en las formas estrictas: un objeto para noul y choice, un arreglo para score.
  • Mantén umbrales por nivel, nunca un solo número para todos los niveles.
  • Usa escalate con max_tier para limitar el costo: la mayoría de las preguntas se detienen en el nivel más barato.
  • Coloca en state todo lo que el juicio necesita, incluidos los pasajes recuperados; el motor no ve nada más.
  • Usa ids de pregunta estables, para que las respuestas puedan compararse entre llamadas y almacenarse por id.
  • Deriva el id de operación del elemento juzgado para que un lote reintentado repita la respuesta en lugar de pagar dos veces.

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/run

Ejemplo

Lenguaje

⋮
POST /v1/runEjemplo

cURL

curl -X POST "https://ai.inovacc.dev/v1/run" \
  -H "Authorization: Bearer $INOVACC_API_KEY" \
  -H "X-Operation-Id: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "r_eval",
  "input": {
    "state": "The reply resolves the customer'\''s billing question and links the invoice.",
    "questions": {
      "meets_bar": {
        "type": "score",
        "instructions": "Rate the reply against our support bar.",
        "criteria": [
          "Below the bar",
          "At the bar",
          "Above the bar"
        ]
      }
    }
  }
}'

TypeScript

import crypto from "node:crypto";

const body: Record<string, unknown> = {
  "model": "r_eval",
  "input": {
    "state": "The reply resolves the customer's billing question and links the invoice.",
    "questions": {
      "meets_bar": {
        "type": "score",
        "instructions": "Rate the reply against our support bar.",
        "criteria": [
          "Below the bar",
          "At the bar",
          "Above the bar"
        ]
      }
    }
  }
};

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

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": "r_eval",
    "input": {
        "state": "The reply resolves the customer's billing question and links the invoice.",
        "questions": {
            "meets_bar": {
                "type": "score",
                "instructions": "Rate the reply against our support bar.",
                "criteria": [
                    "Below the bar",
                    "At the bar",
                    "Above the bar",
                ],
            },
        },
    },
}

request = urllib.request.Request(
    "https://ai.inovacc.dev/v1/run",
    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/run"

const body = `{
  "model": "r_eval",
  "input": {
    "state": "The reply resolves the customer's billing question and links the invoice.",
    "questions": {
      "meets_bar": {
        "type": "score",
        "instructions": "Rate the reply against our support bar.",
        "criteria": [
          "Below the bar",
          "At the bar",
          "Above the bar"
        ]
      }
    }
  }
}`

// 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": "r_eval",
        "input": {
            "state": "The reply resolves the customer's billing question and links the invoice.",
            "questions": {
                "meets_bar": {
                    "type": "score",
                    "instructions": "Rate the reply against our support bar.",
                    "criteria": [
                        "Below the bar",
                        "At the bar",
                        "Above the bar"
                    ]
                }
            }
        }
    });
    let response = reqwest::blocking::Client::new()
        .post("https://ai.inovacc.dev/v1/run")
        .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": "r_eval",
  "input": {
    "state": "The reply resolves the customer's billing question and links the invoice.",
    "questions": {
      "meets_bar": {
        "type": "score",
        "instructions": "Rate the reply against our support bar.",
        "criteria": [
          "Below the bar",
          "At the bar",
          "Above the bar"
        ]
      }
    }
  }
};

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

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": "r_eval",
  "input": {
    "state": "The reply resolves the customer's billing question and links the invoice.",
    "questions": {
      "meets_bar": {
        "type": "score",
        "instructions": "Rate the reply against our support bar.",
        "criteria": [
          "Below the bar",
          "At the bar",
          "Above the bar"
        ]
      }
    }
  }
}
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/run');
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/run')
body = <<~'JSON'
{
  "model": "r_eval",
  "input": {
    "state": "The reply resolves the customer's billing question and links the invoice.",
    "questions": {
      "meets_bar": {
        "type": "score",
        "instructions": "Rate the reply against our support bar.",
        "criteria": [
          "Below the bar",
          "At the bar",
          "Above the bar"
        ]
      }
    }
  }
}
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\": \"r_eval\"," +
                "\"input\": {" +
                "\"state\": \"The reply resolves the customer's billing question and links the invoice.\"," +
                "\"questions\": {" +
                "\"meets_bar\": {" +
                "\"type\": \"score\"," +
                "\"instructions\": \"Rate the reply against our support bar.\"," +
                "\"criteria\": [" +
                "\"Below the bar\"," +
                "\"At the bar\"," +
                "\"Above the bar\"" +
                "]" +
                "}" +
                "}" +
                "}" +
                "}";

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://ai.inovacc.dev/v1/run"))
                .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\": \"r_eval\"," +
    "\"input\": {" +
    "\"state\": \"The reply resolves the customer's billing question and links the invoice.\"," +
    "\"questions\": {" +
    "\"meets_bar\": {" +
    "\"type\": \"score\"," +
    "\"instructions\": \"Rate the reply against our support bar.\"," +
    "\"criteria\": [" +
    "\"Below the bar\"," +
    "\"At the bar\"," +
    "\"Above the bar\"" +
    "]" +
    "}" +
    "}" +
    "}" +
    "}";

var url = "https://ai.inovacc.dev/v1/run";
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\": \"r_eval\"," +
        "\"input\": {" +
        "\"state\": \"The reply resolves the customer's billing question and links the invoice.\"," +
        "\"questions\": {" +
        "\"meets_bar\": {" +
        "\"type\": \"score\"," +
        "\"instructions\": \"Rate the reply against our support bar.\"," +
        "\"criteria\": [" +
        "\"Below the bar\"," +
        "\"At the bar\"," +
        "\"Above the bar\"" +
        "]" +
        "}" +
        "}" +
        "}" +
        "}"

    val request = HttpRequest.newBuilder()
        .uri(URI.create("https://ai.inovacc.dev/v1/run"))
        .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": "r_eval",
  "input": {
    "state": "The reply resolves the customer's billing question and links the invoice.",
    "questions": {
      "meets_bar": {
        "type": "score",
        "instructions": "Rate the reply against our support bar.",
        "criteria": [
          "Below the bar",
          "At the bar",
          "Above the bar"
        ]
      }
    }
  }
}
"""#

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

let url = URL(string: "https://ai.inovacc.dev/v1/run")!
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#decision
Repositorio
identity
Ruta
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82

Ejemplo

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