Skip to content
Docs navigation: Decision model / Request policy

Request policy

RightNow AIDecision modelRequest policy Updated

On this page

Add an optional policy object to a decide request for the RightNow decision model. Answers below your confidence threshold come back without a decision, and a request whose maximum possible cost exceeds your cap is refused before any work.

decide.ts
import { SystemZeroClient } from '@rightnowai/system-zero';

const client = new SystemZeroClient({
  apiKey: process.env.SYSTEM_ZERO_API_KEY,
  baseURL: "https://api.rightnowai.co/v1",
});
const reply = await client.decide({
  "model": "rightnow-decision",
  "state": "The blender I ordered arrived with a cracked jar. I need it working before Saturday.",
  "questions": {
    "damaged_in_transit": {
      "type": "boolean",
      "instructions": "Did the item arrive damaged?"
    },
    "team": {
      "type": "choice",
      "instructions": "Which team should handle this ticket?",
      "criteria": {
        "replacements": "Ship a new item or part",
        "refunds": "Return money to the customer",
        "support": null
      }
    },
    "urgency": {
      "type": "score",
      "instructions": "How urgent is this ticket?",
      "criteria": [
        "Routine",
        "Priority",
        "Immediate"
      ]
    }
  },
  "policy": {
    "min_confidence": 0.8,
    "on_low_confidence": "abstain",
    "max_cost_usd": 0.29
  }
});
console.log(JSON.stringify(reply, null, 2));
const models = await client.models.list();
console.log(models);

// Set SYSTEM_ZERO_API_KEY to your API key before running this example.
// Run this example on your server.
// SDK defaults: The default is 5 retries, also the maximum, with 150 seconds
// per attempt and a 60-second total retry budget. A single wait longer than 60
// seconds raises the last error.
// Use Retry-After-Ms, or Retry-After when milliseconds are absent. The wait is
// never shorter than a random backoff floor: a random fraction of 0.5 seconds
// doubled for each attempt, capped at 8 seconds.

Policy fields

min_confidence
Optional. A finite number greater than 0 and at most 1. An answer whose confidence is below it abstains; equal confidence passes.
on_low_confidence
Optional. Only abstain is accepted. An omitted value also abstains.
max_cost_usd
Optional. A cap in US dollars, greater than 0 and at most 1,000, with at most eight decimal places.

Every member is optional, and an empty object is valid. Send policy in a decide request or in a stream's start request. The gateway applies it; the model never receives it.

on_low_confidence: "error" and require_calibrated are not available. A request that sets either, including require_calibrated: false, returns 400 invalid_request.

Published SDK 0.4.7 accepts policy on decide requests and stream starts. TypeScript declares PolicyDecideRequest, exported as DecideRequest, with an optional policy member of type Policy. Read a reply with decisionsFor(reply, request) in TypeScript or decisions_for(reply, request) in Python. An abstained result has kind: "abstained", abstention_reason and no decision. Check kind before reading a decision. Playground runs send no policy.

Validation

An invalid policy returns 400 invalid_request with type invalid_request_error and error.retryable: false. A null policy, numeric strings, values that are not finite, wrong types and unknown members are invalid.

error.param is policy for a value that is not an object, policy.<member> for a known member and policy.* for an unknown one. A decision path that takes no policy refuses one with error.param policy.

The cap allows at most eight decimal places after exponent expansion, and trailing zeros do not count: 0.29, 0.07 and 3e-8 are valid, and 1e-9 is not. The gateway reads the original JSON number, so digits that a JSON parser would round away still count.

Abstained answers

After the model replies, the gateway compares each answer's confidence with min_confidence. An answer below the threshold keeps confidence and its other fields as evidence, loses decision and gains abstained: true and abstention_reason: "low_confidence".

Passing answers and question refusals keep their usual shape. A successful reply echoes the policy you sent, with no defaults added. In this illustrative reply, damaged_in_transit passes and the two other answers abstain.

