Decisions
Ask Metis yes/no, multiple-choice and score questions about a case, and get a probability for every possible answer.
Metis is named after the Greek figure of wise counsel, whose name means "wise advice".
Give Metis a case, the state, and a set of named questions. It answers each question with a probability for every possible answer: yes or no, one of a set of named options, or a level on an ordered scale. It applies the rules and facts you supply, reads every answer of a request in a single forward pass, and generates no text.
The endpoint speaks the System One API from TypeSafe, so the official TypeSafe SDK works against GreenPT once it points at GreenPT's host and model (see Use the TypeSafe SDK).
API endpoint
POST /v1/systemoneTakes a state and up to 32 questions about it, and returns one answer per question.
Example request
A support ticket and three questions about it: is a live service down, which team should take it, and how urgent is it.
curl https://api.greenpt.ai/v1/systemone \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer sk-your_api_key' \
--data '{
"model": "metis",
"state": "Support ticket #T-4821 from an enterprise customer: \"Since 09:00 every checkout on our web shop fails with error 502, so we cannot take any orders. Our status page shows the payment service as down. Please help, this is costing us sales.\"",
"questions": {
"outage": {
"type": "noul",
"instructions": "Is a live service down?",
"criteria": { "true": "a production service is unavailable", "false": "every service is reachable" }
},
"team": {
"type": "choice",
"instructions": "Which team should take this ticket?",
"criteria": {
"billing": "invoices, payments and refunds",
"technical": "bugs, errors and outages",
"account": "logins, users and permissions",
"sales": "plans, pricing and upgrades"
}
},
"urgency": {
"type": "score",
"instructions": "How urgent is this ticket?",
"criteria": ["can wait", "this week", "today", "immediately"]
}
}
}'import { TypeSafeClient, choice, noul, score } from '@typesafe-ai/sdk';
const client = new TypeSafeClient({
apiKey: process.env.GREENPT_API_KEY,
baseURL: 'https://api.greenpt.ai',
defaultModel: 'metis',
timeout: 60_000,
});
const { answers, usage } = await client.systemOne({
state:
'Support ticket #T-4821 from an enterprise customer: "Since 09:00 every checkout on our ' +
'web shop fails with error 502, so we cannot take any orders. Our status page shows the ' +
'payment service as down. Please help, this is costing us sales."',
questions: {
outage: noul('Is a live service down?', {
true: 'a production service is unavailable',
false: 'every service is reachable',
}),
team: choice('Which team should take this ticket?', {
billing: 'invoices, payments and refunds',
technical: 'bugs, errors and outages',
account: 'logins, users and permissions',
sales: 'plans, pricing and upgrades',
}),
urgency: score('How urgent is this ticket?', ['can wait', 'this week', 'today', 'immediately']),
},
});
console.log(answers.outage.noul); // probability of yes
console.log(answers.team.choice); // 'technical'
console.log(answers.urgency.probabilities); // { '0': ..., '1': ..., '2': ..., '3': ... }
console.log(usage.input_tokens);Response format
Numbers shortened to four decimals:
{
"model": "metis",
"answers": {
"outage": { "type": "noul", "noul": 0.9533 },
"team": {
"type": "choice",
"choice": "technical",
"probabilities": {
"billing": 0.0552,
"technical": 0.9299,
"account": 0.0083,
"sales": 0.0066
},
"confidence": 0.9065
},
"urgency": {
"type": "score",
"score": 2.9415,
"legend": { "0": "can wait", "1": "this week", "2": "today", "3": "immediately" },
"probabilities": { "0": 0.0067, "1": 0.0064, "2": 0.0257, "3": 0.9613 },
"confidence": 0.9415
}
},
"usage": { "input_tokens": 409, "output_tokens": 0 },
"impact": {
"inferenceTime": { "total": 72, "unit": "ms" },
"energy": { "total": 16882, "unit": "Wms" },
"emissions": { "total": 80, "unit": "ugCO2e" },
"version": "20250922"
}
}Metis puts 0.95 on a live service being down, 0.93 on the technical team and
0.96 on the immediately level. answers has one entry per question, under
the id you gave it.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | "metis". |
state | string, object, array or null | Yes | The case to decide about: a document, a ticket, a record. Objects and arrays are read as JSON. |
questions | object | Yes | 1 to 32 questions, keyed by an id of 1 to 128 characters. Each answer comes back under its id. |
Every question has these fields:
| Field | Type | Required | Description |
|---|---|---|---|
type | "noul", "choice" or "score" | Yes | The kind of answer. See Answers. |
instructions | string, object, array or null | No | The question itself, or the rule to apply. Without it, the question's id is read in its place. |
criteria | depends on type | Yes, except for noul | The possible answers and what each one means. |
What criteria holds per type:
noul(yes or no): an optional object{ "true": ..., "false": ... }describing what yes and what no mean. Either key may be left out, and so may the whole object.choice(one of a set): an object from option key to description, with 2 to 255 options. The keys are what the answer returns.score(a level on an ordered scale): an array of 2 to 10 level descriptions, lowest first. Leveliis the entry at indexi.
A description can be text, a JSON object or array, or null. A null
description shows the model the option key (for choice) or the level number
(for score) in its place. The descriptions are what Metis reads, so a short
phrase stating what the option means works better than a bare label.
Numbers in the state, the instructions and the descriptions are read as double-precision floats, so an integer larger than 9,007,199,254,740,992 in magnitude, such as a 64-bit id or an account number, reaches Metis rounded. Send such integers as strings.
A question id, an option key, or a key anywhere inside the state, the
instructions or a description may not be __proto__; a request with one
answers 422.
Unknown fields are ignored.
Answers
Metis reads the state and every question of a request together, and answers them all in one pass with a probability for every option. The probabilities of one question add up to 1. The questions of a request are decided jointly, so one question can inform the answer to another. Ask the questions about one case in one request, and send a question in a request of its own when its answer must not depend on any other.
noul
{ "type": "noul", "noul": 0.2689 }noul is the probability of yes. The probability of no is 1 - noul. A
noul answer has no confidence field.
choice
{
"type": "choice",
"choice": "request_approval",
"probabilities": { "execute": 0.0561, "request_approval": 0.683, "reduce_size": 0.2083, "reject": 0.0527 },
"confidence": 0.5773
}choice is the most likely option; on an exact tie, the first one wins.
probabilities has every option, keyed as in criteria.
score
{
"type": "score",
"score": 1.9137,
"legend": { "0": "low", "1": "moderate", "2": "high", "3": "critical" },
"probabilities": { "0": 0.0527, "1": 0.1432, "2": 0.6418, "3": 0.1623 },
"confidence": 0.5891
}score is the expected level, the sum of each level times its probability, so
it can fall between levels. legend maps each level to the description you
sent, and probabilities holds the probability of each level. For the single
most likely level, read the largest entry in probabilities rather than
rounding score: the two can differ when the distribution is lopsided.
Confidence
choice and score answers carry a confidence between 0 and 1, computed
from the probabilities with a different formula per type, the same ones the
System One API uses.
For choice, with n options and p_max the probability of the chosen one:
confidence = (p_max - 1/n) / (1 - 1/n)It is 0 when every option is equally likely and 1 when one option holds all
the probability. In the example, (0.683 - 0.25) / 0.75 = 0.5773.
For score, with n levels numbered 0 to n - 1, p_i the probability
of level i, and mode the most likely level (the lowest one on a tie):
confidence = max(0, 1 - sum(p_i * |i - mode|) / MAD_uniform)
MAD_uniform = mean over all levels i of |i - (n - 1) / 2|It measures how concentrated the probability is around the most likely level:
1 when one level holds all of it, 0 when it is spread as widely as a uniform
distribution or wider. In the example, mode is 2, the weighted distance is
0.0527 * 2 + 0.1432 * 1 + 0.1623 * 1 = 0.4109, and MAD_uniform for four
levels is 1, so confidence is 0.5891. Probabilities of [0.5, 0, 0.5] give
0, where the choice formula would give 0.25.
Use confidence or the probabilities to decide when to act on an answer and
when to hand the case to a person.
Usage and impact
"usage": { "input_tokens": 606, "output_tokens": 3 }input_tokensis the length of the one prompt Metis reads: the state, every question's instructions and options, and a fixed instruction.output_tokensis 0: Metis generates no tokens.
Billing uses these counts. For the price, call GET /v1/pricing/metis. If any
question fails, the whole request fails and nothing is billed.
impact has the same shape as on chat completions
and covers the whole request: inferenceTime is the time of its one pass.
Limits
| What | Limit |
|---|---|
| Questions per request | 32 |
| Question id | 1 to 128 characters |
Options per choice question | 2 to 255 |
Levels per score question | 2 to 10 |
| Context per request | 16,384 tokens |
The context limit applies to the whole request: the state, every question's instructions and options, plus a fixed instruction and the model's prompt format.
Errors
| Status | When |
|---|---|
400 | model is not metis. An unknown id, such as the TypeSafe SDK's default jev-latest, answers Unsupported model; a model of another kind, such as glm-5.2, answers Unsupported model type. A body that is not valid JSON answers Malformed JSON in request body. |
401 | The API key is missing or invalid. The body is Unauthorized. |
402 | Your credit balance is spent. The body is {"error": "No remaining credits (EUR)"}. |
403 | The request went to the US endpoint. See Region. |
422 | The body is invalid (invalid_request), or the request does not fit the 16,384-token context (context_length_exceeded). |
429 | Your account's rate limit is spent. |
500 | An internal failure that is not caused by your request, such as an answer from the model that cannot be read. These answer {"error": "Internal server error", "code": "internal_error"}; other internal failures may answer without a code. Retrying meets the same failure. |
503 | The model is at capacity or temporarily degraded. The body is {"error": "Service temporarily degraded. Please retry.", "code": "service_degraded"}. |
504 | The request did not finish within the time limit. |
A 422 is JSON with a code to branch on and an error that says what
failed:
{ "error": "<what failed>", "code": "invalid_request" }A 503 comes with a Retry-After: 5 header. Retry after the delay.
Requests are limited per account (shared across all your API keys): 600
requests per 15-minute window, shared across every GreenPT API endpoint.
Exceeding it returns 429 Too many requests, please try again later. with
RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and Retry-After
response headers.
Use the TypeSafe SDK
The official TypeSafe JavaScript SDK, @typesafe-ai/sdk, sends
POST /v1/systemone with a bearer key, which GreenPT accepts. Out of the box
it calls TypeSafe's own host and model, so it needs two settings, not one: the
base URL and the model. Set them in the environment:
export TYPESAFE_API_KEY=sk-your_api_key # your GreenPT API key
export TYPESAFE_BASE_URL=https://api.greenpt.ai
export TYPESAFE_DEFAULT_MODEL=metisor in code, with baseURL and defaultModel as in the
example, or per call with model: 'metis' in the request.
The base URL has no /v1: the SDK appends /v1/systemone itself.
Set the model as well as the base URL
Changing only the base URL sends TypeSafe's default model, jev-latest,
which GreenPT answers with 400 Unsupported model.
- The SDK gives up on an attempt after 10 seconds by default, and then
retries it. A request with many questions over a long state, or one that
waits behind other requests, can take longer than that, so every attempt
times out. Pass
timeout: 60_000(in milliseconds) or more to the client or the call. - It also retries
429and5xxresponses on its own, and honoursRetry-After. client.models.list()expects TypeSafe's response shape and fails against GreenPT. List models withGET /v1/modelsinstead.
Scope
- Metis applies the rules, policies and facts you put in the state and the instructions. It does not look anything up, and it does not predict prices.
- It is not legal, tax or investment advice. Treat an answer as an input to your own process, and route uncertain cases to a person.
- It returns probabilities, not explanations. For the reasoning behind a decision, ask a chat model.
- It is a general-purpose decision model: routing and classification, policy and rule checks, and choosing a tool or an action.
- It does not calculate reliably. For a rule that compares amounts, put the computed figures in the state (an order of USD 61,800 against a limit of USD 24,000), not only the inputs they come from.
Region
Metis is served from the EU endpoint only: https://api.greenpt.ai, or
https://api.eu.greenpt.ai under its explicit name. The US endpoint answers
403 with {"code": "endpoint_not_available_in_location"}. See
Region Inference.
Model and attribution
Metis runs Clef-flash, a decision model by Cloudflare, released under the Apache License 2.0, in its full-precision release. It uses the model's own prompt format and decision head.
Clef-flash is a fine-tune of Qwen3.5-9B by the Qwen team at Alibaba Cloud, licensed under the Apache License 2.0. Its model card reports its evaluation.
metis is GreenPT's id for this endpoint's model: the model behind it can be
replaced without the id changing.