Skip to content
Bayanii
Developers

The Bayanii API

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.

Base URL
https://bayanii.com/api/v1
Format
JSON in, JSON out
Authentication
Bearer token
Version
v1
Rows per request
up to 10,000

Quick start

Three steps from nothing to a prediction. You need a Bayanii account with at least one trained model.

  1. 1

    Create a token

    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:

    Terminal
    export BAYANII_TOKEN="bay_..."
  2. 2

    Find your model

    List your models. Note the id of the one you want and the input columns it expects.

    curl
    curl -H "Authorization: Bearer $BAYANII_TOKEN" \
      https://bayanii.com/api/v1/models
    Response
    {
      "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}
    }
  3. 3

    Get a prediction

    Send one object per row, with every input column as a key.

    curl
    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}]}'
    Response
    {
      "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
      }
    }

Authentication

Every request carries your token in the Authorization header:

HTTP
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.

Requests and responses

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.

Success
{
  "data": { ... },
  "meta": { ... }
}
Failure
{
  "error": {
    "code": "missing_columns",
    "message": "Row 3 is missing: plan.",
    "details": {"row": 3, "columns": ["plan"]}
  }
}
  • "meta" appears on lists and carries the paging facts.
  • Branch on "code" in your program — it never changes. "message" is a sentence for people and follows the Accept-Language header (en or ar; English by default).
  • Timestamps such as created_at and expires_at are ISO 8601 in UTC, ending in Z — for example 2026-11-01T09:30:00Z. A forecast's period is the date as your training data had it, with no time zone added.
  • Send request bodies as JSON with Content-Type: application/json.
  • Responses are never cached (Cache-Control: no-store): they can hold your data.

Endpoints

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.

GET /me

Who the token belongs to. The first call to make with a new token.

Response
{
  "data": {
    "user_id": "Xy12...",
    "token": {"id": 7, "name": "Sales dashboard", "expires_at": "2027-01-30T10:00:00Z"}
  }
}
GET /models

Your current, ready models, newest first.

Query parameters

Name Description
limitinteger How many to return, 1–100. Default 50.
offsetinteger 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.

GET /models/{id}

One model, plus exactly what it expects as input: each column's type, the range it saw in training, and the categories it knows.

Response
{
  "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.

POST /models/{id}/predictions

Predict. The body and the response are described in Making predictions below.

Body

Name Description
rowsarray 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 Only for a forecaster whose "input" is "steps": how many periods ahead, 1 to its horizon. Default: the horizon.
storeboolean 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.
GET /models/{id}/predictions

The model's prediction history — batches from the website and from the API — newest first.

Query parameters

Name Description
limitinteger How many to return, 1–100. Default 20.
beforeinteger Return batches older than this id. For the next page, pass the meta.next_before of the page you have.
Response
{
  "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}
}

Making predictions

What to send

  • Every input column must be present in every row. A value may be a number, text, true / false, or null for an empty cell — the model fills an empty cell the way it did in training.
  • Extra keys are ignored, and a warning names them, so a typo in a column name does not go unnoticed.
  • Use the model's input_schema to build rows: the exact column names, and for categories the values it knows.

What you get back

Name Description
predictionsarray 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 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 The future period each value is for, and a 90% prediction interval around it.
warningsarray Things worth knowing about your input — see below. The predictions still stand.
prediction_run_idinteger or null The history entry for this batch; null if it could not be recorded.
storedboolean Whether the results CSV was actually kept. Keep your own copy unless this is true.

Warnings

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.

A warning
{
  "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 Meaning
out_of_rangecaution A number is outside the range the model saw in training.
unseen_categoriescaution A category the model never saw in training.
rare_categoriescaution A category the model saw only a few times.
empty_cellscaution Empty cells were filled the way the model learned to.
non_numeric_valuescaution Text in a numeric column was treated as empty.
missing_columncaution A column the model uses was not usable.
extra_columnsnotice Keys the model does not use were ignored.
low_confidencecaution The model was unsure about some rows.
driftcaution This batch looks different from the training data overall.
sklearn_version_mismatchnotice The model was trained on an older library version.

Forecasting models

A forecaster with no input columns takes "steps" instead of rows, and each item comes back with its period and interval:

Request body
{"steps": 3}
One item
{
  "period": "2026-01-01T00:00:00",
  "prediction": 120.5,
  "lower": 98.1,
  "upper": 141.7
}

Errors

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 Meaning
400invalid_json The body is not valid JSON.
401unauthorized The token is missing, unknown, revoked or expired.
404not_found No such endpoint, or no model with that id in your account.
405method_not_allowed Wrong HTTP method — the Allow header lists the right ones.
409model_not_ready The model's files are incomplete (its training did not finish saving).
409model_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_too_large The body is larger than the upload limit.
415unsupported_media_type The body was not sent as application/json.
422invalid_body The body or a row is not the expected shape; details say where.
422unknown_field A field this endpoint does not take.
422field_not_allowed "rows" sent to a steps-only forecaster, or "steps" to any other model.
422missing_columns A row lacks some input columns; details name the row and the columns.
422invalid_value A value of the wrong type or out of range.
422too_many_rows More rows than allowed; details.max_rows says how many.
422invalid_parameter A query parameter is out of range.
422prediction_failed The model could not use the values sent; details.reason says why.
429rate_limited Too many requests — wait for the seconds in the Retry-After header.
500internal_error Something failed on our side.

Limits

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.

prediction requests per minute
30
other requests per minute
120
rows per prediction request
10,000
active tokens per account
10

Full examples

A prediction with error handling, reading the token from the environment.

Python · requests
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"])

Versioning

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.

Contact us