Docs · Trio-Spark API

One decision per request.

Send the current state, as text, JSON, an image or a short sampled video, plus the two to eight moves your application allows. Trio-Spark returns one choice and a probability for every choice from a single forward pass. Nothing is generated, so there is nothing to parse.

Quickstart

1. Get a key. Sign in and create an API key under API Keys. Every new account includes 100 free decisions. Keep the key on your server: the API is meant to be called from your backend, not from a browser.

2. Make a decision.

curl https://platform.machinefi.com/api/spark/v1/decisions \
  -H "Authorization: Bearer $TRIO_SPARK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "model": "trio-spark",
  "task": "The user asked to export the current report as CSV. Choose the next safe UI action from the available controls.",
  "state": {
    "app": "Reports",
    "screen": "Report details",
    "visible_controls": [
      "Export menu",
      "Filters",
      "Help"
    ]
  },
  "choices": [
    {
      "id": "open_export_menu",
      "description": "Open the Export menu"
    },
    {
      "id": "open_filters",
      "description": "Open report filters"
    },
    {
      "id": "wait",
      "description": "Wait for more information"
    }
  ]
}'

3. Read the answer.

{
  "request_id": "6f1c2a9e-3b7d-4f0a-9c55-2d8e41b7a013",
  "model": "trio-spark",
  "model_version": "trio-spark-2026-10-28",
  "choice_id": "open_export_menu",
  "probabilities": [
    {
      "choice_id": "open_export_menu",
      "probability": 0.894
    },
    {
      "choice_id": "open_filters",
      "probability": 0.041
    },
    {
      "choice_id": "wait",
      "probability": 0.065
    }
  ],
  "confidence": 0.894,
  "disposition": "decide",
  "usage": {
    "billed_input_tokens": 57
  },
  "billing": {
    "amount_nano_usd": "2394",
    "currency": "USD",
    "mode": "paid"
  },
  "latency_ms": 142
}

The same request in Python and in JavaScript (Node 18+):

import os, uuid, requests

resp = requests.post(
    "https://platform.machinefi.com/api/spark/v1/decisions",
    headers={
        "Authorization": f"Bearer {os.environ['TRIO_SPARK_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "model": "trio-spark",
        "task": "Is this support ticket urgent?",
        "state": {"ticket": "Our checkout is down for every customer."},
        "choices": [
            {"id": "urgent", "description": "Needs a response within the hour"},
            {"id": "normal", "description": "Can wait for the normal queue"},
        ],
    },
    timeout=30,
)
resp.raise_for_status()
decision = resp.json()
print(decision["choice_id"], decision["confidence"])
const res = await fetch("https://platform.machinefi.com/api/spark/v1/decisions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TRIO_SPARK_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    model: "trio-spark",
    task: "Is this support ticket urgent?",
    state: { ticket: "Our checkout is down for every customer." },
    choices: [
      { id: "urgent", description: "Needs a response within the hour" },
      { id: "normal", description: "Can wait for the normal queue" },
    ],
  }),
});
const decision = await res.json();

Request

POST https://platform.machinefi.com/api/spark/v1/decisions

HeaderDescription
AuthorizationBearer followed by your API key. Required.
Idempotency-KeyRequired. 8–128 characters from A–Z a–z 0–9 . _ : -. Use a new value for every new decision; reuse it only to retry the same request.
Content-Typeapplication/json
FieldDescription
modelUse "trio-spark". It always runs the current Trio-Spark release.
taskRequired string. What to decide, in one or two sentences.
stateRequired. A string or a JSON object describing what is true right now.
choicesRequired. 2–8 objects of { id, description }. Each id is 1–64 characters from a–z A–Z 0–9 _ - and must be unique.
mediaOptional. One image or a 2–4-frame sampled video. See Images and video.

The whole input, including task and every choice description, must fit in 1,024 tokens. Include a choice such as wait or ask for review whenever your application needs a safe default.

Response

FieldDescription
choice_idThe selected choice.
probabilitiesA probability for every choice you sent. They sum to 1.
confidenceThe probability of the selected choice.
dispositiondecide, review or abstain: a suggested handling. Route review and abstain to a person or to your fallback.
usage.billed_input_tokensInput tokens billed for this decision. Output is never billed.
billingamount_nano_usd (string; 1,000,000,000 nano-USD = $1), currency, and mode: trial or paid.
model · model_versionThe model name you sent, and the exact release that answered.
request_id · latency_msRequest identifier for support, and model time in milliseconds.

Use the probabilities to rank choices and to decide when a person should look. Set any automation threshold on a sample of your own labelled cases.

Images and video

Add a media object to the same request. Frames are sent inline as base64 (no data: prefix); URLs are not fetched. A video answer describes only the frames you send.

Single image or camera frame

