Logic - Decisions

Free Decision API EndpointAgent Optimal

Send the facts and your rules, and get typed decisions back: yes or no, one of several options, or a level on a scale. Rules are the default and explain each answer. Optional model selection uses the same endpoint with a separate question format and its own usage limits.

  • No API key
  • One POST
  • Rules by default
  • Optional model selection

Decide

POST/decide

https://aisenseapi.com/services/v1/decide

Call the free Decision API endpoint

Send a state, the facts to decide on, and named questions. Each question has rules, and each rule is a condition and a weight. This one routes a support ticket and decides whether the refund can go through at once:

curl -s -X POST https://aisenseapi.com/services/v1/decide \
  -H "Content-Type: application/json" \
  -d '{
  "state": {
    "ticket": {
      "subject": "Mug arrived broken, I want my money back",
      "amount": 30,
      "tier": "gold",
      "order_age_days": 3,
      "has_photo": true
    }
  },
  "questions": {
    "team": {
      "type": "choice",
      "options": {
        "returns": [
          {"if": {"ticket.subject": {"contains": "broken"}}, "weight": 3},
          {"if": {"ticket.subject": {"contains": "return"}}, "weight": 2}
        ],
        "billing": [
          {"if": {"ticket.subject": {"contains": "money"}}, "weight": 1},
          {"if": {"ticket.subject": {"contains": "charged"}}, "weight": 2}
        ],
        "shipping": [{"if": {"ticket.subject": {"contains": "tracking"}}, "weight": 2}]
      }
    },
    "refund_now": {
      "type": "yes_no",
      "bias": -2,
      "rules": [
        {"if": {"ticket.amount": {"lte": 50}}, "weight": 2},
        {"if": {"ticket.tier": {"eq": "gold"}}, "weight": 1},
        {"if": {"ticket.order_age_days": {"lte": 14}}, "weight": 1},
        {"if": {"ticket.has_photo": {"eq": true}}, "weight": 1}
      ]
    }
  }
}'
{
  "answers": {
    "team": {
      "type": "choice",
      "choice": "returns",
      "probabilities": {"returns": 0.8438, "billing": 0.1142, "shipping": 0.042},
      "confidence": 0.7657,
      "action": "review",
      "because": [
        {"option": "returns", "rule": 0, "weight": 3},
        {"option": "billing", "rule": 0, "weight": 1}
      ]
    },
    "refund_now": {
      "type": "yes_no",
      "answer": "yes",
      "probability": 0.9526,
      "confidence": 0.9051,
      "action": "act",
      "because": [
        {"rule": 0, "weight": 2},
        {"rule": 1, "weight": 1},
        {"rule": 2, "weight": 1},
        {"rule": 3, "weight": 1}
      ]
    }
  }
}

The refund gets −2 + 2 + 1 + 1 + 1 = 3, and sigmoid(3) = 0.9526. Its confidence, 0.9051, is above 0.9, so the action is act: it can go through without anyone looking. The team gets softmax over 3, 1 and 0, and a confidence of 0.7657 says review. because lists the rules that held, including the word "money" that pulled a little towards billing.

Three kinds of rule question

Omit model or send "model":"rules" for the existing rule format. Its response is unchanged.

TypeAnswers withHow it is worked out
yes_noanswer, probabilitysigmoid(bias + the weights of the rules that held)
choicechoice, probabilitiessoftmax over each option: its prior plus the weights of its rules that held
scalelevel, expected, probabilitiesa choice over ordered levels; expected counts the first level as 0

Every answer also has confidence, action and because. A weight is evidence in log-odds: positive pulls towards the answer, negative away, and 1 multiplies the odds by about 2.7. bias is where a yes_no starts, 0 unless you set it, and priors give options or levels a head start.

Conditions

A condition is an object of fields and tests, and every field must hold. A field is a dotted path into the state, where a number picks from a list: items.0.sku. any is a list of conditions of which one must hold, and not holds when its condition does not.

TestHolds when the value
eq, neequals, or does not equal, the given text, number, true, false or null. 1 equals 1.0; true is not 1
gt, gte, lt, lteis a number above, at least, below or at most the given number
inequals one of a list of up to 100 values
existsis there (true) or is not (false)
prefixis text that starts with the given text, whatever the case
containsis text that contains the given text, whatever the case, or a list that holds the given item

A path that leads nowhere fails every test except exists: false, so ne on a missing field does not hold. There are no regular expressions, so no condition can make the server spend minutes on one match.

Confidence and the action

