To run this example you need an API key from the dashboard. Put the key in CAUSILO_KEY and run the commands below. The script calls https://api.nums.world unless CAUSILO_ENDPOINT names another address.
pip install -U causilo-client scikit-learn
python quickstart.py
import os
import numpy as np
from sklearn.datasets import fetch_openml, load_diabetes
from sklearn.metrics import accuracy_score, mean_absolute_error, r2_score, roc_auc_score
from sklearn.model_selection import train_test_split
from causilo_client import Causilo
cx = Causilo(os.environ.get("CAUSILO_ENDPOINT", "https://api.nums.world"), token=os.environ["CAUSILO_KEY"])
print("ready:", cx.health())
Classification
The German credit dataset from OpenML has 1,000 applicants, 20 columns of mixed numeric and categorical features and a good/bad label. You can pass the DataFrame as it comes, because the server handles categorical columns and missing values itself and needs no encoding or scaling on your side.
X, y = fetch_openml(data_id=31, as_frame=True, return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
context = X_train.assign(credit=y_train.values) # answers filled in
query = X_test # answers blank
print("context", context.shape, "query", query.shape)
proba, meta = cx.predict_with_metadata(context, query, target="credit", model="causilo-clf", output_type="probas")
proba = np.array(proba)
good = proba[:, meta["classes"].index("good")]
print("ROC AUC:", round(roc_auc_score(y_test == "good", good), 4))
print("accuracy:", round(accuracy_score(y_test, np.where(good >= 0.5, "good", "bad")), 4))
predict_with_metadata also returns what the server did with the call: classes gives the column order of the probability matrix, and quota shows how much of your allowance has been used. The limits themselves are on the dashboard.
print("classes:", meta["classes"])
print("quota:", {k: meta["quota"][k] for k in ("scope", "used", "resets_at")})
Regression
The diabetes dataset from scikit-learn has 442 patients, 10 features and a disease-progression score. You can ask for the mean, or for quantiles when you want an interval around each prediction. Each is a call of its own, so this section makes two calls and is counted twice.
d = load_diabetes(as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(d.data, d.target, test_size=0.2, random_state=42)
pred = cx.predict(X_train.assign(progression=y_train.values), X_test, target="progression", model="causilo-reg")
print("R2:", round(r2_score(y_test, pred), 4))
print("MAE:", round(mean_absolute_error(y_test, pred), 2))
q = np.array(cx.predict(X_train.assign(progression=y_train.values), X_test,
target="progression", model="causilo-reg",
output_type="quantiles", quantiles=[0.1, 0.5, 0.9]))
inside = np.mean((y_test.values >= q[:, 0]) & (y_test.values <= q[:, 2]))
print("quantiles array shape (rows, quantiles):", q.shape)
print("share of held-out values inside the 10-90 band:", round(inside, 3))
What the calls used
Usage is counted in cells of the tables you sent, with the context and query rows counted together. The output type and the time a call took do not affect the count, and usage() reads the figures without using any.
u = cx.usage()
print("usage:", {k: u[k] for k in ("scope", "used", "resets_at")})
Output
stdout
ready: True
context (800, 21) query (200, 20)
ROC AUC: 0.7771
accuracy: 0.735
classes: ['bad', 'good']
quota: {'scope': 'daily', 'used': 20000, 'resets_at': '2026-09-30T00:00:00+00:00'}
R2: 0.5055
MAE: 40.49
quantiles array shape (rows, quantiles): (89, 3)
share of held-out values inside the 10-90 band: 0.831
usage: {'scope': 'daily', 'used': 28840, 'resets_at': '2026-09-30T00:00:00+00:00'}