answers and policy
JSON
{
  "answers": {
    "damaged_in_transit": {
      "type": "boolean",
      "probability": 0.93,
      "confidence": 0.93,
      "calibration_version": "cal-2026-11-a",
      "decision": true,
      "alternatives": [
        {
          "decision": false,
          "confidence": 0.07
        }
      ],
      "calibrated": false
    },
    "team": {
      "type": "choice",
      "choice": "replacements",
      "probabilities": {
        "replacements": 0.71,
        "refunds": 0.21,
        "support": 0.08
      },
      "confidence": 0.71,
      "calibration_version": "cal-2026-11-b",
      "alternatives": [
        {
          "decision": "refunds",
          "confidence": 0.21
        },
        {
          "decision": "support",
          "confidence": 0.08
        }
      ],
      "calibrated": false,
      "abstained": true,
      "abstention_reason": "low_confidence"
    },
    "urgency": {
      "type": "score",
      "score": 1.48,
      "legend": {
        "0": "Routine",
        "1": "Priority",
        "2": "Immediate"
      },
      "probabilities": {
        "0": 0.07,
        "1": 0.38,
        "2": 0.55
      },
      "confidence": 0.55,
      "calibration_version": "cal-2026-11-c",
      "alternatives": [
        {
          "decision": "1",
          "confidence": 0.38
        },
        {
          "decision": "0",
          "confidence": 0.07
        }
      ],
      "calibrated": false,
      "abstained": true,
      "abstention_reason": "low_confidence"
    }
  },
  "policy": {
    "min_confidence": 0.8,
    "on_low_confidence": "abstain",
    "max_cost_usd": 0.29
  }
}

Never act on an abstained answer. Its choice, score, probability, probabilities and alternatives are model evidence, not a decision. Check abstained before you read decision.

Cost cap

Before admission, the gateway compares the request's maximum possible cost with max_cost_usd. It converts the cap exactly, at 100,000,000 microcents per US dollar, and the bound includes media minimums. Equal cost passes.

A request over the cap returns 400 policy_max_cost_exceeded before any model work. The SDK raises PolicyMaxCostExceededError. It is not counted toward monthly usage and is not charged. The bound can exceed the final cost, so a cap near the expected cost can refuse a request that would have cost less.

error.json
JSON
{
  "error": {
    "message": "The request's maximum possible cost exceeds max_cost_usd. The request was not dispatched, counted toward monthly usage or charged.",
    "type": "invalid_request_error",
    "param": "policy.max_cost_usd",
    "code": "policy_max_cost_exceeded",
    "retryable": false,
    "details": {
      "policy": {
        "max_cost_usd": 0.0001
      },
      "counted": false,
      "estimated_cost_microcents": 120000,
      "max_cost_microcents": 10000
    }
  },
  "request_id": "request-id-from-response"
}
error.details.counted
false: nothing was counted or charged.
error.details.policy
The policy you sent.
error.details.estimated_cost_microcents
The maximum possible cost of the request, rounded up to whole microcents.
error.details.max_cost_microcents
Your cap in microcents.

This refusal has error.retryable: false. Raise max_cost_usd or send a smaller request. The amounts in this example are illustrative.

Usage and billing

An abstained answer is charged exactly like any answered question. It counts toward the monthly cap and pays the normal per-answer question fee, alongside the request's other charges. See Pricing.

Question refusals stay unanswered and are not charged. A request refused by the cost cap is neither counted nor charged.

Streams

In a stream, send policy inside start.request. It stays fixed across state changes, and a mutation cannot alter it. A policy error arrives as an error event with fatal: false and does not close the stream. See Streaming.

Replay and stored data

Policy is part of the Idempotency-Key fingerprint. Changing the policy under the same key returns 400 idempotency_key_reuse.

A keyed replay returns the original result, abstentions included, without new model work, counting or charges. See Idempotency.

Stored replies keep their policy and abstentions under the same 24-hour answer window and eviction limits as other replies. Usage records keep only a count of abstained questions, with no policy, threshold, question name, reason or answer.