---
title: "Request a decision with inline questions"
method: POST
path: "/v1/projects/{project_id}/decisions"
tags: ["Deciders"]
---

# Request a decision with inline questions

`POST /v1/projects/{project_id}/decisions`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/decisions`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Evaluates `questions` sent with the request against `input`, answered by the agent or tool the request names (exactly one of `agent_id` and `tool_id`, held to the same rules as a decider's backend), and records the decision with its questions. The request and answers are those of OpenAI's Decisions API, with the backend in place of `model`.

No decider names the questions, so the decision carries them in `questions` and has no `decider_id` or `decider_version`. Admission, `wait` and the tool contract are those of `POST /v1/projects/{project_id}/deciders/{decider_id}/decisions`.

## Request body

- CreateInlineDecisionRequest: Names exactly one of `agent_id` and `tool_id`.
  - `agent_id` string: A tool-less agent that answers
  - `tool_id` string: An http or pipeline tool that answers
  - `questions` DeciderQuestion[], required: The questions, in the order the model is shown them.
    - `type` 'predicate' | 'choice' | 'score', required
    - `name` string, required: Names the question's answer. Unique within the set; starts with a letter or underscore and holds only letters, digits and underscores (at most 64).
    - `instructions` string, required: What to judge
    - `choices` DeciderChoice[]: A `choice` question's options; required for `choice` only
      - `value` string, required: What a `choice` answer carries; unique within the question
      - `description` string, required
    - `levels` DeciderLevel[]: A `score` question's ordered levels; required for `score` only
      - `label` string, required
      - `description` string, required
  - `input` string, required: What is judged, not stored: a string; a list of `user` messages whose `content` is a string or `input_text` and `input_image` parts (an image as a base64 data URL); or any other JSON value, which an agent is shown as indented JSON. An array of objects carrying `role` is read as messages.
  - `metadata` MetadataBag: Caller-owned annotations on a resource, stored as the object they were written as: the types a value was written with are the types a read returns, so a filter can ask an ordering question about a number. Unlike other body fields, keys are stored and returned verbatim in the casing supplied — they are not converted between snake_case and camelCase. No key is reserved, and that is the point: every piece of state the platform owns lives in its own typed column, so nothing written here reaches platform state. The platform never reads the bag — it is not an IAM context, not a policy input and not part of a prompt — which is what separates it from a tag bag.
  - `wait` boolean: True evaluates before answering and returns the settled decision. False — the default — returns the `queued` decision at once. An MCP tool call always waits.

## Response `201`

The decision — `queued`, or settled when `wait` is true

- Decision
  - `id` string
  - `project_id` string
  - `decider_id` string, nullable: The decider asked; kept after the decider is deleted. Null for a decision sent with its own questions.
  - `decider_version` integer, nullable: The question-set version the decision was answered under
  - `questions` DeciderQuestion[], nullable: The questions a decision sent with its own was answered under. Null for a decider's decision, whose questions are its `decider_version`.
    - `type` 'predicate' | 'choice' | 'score', required
    - `name` string, required: Names the question's answer. Unique within the set; starts with a letter or underscore and holds only letters, digits and underscores (at most 64).
    - `instructions` string, required: What to judge
    - `choices` DeciderChoice[]: A `choice` question's options; required for `choice` only
      - `value` string, required: What a `choice` answer carries; unique within the question
      - `description` string, required
    - `levels` DeciderLevel[]: A `score` question's ordered levels; required for `score` only
      - `label` string, required
      - `description` string, required
  - `status` 'queued' | 'running' | 'completed' | 'failed'
  - `answers` DecisionAnswer[], nullable: One answer per question, in question order. Null until the decision completes, then never rewritten.
    - `type` 'predicate' | 'choice' | 'score'
    - `name` string
    - `probability` number
    - `choice` string
    - `score` number
    - `probabilities` AnswerProbability[]
      - `value` union: The choice's `value`, or the level's index
        - string
        - integer
      - `label` string: The level's `label`; on a `score` answer only
      - `probability` number
    - `confidence` number
  - `answers_by_name` object, nullable: The same answers keyed by question name, so a JSON Logic path reads one by name (`answers_by_name.department.choice`) rather than by position.
  - `error` StoredError: Why a resource's work failed, in the shape of an error response's `error` without `hint` and `docs_url`. Every `error` field a resource records carries it, so `code` can be read without first checking the type of `error`.
    - `code` string, required: A key from the server's ERROR_CODES registry, documented on the Error Codes page. `INTERNAL_ERROR`, with the message `Internal Server Error`, records an unexpected failure whose detail reaches the server log only.
    - `message` string, required
    - `meta` object: The failure's own structured context, when it names any.
  - `generation_id` string, nullable: The generation that answered, once one has; null for a tool backend. Its receipt carries what the decision cost.
  - `metadata` MetadataBag: Caller-owned annotations on a resource, stored as the object they were written as: the types a value was written with are the types a read returns, so a filter can ask an ordering question about a number. Unlike other body fields, keys are stored and returned verbatim in the casing supplied — they are not converted between snake_case and camelCase. No key is reserved, and that is the point: every piece of state the platform owns lives in its own typed column, so nothing written here reaches platform state. The platform never reads the bag — it is not an IAM context, not a policy input and not part of a prompt — which is what separates it from a tag bag.
  - `created_at` string, date-time
  - `updated_at` string, date-time

## Other responses

- `400`: Bad request — invalid questions or input, no backend or both, or a backend that cannot answer
- `401`: Unauthorized
- `403`: Forbidden
- `409`: The project is paused
- `429`: A generation quota is exhausted

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
