Skip to content
Docs

API reference

Routes, request fields, response formats and errors for the Causilo API.
On this page

Base URL and authentication

API base URLTEXT
https://api.nums.world

Send your API key in either of these headers. Use an existing API key. See API access for availability and support.

Authentication headersHTTP
Authorization: Bearer csl_...
X-Causilo-Key: csl_...

Routes

POST /v1/predict

Predict. Multipart with context, query and params, or one Parquet file.

API key required · Responses: 200, 400, 401, 404, 409, 413, 422, 429, 503, 500

FieldLocationTypeRequiredDefaultMeaning and constraints
X-Causilo-Cancel-Keyheaderstringno—

Optional. A token you choose, letters and digits, 16 to 128 long. Lets you call /v1/cancel with it if you give up on this request, on a deployment where cancelling works (api.nums.world answers 501 there). The Python client sends one on every call.

At least 16 characters. Pattern: ^[A-Za-z0-9]+$.

Acceptheaderstringno"application/json"

Ask for Parquet when the prediction is large. Metadata is not lost: it goes into the Parquet schema metadata under causilo-metadata, because SageMaker strips custom response headers.

Supported values: application/json, application/vnd.apache.parquet.

GET /v1/limits

What this deployment accepts. Numbers a client can check before sending.

No API key required · Responses: 200

GET /v1/estimate

Whether a table of a given shape will be accepted, and how long it will take.

No API key required · Responses: 200, 422

FieldLocationTypeRequiredDefaultMeaning and constraints
n_contextqueryintegeryes—

Rows in the context table, the ones that already carry an answer.

At least 0.

n_featuresqueryintegeryes—

Feature columns, not counting the target. Admission measures the context table's own width with the target included, which is why cells and counts_against_allowance can differ from what a naive rows-times-columns gives.

At least 0.

n_queryqueryintegerno1

Rows to predict. They cost very little next to context rows.

At least 0.

n_classesqueryintegerno0

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.

modelquerystringno—

Defaults to the first model in GET /v1/limits.

GET /v1/usage

What this credential has used this month and today.

API key required · Responses: 200, 401

GET /v1/contexts

The contexts stored for this caller.

API key required · Responses: 200, 401

DELETE /v1/contexts/{context_id}

Release one stored context.

API key required · Responses: 200, 401, 404

FieldLocationTypeRequiredDefaultMeaning and constraints
context_idpathstringyes—

The context_id from the response that stored it.

GET /v1/ping

Whether the service can take a request.

No API key required · Responses: 200, 503

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 tableWhat 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, structsRefused.
Infinities, or values at or above the documented ceiling Refused. Rescale the column or drop those rows.
float16Widened to float32 for you.
Duplicate column namesRefused.
Your DataFrame indexDropped 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.

RuleApplies to
No missing values in the context's targetboth models
At most 64 distinct values, and at least two causilo-clf
Whole numbers or strings, not fractions causilo-clf
Numeric, finite, under 3.4e38causilo-reg
Not a datetimecausilo-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.

FieldTypeRequiredDefaultConstraints
cache_contextbooleannofalse—
context_idstringno——
modelstringyes—Supported values: causilo-clf, causilo-reg.
targetstringwith a context table——
output_typestringnotask-specificSupported values: probas, preds, mean, median, quantiles.
quantilesarraywith 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

TaskAllowed
classificationprobas, preds (default probas)
regressionmean, 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

FieldShapeMeaning and constraints
predictionone-dimensional or two-dimensional array

One entry per query row. 2-D for probas and quantiles, 1-D otherwise. NaN and infinity are sent as null.

metadataMetadata (object)
metadata.quotaobject

What is left of the month, mirroring the X-RateLimit-* headers. Present only when the request was charged. In the body as well as the headers because SageMaker strips custom response headers, so for callers on that platform this is the only copy.

metadata.served_byobject

What produced this prediction. Record it alongside any result you may have to defend later.

model above names the lineage that was asked for, not the weights that answered: a key is served by whatever checkpoint the running image carries, so two responses naming the same key are not on their own evidence that the same weights answered. These fields are.