Confidence is (n × the largest probability − 1) / (n − 1): 1 when everything is on one answer, 0 when it is spread evenly. For yes_no it is |2p − 1|. The action is act from act_at, 0.9 unless you set it, review from review_at, 0.5 unless you set it, and hold below. Set the thresholds by what a wrong answer costs: a refund of a few euros can act at 0.9, a deletion should not act at all. When two options tie at the top, the first one listed wins and tied names them, so a split is never hidden.

More examples

Can an agent run this command without asking?

A cheap gate in front of an agent that runs commands. The default is yes, and dangerous patterns pull it down.

curl -s -X POST https://aisenseapi.com/services/v1/decide \
  -H "Content-Type: application/json" \
  -d '{
  "state": {"command": "git push --force origin main", "inside_repo": true, "tests_passed": true},
  "questions": {
    "auto_run": {
      "type": "yes_no",
      "bias": 3,
      "rules": [
        {"if": {"command": {"contains": "--force"}}, "weight": -4},
        {"if": {"command": {"contains": "rm -rf"}}, "weight": -6},
        {"if": {"command": {"contains": "| sh"}}, "weight": -6},
        {"if": {"command": {"contains": " main"}}, "weight": -1},
        {"if": {"inside_repo": {"eq": false}}, "weight": -3},
        {"if": {"tests_passed": {"eq": true}}, "weight": 1}
      ]
    }
  }
}'
{
  "answers": {
    "auto_run": {
      "type": "yes_no",
      "answer": "no",
      "probability": 0.2689,
      "confidence": 0.4621,
      "action": "hold",
      "because": [{"rule": 0, "weight": -4}, {"rule": 3, "weight": -1}, {"rule": 5, "weight": 1}]
    }
  }
}

3 − 4 − 1 + 1 = −1 gives 0.2689: no, with a confidence of 0.46, so hold and ask a person. The same request with git status answers yes at 0.982 and act.

How bad is this alert?

A scale with four levels, acting at 0.8, because paging the person on call is right a little more often than it is wrong.

curl -s -X POST https://aisenseapi.com/services/v1/decide \
  -H "Content-Type: application/json" \
  -d '{
  "state": {"alert": {"service": "payments", "error_rate": 0.12, "customers_affected": 340}},
  "questions": {
    "severity": {
      "type": "scale",
      "act_at": 0.8,
      "levels": {
        "low": [],
        "normal": [{"if": {"alert.error_rate": {"lt": 0.05}}, "weight": 2}],
        "high": [
          {"if": {"alert.error_rate": {"gte": 0.05}}, "weight": 2},
          {"if": {"alert.service": {"eq": "payments"}}, "weight": 1}
        ],
        "urgent": [
          {"if": {"alert.error_rate": {"gte": 0.1}}, "weight": 2},
          {"if": {"alert.customers_affected": {"gte": 100}}, "weight": 2},
          {"if": {"alert.service": {"eq": "payments"}}, "weight": 1}
        ]
      }
    }
  }
}'
{
  "answers": {
    "severity": {
      "type": "scale",
      "level": "urgent",
      "expected": 2.8529,
      "probabilities": {"low": 0.0059, "normal": 0.0059, "high": 0.1178, "urgent": 0.8705},
      "confidence": 0.8273,
      "action": "act",
      "because": [
        {"level": "high", "rule": 0, "weight": 2},
        {"level": "high", "rule": 1, "weight": 1},
        {"level": "urgent", "rule": 0, "weight": 2},
        {"level": "urgent", "rule": 1, "weight": 2},
        {"level": "urgent", "rule": 2, "weight": 1}
      ]
    }
  }
}

Who should take the job?

The cheapest layer first: rules, a small model, a large one, a person. Here the large model and the person tie, and the answer says so.

