Inovacc Developer

API reference

Decision engine

Structured, criteria-based decisions at a speed and precision tier you choose.

Base URL https://ai.inovacc.dev

Overview

The Decision engine answers structured questions about a piece of content with structured verdicts. You send a state (the thing to judge: a text, a record, a JSON object) and a set of questions, each of a known type; you get back one answer per question, with the label or score chosen, the probabilities behind it and a confidence. The endpoint is POST /v1/run on https://ai.inovacc.dev, called on a decision route of your organization.

The problem it solves is turning a judgement into data a program can act on. Asking a chat model "is this good?" returns prose you then have to parse and cannot compare from one call to the next. The Decision engine returns the same shape every time, so you can store it, threshold it and audit it.

Use it for classification, scoring, triage and checks against criteria: does this ticket need a person, which of five categories fits, how well does this answer meet the bar. When you need generated prose, use AI chat; when the verdict must rest on your own documents, retrieve the passages first with Knowledge and vector search and put them in the state.

Concepts

Decision routes. As everywhere in Inovacc AI, you call a route, never a model. GET /v1/models lists your routes; an entry whose endpoint is /v1/run is a run route, and the ones Inovacc configured as decision routes answer in the shape below.

Questions and their types. questions is an object of 1 to 64 entries, keyed by your own ids (letters, digits, _, . or -, up to 100 characters). Each question has a type, usually instructions, and criteria whose shape the type fixes:

TypeAsks forcriteria
noulyes or noan object {"true": "...", "false": "..."}
choiceone label among severalan object {"label": "description", ...}
scorea level on a scalean array of levels, lowest first

The shapes are strict: a criteria string such as "1-5" or "yes|no" is refused.

Answers. Each answer carries what its type produced: the choice or the score with probabilities, a legend and a confidence. A noul answer carries its probability p instead of a confidence.

Tiers. A decision route serves one of three tiers: fast, standard or precise. All answer in one shape, and the answer's engine.tier says which one answered. Confidence is not comparable between tiers: each tier reports on its own scale, so set a threshold per tier.

Escalation. With escalate, questions answered below a confidence threshold are asked again, alone, on the next stronger tier (fast, then standard, then precise), and the stronger answer replaces the weaker one. The defaults are fast 0.40, standard 0.80 and precise 0.55; you can pass min_confidence (one number, or one per tier) and max_tier. A noul answer's confidence is the distance of p from a coin flip, |2p - 1|.

How it works

Every call carries Authorization: Bearer <key> and an X-Operation-Id. Your organization comes from the key. The body takes only model, input and escalate. On a decision route input is {"state", "questions", "images"?}: state is any non-null JSON value, images at most 16 entries. Any other field inside input is refused with invalid_decision_input, and the reply never echoes it.

The answer is:

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

and its headers carry x-route-id, x-usage-input-tokens and x-usage-output-tokens.

With escalation, each answer also says which tier answered it and, when it was escalated, escalated_from lists the lower tiers and the confidence each gave. An escalation object reports every attempt, how many questions were asked and how many came back below threshold, and how many answers each tier gave in the end. Each attempt is its own call, reserved against your quotas and metered at its own tier. If an attempt fails, the escalation stops, the earlier answers are kept, and the request is still 200.

There is no streaming and no 60-second timeout on this endpoint. A successful answer is stored for 24 hours under its operation id: the same id returns the same merged answer with x-idempotent-replay: true and calls nothing. What is metered is tokens, at the price of each tier that answered.

Get started

You need an API key and a decision route enabled for your organization (Authentication).

  1. Find your decision route with GET /v1/models: an entry whose endpoint is /v1/run.
  2. Ask one noul question. POST /v1/run with your route as model, a short text as state and one question {"type":"noul","instructions":"...","criteria":{"true":"...","false":"..."}} (see the samples). The answer has your question id under result.answers, with its probability p.
  3. Add a score question with three levels to the same call. Both answers come back in one response; engine.tier names the tier.
  4. Escalate. Send the call again with a new operation id and "escalate": true. The answer now carries tier per answer and an escalation object listing the attempts.

Use cases

Ticket triage. Each incoming support ticket is the state; a choice question picks the team, a noul question asks whether a person must answer, a score question rates urgency. The answers are stored with the ticket, and a ticket whose confidence is below your threshold goes to a person.

Quality checks on generated text. Before an assistant's draft is sent, a decision call scores it against your bar ("Below the bar", "At the bar", "Above the bar") and checks a few noul rules. With escalate, only the drafts the fast tier was unsure about pay for the precise tier.

Content moderation with images. A listing's text and up to 16 images are judged against your policy questions; the per-question confidence decides between publishing, rejecting and sending to review.

Limits and pricing

LimitValue
Questions per call1 to 64
Question id1 to 100 characters: letters, digits, _, ., -
Images per call16
Request body1 MiB by default; your organization may be set between 1 KiB and 20 MiB
Input tokens per requestyour organization's cap
Stored answer for a replay24 hours
Requests, tokens, cost, concurrency, daily budgetsas set for your organization

Pricing: on request. The pricing unit is tokens, at the price of the tier that answered.

Errors

StatusCodeWhat it means and what to do
400invalid_inputinput is missing or not a JSON object.
400invalid_decision_inputinput is not {state, questions, images?} or a question is malformed; check types and ids.
400invalid_escalateescalate has a key or value it does not take, or the route is not a decision route.
400unsupported_fieldA top-level field other than model, input, escalate.
400model_not_allowed, input_too_largeUse a valid route id; shorten the state.
400missing_operation_id, invalid_operation_idSend a valid X-Operation-Id.
401missing_credentials, invalid_credentialsSend a valid key.
403route_forbiddenThe route is not yours, or is not a run route.
409operation_in_progressThat operation id is still running.
413request_too_largeThe body is over your size cap.
429rate_limited, quota_exceeded, concurrency_limited, budget_exhaustedSee Errors; wait Retry-After where given.
502upstream_errorThe tier failed, or a criteria shape was refused; check the criteria, then retry.
503service_unavailableDecisions cannot be reached; retry later.

Best practices

  • Write criteria in the strict shapes: an object for noul and choice, an array for score.
  • Keep thresholds per tier, never one number for all tiers.
  • Use escalate with max_tier to cap the cost: most questions stop at the cheaper tier.
  • Put everything the judgement needs in state, including retrieved passages; the engine sees nothing else.
  • Use stable question ids, so answers can be compared across calls and stored by id.
  • Derive the operation id from the item judged so a retried batch replays instead of paying twice.

Authentication

Every call carries your key; your organization comes from it. See the authentication guide.

HeaderAuthorizationBearer <API key>
HeaderX-Operation-Ida unique id you choose, on every call except GET

Endpoints

POST /v1/run

Example

Language

⋮
POST /v1/runExample

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))
Source details

Catalogue entry

Id
identity/component-taxonomy/capabilities#decision
Repository
identity
Path
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82

Example

Id
identity/component-taxonomy/quickstart#decision
Repository
identity
Path
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82
Updated 2026-10-10.