{
  "model": "trio-spark",
  "task": "Which hand gesture is clearly visible?",
  "state": "Inspect the supplied image only.",
  "choices": [
    {
      "id": "thumbs_up",
      "description": "The thumb points upward"
    },
    {
      "id": "other",
      "description": "A different gesture is visible"
    },
    {
      "id": "unclear",
      "description": "The hand is absent or unclear"
    }
  ],
  "media": {
    "type": "image",
    "frames": [
      {
        "mime_type": "image/jpeg",
        "data_base64": "<BASE64_JPEG>"
      }
    ]
  }
}

Sampled video window

{
  "model": "trio-spark",
  "task": "What happened in this sampled window?",
  "state": "Judge only the supplied frames and timestamps.",
  "choices": [
    {
      "id": "picked_up",
      "description": "The person picked up the cup"
    },
    {
      "id": "not_picked_up",
      "description": "The person did not pick up the cup"
    },
    {
      "id": "unclear",
      "description": "The frames are not enough to tell"
    }
  ],
  "media": {
    "type": "video",
    "frames": [
      {
        "mime_type": "image/jpeg",
        "data_base64": "<BASE64_JPEG_1>",
        "timestamp_ms": 0
      },
      {
        "mime_type": "image/jpeg",
        "data_base64": "<BASE64_JPEG_2>",
        "timestamp_ms": 500
      }
    ]
  }
}
RuleLimit
type: imageExactly one frame, no timestamp_ms.
type: video2–4 frames with strictly increasing integer timestamp_ms, spanning at most 30 seconds.
mime_typeimage/jpeg or image/png; must match the actual bytes. Animated images are rejected.
sizeUp to 2 MB per frame and 8 MB of media per request; request body up to 12 MB.
dimensionsEach side 64–4,096 px; at most 16 megapixels in total.

Idempotency and retries

Every decision needs an Idempotency-Key. If a request times out or the connection drops, send the identical request with the same key: a completed decision is returned again, for 24 hours, without a second charge. Sending a different body with a used key returns 409 idempotency_conflict. When an error has retryable: true, retry with exponential backoff; when it also has new_request_required: true, retry with a new key.

Errors

Errors return a JSON body with a stable code, a human-readable message and a retryable flag.

{
  "error": {
    "code": "idempotency_conflict",
    "message": "This Idempotency-Key belongs to another input.",
    "retryable": false
  }
}
CodeStatusMeaning
invalid_input400Malformed JSON, or the task, state or choices fail validation.
invalid_idempotency_key400Missing or malformed Idempotency-Key.
invalid_media400The media object or a frame is malformed (type, MIME type, base64, timestamps).
unauthenticated401Missing, malformed or revoked API key.
insufficient_balance402Free decisions are used up and the balance is too low. Add credit.
idempotency_conflict409The Idempotency-Key was already used with a different request body.
in_progress409A request with this Idempotency-Key is still running. Retry shortly.
result_expired409The key succeeded more than 24 hours ago; its result is no longer stored. You were not charged again.
too_many_tokens413The full input, including task and choices, exceeds 1,024 tokens.
too_large · media_too_large · media_dimensions413Body, media size or image dimensions exceed the limits below.
rate_limited429Too many requests for your account. Honor Retry-After.
overloaded429The model is briefly at capacity. Retry with backoff.
unavailable503Temporary service problem. Retry with backoff.
timeout504The decision did not finish in time. You were not charged.

Failed and timed-out requests are never charged.

Limits

LimitValue
Input1,024 tokens, including task and choices
Choices2–8 per request
Requests240 per minute per account; up to 4 requests in flight per account
API keysUp to 20 active keys per account

Need higher throughput? Talk to us.

Billing

$0.042 per million billed input tokens for text and visual input. Output is free. Your first 100 decisions are free and shared between the Playground and your API keys. After that, decisions draw on prepaid credit; top-ups start at $5 and there is no subscription. Replays and failed requests are not charged. See pricing.

Model names

Use trio-spark. It always points to the current release, so your integration improves without code changes; the exact release is reported in model_version. Earlier names (trio-spark-v1.2, trio-spark-v1.3) are still accepted and are served by the same current model. GET https://platform.machinefi.com/v1/models lists every accepted name.

Compatibility endpoints

If you already use a System One-style client, POST https://platform.machinefi.com/v1/systemone accepts the same API key and up to 16 named questions per request (choice, score and noul), including one image. POST https://platform.machinefi.com/v1/evaluate accepts boolean, choice and score questions for evaluation tooling. Each question is billed as one decision. On these endpoints the Idempotency-Key is optional, and errors use the { error: { type, message } } shape. New integrations should use the decisions endpoint above.

Try it before you write code.

The Playground runs the same API with your own states and choices.

Open the Playground