Melious

System One answers typed decision questions — choice, score, and noul — in a single JSON response. Decision models are discovered like any other model; see Models.

Endpoint:

POST /v1/systemone

Auth: Bearer token or x-api-key. Requires scope inference.decisions; listing models with Models needs inference.models as well (inference.* covers both).

Example

import httpx

response = httpx.post(
    "https://api.melious.ai/v1/systemone",
    headers={"Authorization": "Bearer sk-mel-<YOUR_API_KEY>"},
    json={
        "model": "<DECISION_MODEL_ID>",
        "state": "Ticket #4128: customer requests a refund 11 days after purchase; the order has shipped.",
        "questions": {
            "resolution": {
                "type": "choice",
                "instructions": "Pick the support resolution that matches policy.",
                "criteria": {
                    "refund": "Refund in full",
                    "credit": "Store credit",
                    "deny": "Deny politely",
                },
            }
        },
    },
    timeout=60.0,  # httpx defaults to 5 s; a decision can take longer
)
response.raise_for_status()

print(response.json()["answers"]["resolution"]["choice"])
curl https://api.melious.ai/v1/systemone \
  -H "Authorization: Bearer sk-mel-<YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<DECISION_MODEL_ID>",
    "state": "Ticket #4128: customer requests a refund 11 days after purchase; the order has shipped.",
    "questions": {
      "resolution": {
        "type": "choice",
        "instructions": "Pick the support resolution that matches policy.",
        "criteria": {
          "refund": "Refund in full",
          "credit": "Store credit",
          "deny": "Deny politely"
        }
      }
    }
  }'

Set a client timeout you can live with — the server allows a decision up to 20 minutes, and httpx gives up after 5 seconds by default.

The response:

{
  "model": "<DECISION_MODEL_ID>",
  "answers": {
    "resolution": {
      "type": "choice",
      "choice": "credit",
      "probabilities": { "refund": 0.25, "credit": 0.7, "deny": 0.05 },
      "confidence": 0.7
    }
  },
  "usage": { "input_tokens": 214 }
}

Availability

Contact support@melious.ai to confirm System One availability. When the endpoint is unavailable, requests with a valid key that has inference.decisions return 404 not_found. A key without that scope gets 403 auth_insufficient_scope first, whether or not the endpoint is on.

Request

ParameterTypeRequiredDescription
modelstringyesDecision model id or alias. The response's model is the canonical catalog id.
statestring, object, or arrayyesThe context the questions are decided against. May be empty.
questionsobjectyes1–32 named questions, each with a type and instructions. choice and score require criteria; noul doesn't.
imagesarraynoA non-empty array returns 422 invalid_request unless the model's _meta.capabilities.decision.images is true.

state and each question's instructions accept strings (including empty), objects, or arrays.

Choice questions

criteria is an object of 2–255 options. Keys are labels of at most 128 characters; values can be any JSON, including null. The answer picks one label and reports probabilities keyed by option labels, together summing to 1; not every option necessarily appears.

Score questions

criteria is an ordered array of 2–10 levels; values can be any JSON. The answer's score is the probability-weighted mean of the level positions — 0.1 × 0 + 0.8 × 1 + 0.1 × 2 = 1.0 in the example below — so it's a decimal between 0 and the last position, not always a whole level. legend maps each position back to the level you supplied.

Noul questions

A noul question has no options. The answer is one number in [0, 1]. criteria is optional. When you send it, it's an object with true and/or false — each can be any JSON, and either one can be left out.

Limits

These are the service-wide maxima; a model can set lower limits.

  • 1–32 questions per request. Question names are 1–100 characters: ASCII letters, digits, dots, underscores, hyphens.
  • Choice questions: 2–255 options, option names at most 128 characters.
  • Score questions: 2–10 levels.
  • Model names: at most 128 characters.
  • Request bodies: at most 262,144 bytes (256 KiB) as sent, whitespace included. Larger bodies return 413 request_too_large.

Per-model limits are exposed as an object under _meta.capabilities.decision: max_questions, max_choice_options, max_score_levels, max_request_bytes, and images (a boolean — false means a non-empty images array returns 422 invalid_request). _meta.context_length is set for decision models too. Exceeding a model's limit returns 422 invalid_request — a lower model byte limit returns 413 request_too_large.

Find decision models with Models: request ?include_meta=true and filter _meta.type to decision.

Response

Success is HTTP 200 with one JSON body containing exactly model, answers, and usage. There is no streaming mode and no stream parameter.

A request using all three question types:

{
  "model": "<DECISION_MODEL_ID>",
  "state": "Ticket #4128: customer requests a refund 11 days after purchase; the order has shipped.",
  "questions": {
    "resolution": {
      "type": "choice",
      "instructions": "Pick the support resolution that matches policy.",
      "criteria": {
        "refund": "Refund in full",
        "credit": "Store credit",
        "deny": "Deny politely"
      }
    },
    "urgency": {
      "type": "score",
      "instructions": "How urgent is this on the ordered scale?",
      "criteria": ["routine", "same-day", "immediate"]
    },
    "escalate": {
      "type": "noul",
      "instructions": "Should a human review this before closing?"
    }
  }
}

Its response:

{
  "model": "<DECISION_MODEL_ID>",
  "answers": {
    "resolution": {
      "type": "choice",
      "choice": "credit",
      "probabilities": { "refund": 0.25, "credit": 0.7, "deny": 0.05 },
      "confidence": 0.7
    },
    "urgency": {
      "type": "score",
      "score": 1.0,
      "probabilities": { "0": 0.1, "1": 0.8, "2": 0.1 },
      "confidence": 0.8,
      "legend": { "0": "routine", "1": "same-day", "2": "immediate" }
    },
    "escalate": {
      "type": "noul",
      "noul": 0.83
    }
  },
  "usage": { "input_tokens": 214 }
}

Answers are keyed by your question names:

  • choice — choice is the selected label from your criteria; probabilities gives each option it reports a share in [0, 1], together summing to 1; confidence accompanies them.
  • score — score is the probability-weighted mean position on your ordered scale (a decimal); probabilities is keyed by level position ("0", "1", …) with the same constraints; legend maps positions back to your levels.
  • noul — noul is one number in [0, 1].

usage carries input_tokens and output_tokens when measured. 0 is a measured zero; an omitted field means the count couldn't be measured.

Errors

This endpoint has its own error envelope. Every error body contains a lowercase code, a message, request_id, type, and param:

{
  "error": {
    "code": "unsupported_model",
    "message": "The model does not support decisions.",
    "request_id": "9f8c2a47-3d1b-4e6a-8b2f-1c9d5e7a0b3d",
    "type": "invalid_request_error",
    "param": null
  },
  "request_id": "9f8c2a47-3d1b-4e6a-8b2f-1c9d5e7a0b3d"
}

Branch on code — it's fixed per cause and repeated in the X-System-One-Error-Code header; one X-Request-ID matches both request_id locations. type is a coarse OpenAI-style category and the same code can arrive with different type values, so don't classify on it. param is null or names the offending part: json, body, model, questions, questions.criteria, images, or Idempotency-Key.

StatusCodeWhen
400invalid_jsonBody isn't valid JSON
401invalid_api_keyMissing or invalid API key
402insufficient_creditsBalance can't cover the reservation (see Billing), or cycle limits
403auth_insufficient_scopeKey lacks inference.decisions
403model_not_allowedKey can't use the model
403inference_model_restrictedModel restricted by your organization
404not_foundEndpoint not found
409request_in_progressSame idempotency key still running (Retry-After: 2)
409idempotency_conflictSame key, different request
409idempotency_failedOriginal request failed
410idempotency_expiredReplay window expired
413request_too_largeTransport or model byte limit exceeded
422invalid_requestSchema or limit violation, including images
422invalid_idempotency_keyMalformed Idempotency-Key
422unsupported_modelUnknown model id, not a decision model, or :free
429rate_limit_exceededRate limit hit
500internal_errorInternal failure
502invalid_provider_responseProvider returned an invalid answer
503service_unavailableProvider unavailable or overloaded
503dedicated_capacity_unavailableYour dedicated capacity is down or full
503entitlement_lookup_failedCapacity entitlements couldn't be read; retry
503model_allowlist_lookup_failedKey model permissions couldn't be read; retry
504provider_timeoutProvider timed out

429 and 503 responses carry Retry-After — follow the delay it names. 409 request_in_progress carries Retry-After: 2. General retry guidance: When to retry.

If you sent an Idempotency-Key and got a 502, 503, or 504, retry with a new key: the failed attempt marks the old key as failed, and reusing it returns 409 idempotency_failed. Nothing was charged for the failed attempt. After a 500, retry with the same key — if the answer was already produced, you get it back without a second charge.

Idempotency

Idempotency-Key is optional — an ASCII string of 1–128 characters that starts with a letter or digit, followed by letters, digits, dots, underscores, colons, or hyphens. With a key, a retry with the same key and the same request returns the stored response, with X-Idempotency-Replayed: true, instead of running — and billing — the request again. The key is scoped to your user account.

"Same request" means the same JSON body: key order, whitespace, and 1 versus 1.0 don't matter, but the model string does — an alias and its canonical id count as different requests and return 409 idempotency_conflict.

Replay is available for up to 24 hours from when the original request started. After that window, reusing a key can start a new request that may be billed. If you receive 410 idempotency_expired, use a new key for a new request.

Requests without a key are treated as new requests.

Conflicts:

  • 409 request_in_progress — the same key is still running.
  • 409 idempotency_conflict — the same key with a different request.
  • 409 idempotency_failed — the original request failed with a 502, 503, or 504; use a new key.

Billing

Decision models bill input tokens only — there is no output price. The charge is usage.input_tokens times the input rate of the provider that served the request. _meta.pricing.input_cost_per_million_eur on Models is the cheapest active provider's rate; routing prefers it, but a failover to another provider is billed at that provider's rate.

Concurrency plans do not cover the per-token cost. When the input-token count can't be measured, usage.input_tokens is omitted and the request is served without an input-token charge.

Before the request runs, Melious reserves the model's worst case — max_request_bytes tokens at the highest provider rate — and settles to the real charge afterwards. A 402 insufficient_credits means your available balance (balance minus other open reservations) is below that worst case, even for a small request. Top up, or pick a decision model with a lower max_request_bytes.

Decision responses carry no billing_cost or environment_impact — the body is model, answers, and usage. For how your plan pays for API requests, see Pricing.

Decision model discovery: Models • Scopes: Authentication • Error handling: Errors

On this page