Base URL and authentication
https://api.nums.worldSend your API key in either of these headers. Use an existing API key. See API access for availability and support.
Authorization: Bearer csl_...
X-Causilo-Key: csl_...Routes
POST /v1/predict
Predict. Multipart with context, query and params, or one Parquet file.
| Field | Location | Type | Required | Default | Meaning and constraints |
|---|---|---|---|---|---|
X-Causilo-Cancel-Key | header | string | no | — | Optional. A token you choose, letters and digits, 16 to 128 long.
Lets you call At least 16 characters. Pattern: ^[A-Za-z0-9]+$. |
Accept | header | string | no | "application/json" | Ask for Parquet when the prediction is large. Metadata is not lost:
it goes into the Parquet schema metadata under Supported values: application/json, application/vnd.apache.parquet. |
GET /v1/limits
What this deployment accepts. Numbers a client can check before sending.
GET /v1/estimate
Whether a table of a given shape will be accepted, and how long it will take.
| Field | Location | Type | Required | Default | Meaning and constraints |
|---|---|---|---|---|---|
n_context | query | integer | yes | — | Rows in the context table, the ones that already carry an answer. At least 0. |
n_features | query | integer | yes | — | Feature columns, not counting the target. Admission measures the
context table's own width with the target included, which is why
At least 0. |
n_query | query | integer | no | 1 | Rows to predict. They cost very little next to context rows. At least 0. |
n_classes | query | integer | no | 0 | Distinct values of a classification target. Only meaningful for classification, and worth sending: a target wider than the checkpoint's ten-way head runs the network several times, so an estimate that does not know the class count answers for a binary target and can report that a shape fits which the service refuses. At least 0. |
model | query | string | no | — | Defaults to the first model in |
GET /v1/usage
What this credential has used this month and today.
GET /v1/contexts
The contexts stored for this caller.
DELETE /v1/contexts/{context_id}
Release one stored context.
| Field | Location | Type | Required | Default | Meaning and constraints |
|---|---|---|---|---|---|
context_id | path | string | yes | — | The |
GET /v1/ping
Whether the service can take a request.
There is no route for cancelling a prediction. A request you abandon runs
to the end and is counted in full, as Limits explains.
POST /v1/cancel answers 501, which tells a client
that calls it that the feature does not exist.
Sending a prediction
The Python client builds the request for you, and the quickstart shows one classification call and one regression call made with it. Callers who do not use the client send the request below.
POST /v1/predict takes a multipart body with three parts:
context and query as Parquet
(application/vnd.apache.parquet), and params as JSON. A
single Parquet file is accepted as well, carrying the same JSON in its
causilo-params schema metadata, in which case the rows whose
target is missing are treated as the query.
Feature columns must appear in both tables with the same names and in the same order, because the model reads them by position.
What a column may hold
Each kind of column is handled as follows. Every refusal is a 422 response whose detail names the column.
| In your table | What happens |
|---|---|
| Missing values in a feature | Accepted as they are. The model handles missing values natively, so there is no need to impute them first. |
| Strings | Accepted and treated as categories, with no encoding needed on your side. |
| Columns on very different scales | Accepted. No scaling or normalisation is needed. |
| A query column that is empty on every row | Refused, because the prediction would otherwise ignore that column without saying so. |
| Dates, times, durations | Refused. Split them into numeric parts such as year, month and day of week, or send epoch seconds. Preprocessing would treat a date as a category, and the predictions would be worse without any warning. |
Decimal columns |
Refused. A Postgres or BigQuery NUMERIC column becomes
this type in pandas, so cast it to float first. |
| Lists, dicts, structs | Refused. |
| Infinities, or values at or above the documented ceiling | Refused. Rescale the column or drop those rows. |
float16 | Widened to float32 for you. |
| Duplicate column names | Refused. |
| Your DataFrame index | Dropped and never sent, so put anything you need to keep in a column. |
The same column must hold the same kind of value in both tables. A context in which every feature column holds a single value is refused, since there is nothing in it to predict from.
What the target column may hold
The target column is present in the context and absent from the query. In a single Parquet file, the rows with an empty target are the rows to predict.
| Rule | Applies to |
|---|---|
| No missing values in the context's target | both models |
| At most 64 distinct values, and at least two | causilo-clf |
| Whole numbers or strings, not fractions | causilo-clf |
| Numeric, finite, under 3.4e38 | causilo-reg |
| Not a datetime | causilo-reg, whose predictions
would otherwise come back as epoch integers with no unit.
causilo-clf treats each distinct date as a class and
returns it as a string. |
params
Storing a context with cache_context and naming it later with
context_id saves the upload but not the cells, as the
One table, many questions
example measures.
| Field | Type | Required | Default | Constraints |
|---|---|---|---|---|
cache_context | boolean | no | false | — |
context_id | string | no | — | — |
model | string | yes | — | Supported values: causilo-clf, causilo-reg. |
target | string | with a context table | — | — |
output_type | string | no | task-specific | Supported values: probas, preds, mean, median, quantiles. |
quantiles | array | with output_type=quantiles | — | At least 1 item. Each item: Greater than 0. Less than 1. |
cache_context
Keep this request's context after answering, and return a
context_id in metadata.cached_context. Later requests send that
id in place of the context part. What it saves is the upload, not
the bill and not the compute: the stored table is encoded again on
every call, and every request is charged for the context rows and
the query rows together, whether the context arrived in that
request or on an earlier one.
Worth it when you will query a large table again and uploading it is the slow part. Not worth it for a single question, which is why it is off by default.
Answers against a stored context are the same, bit for bit, as answers with the table sent inline: the service stores the table itself and runs the same computation on it.
Cannot be combined with context_id. A request against a cached
context has no context of its own to cache.
A deployment with the cache turned off answers normally and returns no id, rather than refusing the request.
context_id
Predict against a context cached by an earlier request, sending no context part at all. The query must carry the same feature columns, in the same order and of the same types, as the context did.
Valid for the account that created it, from any instance and across
restarts, until it expires. A 404 with code context_not_found
means it has expired, been released, been evicted when the account
stored more than it may hold, or was never yours -- send the context table again to start a new one. Clients
should handle that automatically rather than surfacing it.
Multipart only. The single-Parquet format decides which rows are context by looking for a missing target, so a body with no context rows in it is an empty request rather than a cached one.
model
-clf classifies, -reg regresses. Both are served from the
published causilo-v1.0 weights, the same ones the causilo package
downloads.
Earlier model keys have been retired, and none of them is an alias for these. Sending one fails validation with a 422 that lists the keys the server does serve.
Any measured figure quoted elsewhere in this document that does not name its model was taken against an earlier model this API no longer serves. Treat those as historical until they are re-measured.
This enum is the full list the image knows. A deployment may serve a
subset of it (SERVED_MODELS), and a key it does not serve is
refused as unknown, with a 422 naming only the served ones.
GET /v1/limits is authoritative for what a given deployment answers
for.
target
Name of the column to predict.
output_type
Classification takes probas (default) or preds.
Regression takes mean (default), median or quantiles.
quantiles
Required with output_type: quantiles, refused otherwise. Column
order in the response follows the order given here.
Fields that are not listed here are ignored, so a caller moving from another API does not have to remove its own options first.
output_type
| Task | Allowed |
|---|---|
classification | probas, preds (default probas) |
regression | mean, median, quantiles (default mean) |
What comes back
prediction holds one value per query row. metadata
carries the shape of the request, the elapsed time, quota and
served_by, which identifies the image, the checkpoint and the
ensemble width that produced the numbers. When you compare predictions across
runs, compare served_by first.
Response fields
| Field | Shape | Meaning and constraints |
|---|---|---|
prediction | one-dimensional or two-dimensional array | One entry per query row. 2-D for |
metadata | Metadata (object) | |
metadata.quota | object | What is left of the month, mirroring the |
metadata.served_by | object | What produced this prediction. Record it alongside any result you may have to defend later.
|
metadata.model | string | The key that was served. It names the lineage, not the checkpoint version behind it. |
metadata.task | string | Task selected by the model: classification or regression. Supported values: classification, regression. |
metadata.n_context | integer | Context rows behind the answer. With |
metadata.n_query | integer | Number of query rows. |
metadata.n_features | integer | Number of feature columns, excluding the target. |
metadata.output_type | string | Output type applied to this request. |
metadata.elapsed_s | number | Elapsed time reported by the server, in seconds. |
metadata.cached_context | CachedContext (object) | Present when this request cached its context ( |
metadata.cached_context_declined | string | Present instead of
|
metadata.classes | array of number or string | Classification only. Gives the column order of the probability matrix, which follows the sorted label order rather than the order they appear in your table. |
metadata.quantiles | array of number | |
metadata.warning | string | Present when admission control expected the table not to fit in GPU memory in one piece and it was processed in parts anyway. The result is unchanged; it took longer. Wording only -- the GB figures behind the decision stay on the server. |
Limits
For api.nums.world · Verified . Other deployments may differ.
| Limit | Maximum | Applies to |
|---|---|---|
max_rows | 300,000 | Rows across context and query |
max_features | 4,095 | Feature columns, excluding the target |
max_cells | 60,000,000 | Cells in one request |
max_classes | 64 | Target classes for classification |
max_request_bytes | 1 GiB | Request size (1,073,741,824 bytes) |
max_response_bytes | 512 MiB | Response size (536,870,912 bytes) |
All limits apply together; the maximum row and feature counts cannot necessarily be used in the same request.
Check the latest values
Limits can change. Query GET /v1/limits to check the current values. No API key is required.
curl --fail --silent --show-error https://api.nums.world/v1/limits
Account allowance
Your account has a daily and a monthly allowance of cells and limits on
requests per minute and per hour. Use your API key with GET https://api.nums.world/v1/usage to check usage and allowances for that credential,
and every successful response carries them in metadata.quota.
When an allowance runs out the service answers 429 until it
resets. Nothing is invoiced and there is no overage.
Asking about your own table
GET https://api.nums.world/v1/estimate tells you whether a table of your shape
will be accepted. It needs no key, uploads nothing and runs the same checks as
admission control, so its verdict is the one a real request would get.
curl 'https://api.nums.world/v1/estimate?n_context=300000&n_query=1000&n_features=10'
{
"accepted": false,
"cells": 3010000,
"counts_against_allowance": 3010000,
"estimated_seconds": 99.1,
"model": "causilo-clf",
"deadline_s": 300.0,
"max_context_rows": 299000,
"code": "request_too_large",
"reason": "301,000 rows is over the 300,000 row limit."
}
Everything in the answer except the cell count, which is exact, is a
prediction from a fitted model. max_context_rows is the number of
context rows that fit at your column count, which is the figure a refusal
would have named. When a shape is refused, reason gives the same
explanation the refusal would give.
How requests are counted
A cell is one value in a table you send, counting the context and query rows together and excluding the target column. A request that names a stored context is counted for the context rows as well, because storing a context saves the upload and not the cells.
A request refused before it runs, whether by validation, a limit or a rate
limit, uses no cells, and neither does one whose response is too large to
return. It still counts towards the requests per minute and per hour, as every
request that reaches the service does. A request that ran and then failed uses
its cells; these are the
504 and the 502 described under
Errors.
A request you abandon is also counted in full. Disconnecting does not reach
the machine running the prediction, which finishes the work regardless, and a
call that has started cannot be cancelled, which is why
POST /v1/cancel answers 501. The Python client
reports this case as CausiloTimeout and does not retry, since a
retry would be counted again, so give it a timeout long enough to read the
answer the request has already used cells for.
Cell accounting and recovery
Cell cost: (n_context + n_query) * n_features. Feature columns exclude the target. Reusing a stored context still counts its rows; the saving is the upload.
| Condition | Status | Cells charged | Next action |
|---|---|---|---|
Refused before work startsvalidation_failed, request_too_large, rate_limited | 422, 413, 429 | 0 cells | Follow the error code and retryable flag |
Response cannot be returnedresponse_too_large | 413 | 0 cells | Do not retry the same request |
Work ran; answer timed out or could not be readupstream_unavailable, timeout | 502, 504 | Full request cells | Do not retry the same request |
Required store unavailable before workupstream_unavailable | 503 | 0 cells | Retry |
| You stop waiting for a started request Client exception: CausiloTimeout | Client timeout | Full request cells | Do not retry the same request |
POST /v1/cancel returns 501; a started request cannot be stopped. Requests refused before work still count towards request-rate limits.
| Measurement | Unit | Includes the queue |
|---|---|---|
metadata.elapsed_s | seconds | No |
| Client wall-clock time | seconds | Yes |
Client wall-clock time also includes the network round trip.
Sending more than one call at a time
The rate limits are not a parallelism budget. Calls beyond what the service is running at the moment are queued and answered in turn, so none is refused for being concurrent, and the time spent waiting appears as latency.
Sending calls in parallel helps when tables are small and makes little difference when they are large, because large calls wait for the same GPU in turn. Many small queries against one table are faster as a single call with more query rows than as many calls.
A large table also delays the calls queued behind it, since it holds the queue for as long as it runs. Where latency matters, keep tables small and store the context instead of sending it again.
elapsed_s in the response measures the prediction alone and
excludes the time the call spent queued, so a client measuring wall-clock time
will see a larger number. The difference between the two is the time spent in
the queue.
Errors
Errors are RFC 9457 problem documents, and you should branch on
code. The status appears in the body as well as on
the response. Every error carries a request_id, which is also
sent as the X-Request-Id header, and a retryable
boolean that says whether sending the same request again can succeed. A
429 or 503 response also carries
Retry-After in whole seconds. Successful responses carry
X-Request-Id too, in the header only.
Two statuses mean that the work has already run. A 504 with
code timeout means the service stopped waiting while the
prediction continued to the end, and it is counted in full. A 502
with code upstream_unavailable means the prediction ran but its
answer could not be read. Both carry retryable: false, because a
second attempt would be counted again, so send a smaller table instead. The
Python client never retries either of them, whatever the body says.
The type of every error links to its row in the table below.
The Python client raises one exception class for each group of codes, and the
Usage, limits and
cancellation example lists which class covers which codes.
| code | status | What happened | What to do |
|---|---|---|---|
unauthorized | 401 | No key, a wrong key, or one that has been revoked. | Check the key. A revoked key stops working within about a minute, or within 15 minutes while our key store is unreachable. |
validation_failed | 422 | The tables or the params do not fit the contract. | The detail names the column or field. Feature columns must match in name and order. |
bad_request | 400 | The body could not be parsed at all. Only a container you run yourself answers this way; the hosted API reports the same request as validation_failed. | Check the content type and that both parts are present. |
empty_body | 400 | The request arrived with no body. | Send the tables. A proxy that strips bodies will produce this. |
payload_too_large | 413 | The body is over this deployment's ceiling. | Send fewer rows, or store the context once and name it afterwards. |
request_too_large | 413 | The table is over the row or cell cap, or would take longer than the deployment allows. | The detail says how many context rows fit at your column count. |
context_not_found | 404 | The stored context has expired or been released. | Send the table again. The Python client does this for you. |
quota_exceeded | 429 | The monthly or daily cell cap. | The body says which cap, what is used and when it resets. |
rate_limited | 429 | Too many requests in the minute or hour window. The cell caps are separate. | Wait for the Retry-After seconds and resend. |
response_too_large | 413 | The prediction is bigger than can be returned. | Ask for fewer query rows. Nothing counts against your allowance. |
not_ready | 503 | The service is starting or being replaced. | Retry. This is not an error on your side. |
unavailable | 503 | The GPU was busy for the whole wait, the service is short of memory, or it is shutting down. | Retry. Nothing counts against your allowance. |
upstream_unavailable | 502 or 503 | 502: the prediction ran and its answer could not be read; its cells count. 503: a store the service depends on could not be reached before the work started. | On a 502, do not resend; send a smaller table. On a 503, retry. retryable says which. |
inference_failed | 500 | The prediction failed inside the service. | Nothing counts against your allowance. Quote the request_id if it repeats. |
client_gone | 499 | The caller disconnected before the answer was returned. | Recorded in the logs only. You will not receive this one. |
cancel_key_in_use | 409 | Two requests sent the same cancel key. | Use a fresh key per request. |
not_found | 404 | No such route. | Check the path. |
method_not_allowed | 405 | Wrong method for that route. | Check the method. |
not_implemented | 501 | The route exists but this deployment cannot do it. | POST /v1/cancel is the only one. A call that has started cannot be stopped. |
timeout | 504 | The service did not answer within its own wait. | The work is still running and is counted. Do not retry; send a smaller table. |