JavaScript
Install the mellix package from npm and call the Decision API, batches and images from Node.
The mellix package on npm is the JavaScript and TypeScript client for the Decision API. It is ESM, includes TypeScript types, has no runtime dependencies and runs on Node 18 or later (it uses the global fetch). It calls the Myles-G models over HTTP; it does not run the CPU models (see Model types).
npm install mellixThis page describes version 0.5.0, the first with batch support. There is also a Python SDK with the same calls.
To let an agent call Myles as a tool rather than from your own code, use the MCP server, which is built on this package.
Keep your API key on a server. The client refuses to start in a browser, because a key in a web page or app is public.
Authentication
The client reads your key from the MELLIX_API_TOKEN environment variable, or you can pass it as apiKey. Get a key in the console.
export MELLIX_API_TOKEN=mellix_...Ask a question
import { Mellix, noul } from "mellix";
const client = new Mellix(); // reads MELLIX_API_TOKEN
const resp = await client.decide(
"Customer bought the item 12 days ago and has no receipt. Policy: refunds within 30 days with proof of purchase.",
{ refund: noul("Is a refund permitted under the policy?") },
"myles-g-12b",
);
const answer = resp.answers.refund;
console.log(answer.probability); // probability of yes, e.g. 0.04
console.log(answer.decide({ thresholdYes: 0.9, thresholdNo: 0.1 })); // "yes", "no" or "person"
console.log(resp.usage.input_tokens, resp.model);decide(state, questions, model) returns { model, answers, usage, raw }. Always name the model, "myles-g-12b" or "myles-g-31b": every request should say which model answers it. A request without one is still answered by a default model for now, but do not rely on it. The state can be a string or an object. Answers are typed from the questions you ask, so resp.answers.refund above is a yes-or-no answer.
Question types
import { noul, choice, score } from "mellix";
const resp = await client.decide(state, {
refund: noul("Is a refund permitted?", { true: "Policy clearly allows it", false: "Policy clearly forbids it" }),
topic: choice("What is this about?", ["billing", "returns", "other"]), // or { label: description }
urgency: score("How urgent is it?", ["not urgent", "low", "medium", "high"]), // lowest level first, numbered from 0
}, "myles-g-12b");
resp.answers.topic.choice; // "returns"
resp.answers.topic.probabilities; // { billing: 0.06, returns: 0.89, ... }
resp.answers.urgency.level; // 2
resp.answers.urgency.score; // 1.92The helpers check your questions before anything is sent and throw ValidationError if a request would be refused: at most 256 questions a request, at most 256 options a choice and 4,096 over a request, and instructions that are not empty (from version 0.6.0; version 0.5.0 checked 16 questions and 26 options). See Question types for what each type returns.
Images
From version 0.4.0, the third argument of decide can be options, { model, assets, imageBudget }, as well as a model name. Pass images in assets under a reference and write the reference where the model should see the image (name the model: model: "myles-g-12b" or model: "myles-g-31b"):
import { Mellix, imageFromPath, noul } from "mellix";
const resp = await client.decide(
{ receipt: "receipt_photo" },
{ total_matches: noul("Does the total printed on receipt_photo equal 23.40?") },
{ model: "myles-g-12b", assets: { receipt_photo: await imageFromPath("receipt.jpg") }, imageBudget: 140 }, // budget: 70, 140 or 280
);imageFromPath and imageFromBytes detect PNG or JPEG from the bytes and throw ValidationError for anything else or anything over 16 MiB; decide throws it for an imageBudget other than 70, 140 or 280. The server's image errors arrive as InvalidRequestError with a type such as image_dimensions. See Images for references, limits and billing.
Batches
From version 0.5.0, the client sends work to the Batch API: many decisions in one submission, answered within 24 hours at half the live price per input token. Each line is a decide body with your own custom_id and the model to answer it, which every line must name. Field names are as they are sent: custom_id and image_budget.
import { Mellix, noul, choice } from "mellix";
const client = new Mellix();
const batch = await client.batch([
{ custom_id: "ticket-1001", model: "myles-g-12b", state: "Charged twice for order 4471", questions: { refund: noul("Is a refund due?") } },
{ custom_id: "ticket-1002", model: "myles-g-31b", state: { message: "The app crashes on login" },
questions: { team: choice("Which team?", ["billing", "returns", "technical", "other"]) } },
]); // { id: "batch_...", status: "queued", lines: 2, estimated_cost, expires_at, data_region }
await client.batches.waitFor(batch.id); // checks after 5 s, then 1.5 times longer each time, up to a minute apart
for await (const r of client.batches.streamResults(batch.id)) {
if (r.ok) console.log(r.custom_id, r.response.answers); // typed as decide() answers
else console.log(r.custom_id, r.error.type); // for example "expired" or "cancelled"
}| Call | What it does |
|---|---|
batch(lines, { webhookUrl }) | Checks every line, sends them as one file and returns the queued batch. The same as batches.create(). |
batches.list() | The key's batches, oldest first. |
batches.retrieve(id) | Status, counts and times. |
batches.waitFor(id, options) | Waits until the results can be collected. Options: pollInterval (5000 ms), maxInterval (60000 ms), both at least 1000 ms, timeout (25 hours, then BatchTimeoutError), signal to stop waiting and onPoll. It keeps waiting through a brief outage. Giving up never cancels the batch. |
batches.streamResults(id) | Each result as it arrives, in input order, so a large file is never held in memory. |
batches.results(id) | Every result in one array. |
batches.cancel(id) | Stops the batch. Answered lines are kept and billed; the rest are not. |
A result is { custom_id, model, status, ok, response, error, usage, raw }, where response is set when the line was answered and error when it was not. The client checks the lines before sending and throws ValidationError naming the line. If the server finds an invalid line it refuses the whole batch with an InvalidRequestError whose type is "invalid_batch", with each line at fault in body.errors. A submission is retried only after a refusal that stored nothing (429 rate_limited or 503 busy), never after a lost connection or a 502, so a retry cannot submit the same batch twice. A batch belongs to the key that submitted it: collect its results with that key.
Other calls
await client.models(); // [{ id: "myles-g-12b", object: "model", owned_by: "mellix" }]
await client.health(); // { status: "ok" }models() and health() need no key.
Options
| Option | What it does |
|---|---|
apiKey | Your API key. Defaults to MELLIX_API_TOKEN. |
baseURL | Set it for an endpoint you host yourself. Defaults to the hosted API. |
timeout | Milliseconds allowed for each attempt. Default 30000. |
maxRetries | Retries after the first attempt, for 429, 502, 503 and connection errors. Default 3. A batch submission is the exception: it is retried only after 429 rate_limited or 503 busy, never after a 502 or a lost connection, so it is never sent twice. |
const client = new Mellix({ apiKey: "mellix_...", timeout: 10_000, maxRetries: 5 });Retries wait 0.5, 1 and 2 seconds, or as long as a Retry-After header asks, up to 60 seconds. Other errors are thrown straight away.
Errors
Every error extends MellixError and carries status, serverMessage, body and type, the server's error type such as "out_of_credit".
| Error | When |
|---|---|
AuthenticationError | 401, or no key given or found |
OutOfCreditError | 402: the organisation has run out of credit (type "out_of_credit"), or the key's workspace has reached its monthly budget ("budget_exceeded") |
PermissionDeniedError | 403: the key's workspace may not use this model ("model_not_allowed") |
RateLimitError | 429, with retryAfter in seconds when the server sends it |
InvalidRequestError | 400, 411, 413, 422 and other 4xx; ValidationError is the check made before sending |
NotFoundError, ConflictError | 404 and 409, both kinds of InvalidRequestError: for a batch, an id the key does not own ("batch_not_found"), results asked for before the batch has ended ("batch_not_finished") or cancelling a batch that has already ended ("batch_finished") |
BatchTimeoutError | batches.waitFor() ran out of time; batch is the last status read, and the batch keeps running |
APIError | 5xx |
APIConnectionError | The endpoint could not be reached, including timeouts |
import { OutOfCreditError, RateLimitError, InvalidRequestError, MellixError } from "mellix";
try {
await client.decide(state, questions, "myles-g-12b");
} catch (e) {
if (e instanceof OutOfCreditError) console.log(e.type === "budget_exceeded" ? "workspace budget reached" : "top up credit");
else if (e instanceof RateLimitError) console.log("slow down", e.retryAfter);
else if (e instanceof InvalidRequestError) console.log(e.status, e.serverMessage);
else if (e instanceof MellixError) console.log("failed:", e.message);
else throw e;
}To update an existing install, run npm install mellix@latest. Batch support needs version 0.5.0 or later; images need 0.4.0 or later.
Python
The Python SDK (pip install mellix) has the same calls, with Python names: client.batch(lines), batches.wait_for(id), batches.stream_results(id) and so on.

