Who the token belongs to. The first call to make with a new token.
{
"data": {
"user_id": "Xy12...",
"token": {"id": 7, "name": "Sales dashboard", "expires_at": "2027-01-30T10:00:00Z"}
}
}
Train a model in Bayanii, then use it from your own code. Send rows as JSON and get predictions back — from a script, a spreadsheet job, a back-end service or a notebook.
https://bayanii.com/api/v1
Three steps from nothing to a prediction. You need a Bayanii account with at least one trained model.
Sign in and open Account → API. Give the token a name you will recognize later (for example, the program that will use it), choose how long it lasts, and copy it. It starts with bay_ and is shown only once.
Keep it in an environment variable, not in your code:
export BAYANII_TOKEN="bay_..."
List your models. Note the id of the one you want and the input columns it expects.
curl -H "Authorization: Bearer $BAYANII_TOKEN" \
https://bayanii.com/api/v1/models
{
"data": [
{
"id": 88,
"name": "Churn",
"task_type": "classification",
"targets": ["churned"],
"input_columns": ["tenure", "plan", "monthly_charges"],
"input": "rows",
...
}
],
"meta": {"total": 1, "limit": 50, "offset": 0, "has_more": false}
}
Send one object per row, with every input column as a key.
curl -X POST https://bayanii.com/api/v1/models/88/predictions \
-H "Authorization: Bearer $BAYANII_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rows": [{"tenure": 12, "plan": "monthly", "monthly_charges": 70.5}]}'
{
"data": {
"model_id": 88,
"task_type": "classification",
"row_count": 1,
"predictions": [
{"prediction": "yes", "confidence": 0.82, "runner_up": "no", "runner_up_confidence": 0.18}
],
"warnings": [],
"prediction_run_id": 1234,
"stored": false
}
}
Every request carries your token in the Authorization header:
Authorization: Bearer bay_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Lifetime
30, 90 or 365 days, or no expiry. Up to 10 active tokens per account.
Revoking
Revoke a token on Account → API. The next request with it is refused.
Account changes
Deleting your account deletes its tokens. A disabled account stops working within 15 minutes.
Token management
A token cannot create, list or revoke tokens — that needs you, signed in.
Treat a token like a password
Anyone who has it can use your models. Call the API from a server, a script or a notebook — never from code that runs in a visitor's browser, where the token would be visible to anyone. Browsers cannot call the API from another website anyway.
If a token leaks, revoke it and create a new one. Bayanii keeps only a fingerprint of each token, so a lost token cannot be shown again — create a new one instead.
Every response has the same shape: the result under "data", or a problem under "error" — never both. Check the HTTP status first, then read the matching key.
{
"data": { ... },
"meta": { ... }
}
{
"error": {
"code": "missing_columns",
"message": "Row 3 is missing: plan.",
"details": {"row": 3, "columns": ["plan"]}
}
}
Five endpoints, all under the base URL. A model that is not in your account answers 404, the same as one that does not exist.
Who the token belongs to. The first call to make with a new token.
{
"data": {
"user_id": "Xy12...",
"token": {"id": 7, "name": "Sales dashboard", "expires_at": "2027-01-30T10:00:00Z"}
}
}
Your current, ready models, newest first.
| Name | Type | Description |
|---|---|---|
limitinteger |
integer | How many to return, 1–100. Default 50. |
offsetinteger |
integer | How many to skip. Default 0. Use meta.has_more to know when to stop. |
Each model has an "input" field: "rows" means you send input rows; "steps" means it is a forecaster with no input columns and you send how many periods ahead you want.
One model, plus exactly what it expects as input: each column's type, the range it saw in training, and the categories it knows.
{
"data": {
"id": 88,
"name": "Churn",
"task_type": "classification",
"input_schema": {
"features": [
{"name": "tenure", "dtype": "numeric", "min": 0, "max": 72, "integer": true},
{"name": "plan", "dtype": "categorical", "options": ["monthly", "yearly"], "options_complete": true}
],
"order": ["tenure", "plan", "monthly_charges"]
}
}
}
A forecasting model also has "forecast": its horizon (how many periods it can predict) and the width of its prediction interval.
Predict. The body and the response are described in Making predictions below.
| Name | Type | Description |
|---|---|---|
rowsarray of objects |
array of objects | The input rows, 1 to 10,000 — one object per row, every input column as a key. For a forecaster with input columns, one row per future period, up to its horizon. |
stepsinteger |
integer | Only for a forecaster whose "input" is "steps": how many periods ahead, 1 to its horizon. Default: the horizon. |
storeboolean |
boolean | Also keep the results as a CSV you can download from the model's page. Default false. The batch appears in the model's history either way. |
The model's prediction history — batches from the website and from the API — newest first.
| Name | Type | Description |
|---|---|---|
limitinteger |
integer | How many to return, 1–100. Default 20. |
beforeinteger |
integer | Return batches older than this id. For the next page, pass the meta.next_before of the page you have. |
{
"data": [
{"id": 1234, "created_at": "2026-11-01T09:30:00Z", "row_count": 2,
"input_method": "api", "drift": false, "stored": false}
],
"meta": {"limit": 20, "has_more": true, "next_before": 1234}
}
| Name | Type | Description |
|---|---|---|
predictionsarray |
array | One item per input row, in the same order. "prediction" is the value — a class name for classification, or an object of target → value for a model with several targets. |
confidencenumber 0–1 |
number 0–1 | Classification only, when the algorithm gives probabilities: how sure the model is, with the runner_up class and its runner_up_confidence. |
period / lower / upperforecasts |
forecasts | The future period each value is for, and a 90% prediction interval around it. |
warningsarray |
array | Things worth knowing about your input — see below. The predictions still stand. |
prediction_run_idinteger or null |
integer or null | The history entry for this batch; null if it could not be recorded. |
storedboolean |
boolean | Whether the results CSV was actually kept. Keep your own copy unless this is true. |
Each warning has a code, a severity, a title and a message. "caution" means the prediction may be less reliable; "notice" is for your information.
{
"code": "out_of_range",
"severity": "caution",
"title": "Values outside the training range",
"message": "Values in \"tenure\" are outside the range seen during training (0–72; count: 1). They were not changed.",
"details": {"column": "tenure", "min": 0, "max": 72, "count": 1}
}
| Code | Severity | Meaning |
|---|---|---|
out_caution |
caution | A number is outside the range the model saw in training. |
unseen_caution |
caution | A category the model never saw in training. |
rare_caution |
caution | A category the model saw only a few times. |
empty_caution |
caution | Empty cells were filled the way the model learned to. |
non_caution |
caution | Text in a numeric column was treated as empty. |
missing_caution |
caution | A column the model uses was not usable. |
extra_notice |
notice | Keys the model does not use were ignored. |
low_caution |
caution | The model was unsure about some rows. |
driftdriftcaution |
caution | This batch looks different from the training data overall. |
sklearn_notice |
notice | The model was trained on an older library version. |
A forecaster with no input columns takes "steps" instead of rows, and each item comes back with its period and interval:
{"steps": 3}
{
"period": "2026-01-01T00:00:00",
"prediction": 120.5,
"lower": 98.1,
"upper": 141.7
}
A 4xx status means something in the request needs fixing — retrying the same request will fail the same way, except 429. A 5xx status is our side: retry later, and quote details.request_id if you contact us.
| Status | Code | Meaning |
|---|---|---|
400invalid_ |
invalid_json |
The body is not valid JSON. |
401unauthorized |
unauthorized |
The token is missing, unknown, revoked or expired. |
404not_ |
not_found |
No such endpoint, or no model with that id in your account. |
405method_ |
method_not_allowed |
Wrong HTTP method — the Allow header lists the right ones. |
409model_ |
model_not_ready |
The model's files are incomplete (its training did not finish saving). |
409model_ |
model_too_large |
The model is too large to load on the server (it was trained before every model was kept within the limit) — retrain it. details: footprint_mb, limit_mb, and footprint_is_lower_bound (true: the size was counted only up to the limit). |
413payload_ |
payload_too_large |
The body is larger than the upload limit. |
415unsupported_ |
unsupported_media_type |
The body was not sent as application/json. |
422invalid_ |
invalid_body |
The body or a row is not the expected shape; details say where. |
422unknown_ |
unknown_field |
A field this endpoint does not take. |
422field_ |
field_not_allowed |
"rows" sent to a steps-only forecaster, or "steps" to any other model. |
422missing_ |
missing_columns |
A row lacks some input columns; details name the row and the columns. |
422invalid_ |
invalid_value |
A value of the wrong type or out of range. |
422too_ |
too_many_rows |
More rows than allowed; details.max_rows says how many. |
422invalid_ |
invalid_parameter |
A query parameter is out of range. |
422prediction_ |
prediction_failed |
The model could not use the values sent; details.reason says why. |
429rate_ |
rate_limited |
Too many requests — wait for the seconds in the Retry-After header. |
500internal_ |
internal_error |
Something failed on our side. |
Limits count per account — all of your tokens together. Batch rows into one request instead of sending one request per row: it is faster for you and counts once.
A prediction with error handling, reading the token from the environment.
import os
import requests
API = "https://bayanii.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['BAYANII_TOKEN']}"
rows = [
{"tenure": 12, "plan": "monthly", "monthly_charges": 70.5},
{"tenure": 48, "plan": "yearly", "monthly_charges": 55.0},
]
resp = session.post(f"{API}/models/88/predictions", json={"rows": rows}, timeout=120)
body = resp.json()
if resp.ok:
for row, p in zip(rows, body["data"]["predictions"]):
print(row, "->", p["prediction"], p.get("confidence"))
for w in body["data"]["warnings"]:
if w["severity"] == "caution":
print("caution:", w["message"])
elif resp.status_code == 429:
print("Slow down; retry after", resp.headers.get("Retry-After"), "seconds")
else:
print(resp.status_code, body["error"]["code"], body["error"]["message"])
The version is in the URL. Within v1 we may add endpoints, response fields, warning codes and error codes, so ignore fields you do not know. We will not rename or remove a field or change what a code means — a change like that becomes /api/v2, and v1 keeps working alongside it.
Stuck, or missing something?
Tell us what you are building — the API grows from what people need.