curl -s -X POST https://aisenseapi.com/services/v1/decide \
  -H "Content-Type: application/json" \
  -d '{
  "state": {"text_chars": 1800, "language": "no", "legal_terms": true, "tier": "gold"},
  "questions": {
    "handler": {
      "type": "choice",
      "options": {
        "rules_only": [{"if": {"text_chars": {"lte": 200}}, "weight": 2}],
        "small_model": [
          {"if": {"text_chars": {"lte": 2000}}, "weight": 1},
          {"if": {"language": {"in": ["en"]}}, "weight": 1}
        ],
        "large_model": [
          {"if": {"text_chars": {"gt": 2000}}, "weight": 2},
          {"if": {"language": {"in": ["no", "sv", "da"]}}, "weight": 1},
          {"if": {"legal_terms": {"eq": true}}, "weight": 1}
        ],
        "human": [
          {"if": {"legal_terms": {"eq": true}}, "weight": 1},
          {"if": {"tier": {"eq": "gold"}}, "weight": 1}
        ]
      }
    }
  }
}'
{
  "answers": {
    "handler": {
      "type": "choice",
      "choice": "large_model",
      "tied": ["large_model", "human"],
      "probabilities": {"rules_only": 0.0541, "small_model": 0.147, "large_model": 0.3995, "human": 0.3995},
      "confidence": 0.1993,
      "action": "hold",
      "because": [
        {"option": "small_model", "rule": 0, "weight": 1},
        {"option": "large_model", "rule": 1, "weight": 1},
        {"option": "large_model", "rule": 2, "weight": 1},
        {"option": "human", "rule": 0, "weight": 1},
        {"option": "human", "rule": 1, "weight": 1}
      ]
    }
  }
}

A confidence of 0.20 gives hold. The rules say plainly that they cannot tell, and the code can then send the job to the safer of the two.

Try it

Write the request yourself below, or build it in a form with examples and read the answer in plain words on Try Decide.

Optional decision models

Send "model" set to clef, nimble or tev1 with instructions and criteria instead of weighted rules. Clef is from Cloudflare, Nimble from Bespoke Labs and Tev1 from Together AI, and all three answer the same question types. Tev1 is the smallest and fastest. Together AI tested it in English only, and it suits short, direct questions. An unknown model returns 400, a request too long for the selected model 413, and a model that is temporarily unavailable 503. None of them falls back to rules.

{
  "model": "clef",
  "state": {
    "message": "The production API returns HTTP 500 and blocks checkout."
  },
  "questions": {
    "urgent": {
      "type": "noul",
      "instructions": "Does this need urgent attention?",
      "criteria": {
        "true": "An active production failure blocks business.",
        "false": "The issue can wait."
      }
    }
  }
}

This is a request example, not a measured production result. The response has model, model_version, answers and optional token usage.

Model typeCriteriaAnswer
noulAn object with descriptions named true and falseNumeric noul from 0 to 1, not a Boolean
choiceAn object of named descriptionschoice, probabilities and confidence
scoreAn ordered list of labelsscore, legend, probabilities and confidence

A score starts at zero for the first label and can be fractional. Model confidence comes from the model and does not use the rule formula above. Model answers have no action or because. Do not mix model criteria with rules, act_at or review_at. Model output can be wrong. Keep human approval for consequential actions.

Model limits

A model request may hold 8 KiB of JSON, 3 KiB for tev1, 4 questions, 8 choices or score levels, 1024 UTF-8 bytes per instruction and 512 per criterion. Each IP address may make 60 model requests per UTC minute and 1000 per UTC calendar day. An accepted request counts, also when it fails.

Respect Retry-After where a refusal carries it. Do not retry in a loop. Rules remain available. Callers cannot choose the model version or the limits.

Errors

Refusals include error and fix. No request or answer body is saved by the Decide API. Model mode records counters and timing aggregates.

StatusWhen
400A field is missing, of the wrong kind or out of range, or the body is not valid JSON. The error names the place, such as questions.team.options.returns[0].weight
405Any method but POST
413The body is over 64 KiB for rules or the configured model byte limit, or the rules hold more than 5000 tests
415The body is not sent as application/json
429The service-wide limit of 5000 requests per IP per day, or the model request limit
502The model request could not be completed
503The model is temporarily unavailable
504The model request timed out

When rules fit

Guardrails for agents

Decide whether an action may run on its own, needs a look, or has to wait for a person.

Routing and triage

Send tickets, alerts and jobs to the right place, and see why they went there.

Confidence-gated automation

Act only when the evidence is strong, with a threshold that fits what a mistake costs.

A cheap first layer

Answer the clear cases with rules, and hand only the uncertain ones to a model or a person.

Privacy and limits

Rules are computed by the API itself, without sending the state anywhere else or saving the request body. Rule limits are 64 KiB of JSON, 64 questions, 100 options, 20 levels, 50 rules per list, weights from −100 to 100, 5000 tests and 8 levels of any and not. The service-wide ceiling is 5000 requests per IP per Norwegian day.

Model mode sends the state and questions to the model for processing. The API keeps usage counters keyed by an IP HMAC and aggregate timings, not input or answer bodies. Keep credentials and sensitive personal data out. Ordinary access logs still record IP addresses and other request metadata. See Privacy.