metadata.modelstring

The key that was served. It names the lineage, not the checkpoint version behind it.

metadata.taskstring

Task selected by the model: classification or regression.

Supported values: classification, regression.

metadata.n_contextinteger

Context rows behind the answer. With context_id these were sent on an earlier request rather than this one, and are billed on both -- so this figure agrees with the cells you were charged for either way.

metadata.n_queryinteger

Number of query rows.

metadata.n_featuresinteger

Number of feature columns, excluding the target.

metadata.output_typestring

Output type applied to this request.

metadata.elapsed_snumber

Elapsed time reported by the server, in seconds.

metadata.cached_contextCachedContext (object)

Present when this request cached its context (cache_context: true) and when it answered from one (context_id). Absent otherwise, including on a deployment with the cache turned off.

metadata.cached_context_declinedstring

Present instead of cached_context when cache_context: true was sent and the context was not kept. The prediction is unaffected — the request is answered exactly as it would have been without the flag. The string says which of these it was:

storing contexts is turned off on this deployment — the operator left the context store unconfigured.

the context store is unavailable — writing the table to storage failed. Send cache_context: true again on a later request.

unidentified caller — the request did not carry an issued key, so there is no account to store the context against. Every such caller would otherwise share one.

metadata.classesarray 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.quantilesarray of number
metadata.warningstring

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.

LimitMaximumApplies to
max_rows300,000Rows across context and query
max_features4,095Feature columns, excluding the target
max_cells60,000,000Cells in one request
max_classes64Target classes for classification
max_request_bytes1 GiBRequest size (1,073,741,824 bytes)
max_response_bytes512 MiBResponse 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.

Read current request limitsBASH
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.

TerminalBASH
curl 'https://api.nums.world/v1/estimate?n_context=300000&n_query=1000&n_features=10'
JSON
{
  "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.

ConditionStatusCells chargedNext action
Refused before work starts
validation_failed, request_too_large, rate_limited
422, 413, 4290 cellsFollow the error code and retryable flag
Response cannot be returned
response_too_large
4130 cellsDo not retry the same request
Work ran; answer timed out or could not be read
upstream_unavailable, timeout
502, 504Full request cellsDo not retry the same request
Required store unavailable before work
upstream_unavailable
5030 cellsRetry
You stop waiting for a started request
Client exception: CausiloTimeout
Client timeoutFull request cellsDo 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.

MeasurementUnitIncludes the queue
metadata.elapsed_ssecondsNo
Client wall-clock timesecondsYes

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.

codestatusWhat happenedWhat to do
unauthorized401No 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_failed422The 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_request400The 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_body400The request arrived with no body.Send the tables. A proxy that strips bodies will produce this.
payload_too_large413The body is over this deployment's ceiling.Send fewer rows, or store the context once and name it afterwards.
request_too_large413The 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_found404The stored context has expired or been released.Send the table again. The Python client does this for you.
quota_exceeded429The monthly or daily cell cap.The body says which cap, what is used and when it resets.
rate_limited429Too many requests in the minute or hour window. The cell caps are separate.Wait for the Retry-After seconds and resend.
response_too_large413The prediction is bigger than can be returned.Ask for fewer query rows. Nothing counts against your allowance.
not_ready503The service is starting or being replaced.Retry. This is not an error on your side.
unavailable503The 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_unavailable502 or 503502: 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_failed500The prediction failed inside the service.Nothing counts against your allowance. Quote the request_id if it repeats.
client_gone499The caller disconnected before the answer was returned.Recorded in the logs only. You will not receive this one.
cancel_key_in_use409Two requests sent the same cancel key.Use a fresh key per request.
not_found404No such route.Check the path.
method_not_allowed405Wrong method for that route.Check the method.
not_implemented501The route exists but this deployment cannot do it.POST /v1/cancel is the only one. A call that has started cannot be stopped.
timeout504The service did not answer within its own wait.The work is still running and is counted. Do not retry; send a smaller table.