API documentation
The Iosis API lets you submit strategy runs, fetch results, render graphs, and inspect datasets programmatically. All endpoints accept API key authentication via a bearer token.
Machine-readable: /docs.md · /quickstart · /quickstart.md · /llms.txt
Authentication
API keys are workspace-scoped credentials used to authenticate programmatic access. Create and manage keys in the Account page (/app/account). A key is shown in full only once at creation - copy it immediately and store it as IOSIS_API_KEY.
Key format
iosis_3f9c2a71_kJ9zT2vQ5xDn8sLmP4wRbYcHgNfE1aUbiosis_- fixed prefix identifying an Iosis API key.- Next 8 hex characters - the key prefix, used to look the key up server-side.
- Remainder - the secret token. Only the prefix is ever shown in the UI again.
Using a key
Send the key as a bearer token in the Authorization header on every request:
Authorization: Bearer iosis_3f9c2a71_kJ9zT2vQ5xDn8sLmP4wRbYcHgNfE1aUbThe Python client handles this automatically from the IOSIS_API_KEY environment variable:
from iosisclient import IosisClient
client = IosisClient() # reads IOSIS_API_KEY from environmentInvalid key response
Missing, invalid, or revoked keys return 401:
{
"error": "invalid_api_key",
"message": "API key is invalid."
}Security
- Treat a key like a password - it grants full API access to the workspace it belongs to.
- Never commit keys to source control, print them in logs, or embed them in client-side code.
- Store keys in an environment variable or secret manager.
- There is a limit of 1 key per workspace. Revoke unused keys instead of accumulating them.
- Revoking a key takes effect immediately - requests using it fail with
401right away.
Rate limits and credits
API key requests are rate limited per key in a fixed one-minute window. Limits apply independently of the credit allowance.
| Bucket | Endpoints | Limit |
|---|---|---|
| Get | GET endpoints (runs, charts, datasets, tsfns) | 20/min |
| Render | POST /api/graphs/render | 10/min |
| Runs | POST /api/runs | 10/min |
Over-limit requests return 429 with a Retry-After header:
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded.",
"retry_after_seconds": 12
}Credits
Compute is metered in credits. 1 credit = 6 seconds of worker compute. Runs served from the result cache cost zero credits. Credits are a per-workspace monthly allowance - the free plan includes a small default, paid tiers include their plan allowance.
When the month's allowance is exhausted, POST /api/runs returns:
{
"error": "insufficient_credits",
"message": "Monthly credit allowance exhausted."
}Check your current credits and usage in the Account page (/app/account).
Check credit balance
curl https://iosis.dev/api/credits \
-H "Authorization: Bearer $IOSIS_API_KEY"Returns the workspace credit balance for the current month:granted, total, consumedMs, consumedCredits, and remaining.
{
"credits": {
"granted": 100,
"total": 100,
"consumedMs": 78000,
"consumedCredits": 13,
"remaining": 87
}
}Installation
Install the Python client from PyPI:
pip install iosisclientRequires Python 3.11+. The client module uses only stdlib; the package depends on iosislib.
Endpoints
All endpoints accept API key authentication unless noted otherwise.
| Method | Endpoint | Description |
|---|---|---|
POST | /api/runs | Submit a strategy run |
GET | /api/runs/:runId | Get a run with signed artifact URLs |
GET | /api/runs/:runId/charts | Get chart URLs for a run |
GET | /api/runs/charts | List succeeded runs with charts |
POST | /api/graphs/render | Render a strategy graph as SVG |
GET | /api/credits | Get remaining credit balance |
GET | /api/versions | Oldest supported client/library versions (no auth) |
GET | /api/datasets | List published datasets |
GET | /api/datasets/lookup | Look up a dataset by name/version |
GET | /api/datasets/manifest | List datasets with full manifests |
GET | /api/tsfns | List allowed time-series functions |
GET | /api/schema/strategy | Strategy JSON Schema (v0.1.0) |
Runs
Submit a run
curl -X POST https://iosis.dev/api/runs \
-H "Authorization: Bearer $IOSIS_API_KEY" \
-H "Content-Type: application/yaml" \
-H "Idempotency-Key: $(uuidgen)" \
--data-binary @strategy.yamlThe request body is a strategy YAML document (the iosis.strategy format, version 0.1.0). A malformed document returns 400.
Always send an Idempotency-Key header (a UUID) to make submission idempotent. Reusing the same key with the same YAML returns the original run instead of creating a duplicate; reusing it with different YAML returns 409 idempotency_conflict. The Python client generates one automatically.
A successful submission returns 202 Accepted:
{
"id": "8f3c…",
"status": "queued"
}Get a run
curl https://iosis.dev/api/runs/8f3c… \
-H "Authorization: Bearer $IOSIS_API_KEY"Returns the run status (queued, running, succeeded, or failed), compute time, result summary, and short-lived signed URLs to result artifacts.
Artifact URLs expire after 60 seconds for results and 5 minutes for charts. Download them immediately - do not cache the URLs.
A completed run returns:
{
"run": {
"id": "8f3c…",
"status": "succeeded",
"computeMs": 150,
"resultSummary": { "graph_id": "…", "outputs": ["…"], "charts": ["…"] },
"result": {
"kind": "result",
"name": "result.parquet",
"url": "https://…/result.parquet?X-Amz-…",
"sha256": "…",
"expiresAt": "2026-08-20T…Z"
},
"artifacts": ["…"]
}
}If the run failed, status is failed and run.error explains why.
Wait for a run
The Python client includes a polling helper with exponential backoff:
result = client.wait_for_run(run["id"])
print(result) # {"run": {"status": "succeeded", ...}}Get charts for a run
curl https://iosis.dev/api/runs/8f3c…/charts \
-H "Authorization: Bearer $IOSIS_API_KEY"Returns signed URLs to the run's chart artifacts. URLs expire after 5 minutes.
List runs with charts
GET https://iosis.dev/api/runs/charts
Authorization: Bearer <key>Returns recently succeeded runs that produced charts.
Graphs
Render a graph
curl -X POST https://iosis.dev/api/graphs/render \
-H "Authorization: Bearer $IOSIS_API_KEY" \
-H "Content-Type: application/yaml" \
--data-binary @strategy.yaml \
--output graph.svgThe request body is a strategy YAML document - the same shape accepted by POST /api/runs. The response renders the strategy graph as an SVG in the app's ReactFlow visual language: nodes, ports, and edges are laid out server-side, so no browser is required. Unknown operations render as generic nodes, so the endpoint works with any valid strategy YAML.
A successful render returns 200 with Content-Type: image/svg+xml. The SVG uses the dark theme with a transparent background: node bodies are #101010 with warm borders (model nodes get a blue #4E7CFF border, backtest nodes render as hexagons), edge strokes are red #E31520, and input/output handles sit on the node edges.
Error responses
| Status | Cause |
|---|---|
400 | Malformed or empty strategy graph |
401 | Missing or invalid API key |
413 | Request body too large |
415 | Non-YAML content type |
Datasets
List published datasets
curl https://iosis.dev/api/datasets \
-H "Authorization: Bearer $IOSIS_API_KEY"Returns the names of published datasets.
Any name returned here can be used directly as the name parameter in a source.dataset node (see Strategy Format below). For example, if list_datasets() returns "polymarket_weather", you can reference it with:
nodes:
market:
op: source.dataset
version: 0.1.0
params:
name: polymarket_weatherLook up a dataset
curl "https://iosis.dev/api/datasets/lookup?name=<name>" \
-H "Authorization: Bearer $IOSIS_API_KEY"name- required.
Returns the dataset manifest: glob path, row count, resolution, coverage window, and schema.
List datasets with manifests
curl https://iosis.dev/api/datasets/manifest \
-H "Authorization: Bearer $IOSIS_API_KEY"Returns every published dataset with its full manifest:
{
"datasets": [
{
"name": "polymarket_weather",
"manifest": {
"format": "iosis.cloud-dataset-v1",
"path": "s3://.../datasets/polymarket_weather/**/*.parquet",
"row_count": 8760,
"resolution": "1h",
"time_range": { "start": "...", "end": "..." },
"schema": {
"time": "timestamp",
"columns": { "yes_price": "float64", "forward_price": "float64" }
}
}
}
]
}Capabilities
List allowed TSFNs
curl https://iosis.dev/api/tsfns \
-H "Authorization: Bearer $IOSIS_API_KEY"Returns the catalog of time-series functions (TSFNs) allowed in strategy YAML, generated from the iosislib built-in registry. Each entry carries the operation name and version, category (transform, source, model, backtest), parameters (name, type, required flag, default), null/lookahead policy, and the input/output frame signature.
{
"format": "iosis.tsfn-catalog",
"version": "0.1.0",
"tsfns": [
{
"op": "transform.logit",
"version": "0.1.0",
"category": "transform",
"parameters": [],
"signature": {
"input": { "columns": [["value", "Float64"]] },
"output": { "columns": [["logit", "Float64"]] }
}
}
]
}Sources and transforms whose signature depends on data (e.g. source.dataset_source, backtest.backtest) resolve to "signature": null. To reference an allowed op in a strategy, use op + version as op@version.
Errors
All endpoints return errors as JSON with an error code and a human-readable message:
| Status | Meaning |
|---|---|
400 | Malformed request (invalid YAML, missing parameter, bad ID) |
401 | Missing, invalid, or revoked API key |
403 | Account not approved, workspace not active, or monthly credits exhausted |
404 | Run or dataset not found |
409 | Idempotency key reused with different YAML (idempotency_conflict); strategy integrity failure (strategy_integrity_failed) |
413 | Request body too large |
415 | Unsupported media type |
429 | Rate limit exceeded (see Retry-After header) |
500 | Internal error |
The Python client raises IosisError with .status, .code, and .message attributes:
from iosisclient import IosisError
try:
client.get_run("bad-id")
except IosisError as e:
print(e.status) # 400
print(e.code) # "invalid_run_id"
print(e.message) # "Run ID must be a valid UUID."Strategy format
Strategies are YAML documents conforming to the iosis.strategy format, version 0.1.0. Each strategy declares nodes (sources, transforms, models, backtests) and outputs.
Fetch the JSON Schema to validate strategies programmatically:
curl https://iosis.dev/api/schema/strategy \
-H "Authorization: Bearer $IOSIS_API_KEY"format: iosis.strategy
version: 0.1.0
name: market-zscore
description: Rolling z-scores of changes in market probability.
nodes:
market:
op: source.dataset
version: 0.1.0
params:
name: polymarket_weather
time_range:
start: 2026-09-01
end: 2026-09-30
changes:
op: transform.pct_change
version: 0.1.0
inputs:
price: market.yes_price
params:
input_column: price
output_column: changes
zscore:
op: transform.rolling_z_score
version: 0.1.0
inputs:
changes: changes.changes
params:
input_column: changes
output_column: zscore
periods: 20
min_samples: 10
outputs:
price: market.yes_price
zscore: zscore.zscoreFields
format- must be exactlyiosis.strategy.version- SemVer document contract version. Current:0.1.0.name- human-readable strategy name.description- optional, no execution semantics.metadata- optional.metadata.chartsdeclares the run's charts;metadata.metricsis rejected.nodes- maps stable identifiers to node declarations. Declaration order has no meaning.outputs- gives public names tonode.outputreferences and determines which nodes belong to the strategy.
Node structure
op- stable operation contract name (e.g.transform.logit,source.dataset).version- SemVer operation-contract version, separate fromop.params- operation-specific, JSON-compatible values.inputs- maps the operation's input names tonode.outputreferences.materialize- optional boolean. Omission lets the operation contract choose.
source.dataset params
For source.dataset nodes, the name param accepts a dataset name from GET /api/datasets or list_datasets(). The platform resolves the name to the actual storage location automatically, and the node's schema (time column and value columns with dtypes) comes from the dataset manifest. Declaring schema is rejected with 400 invalid_node_params.
name- dataset name (e.g.polymarket_weather).time_range- optional bounds on the dataset scan.
time_range format
time_range accepts start and end over the time column: bare dates (2026-01-01), ISO-8601 timestamps (2026-01-01T12:30:00), or timestamps with a Z suffix (normalized to UTC). Bounds are inclusive on both ends — a bare-date end covers that whole day. start after end fails the run.
params:
name: polymarket_weather
time_range:
start: 2026-09-01
end: 2026-09-30Input forms
The short form is normally enough:
inputs:
probability: market.yes_priceUse the expanded form when an input needs consumer-owned behavior:
inputs:
probability:
from: market.yes_price
tolerance: 5m
nulls: fill
fill: 0.0Expanded input fields:
from- required source reference.tolerance- non-negative number or Polars-style duration string (e.g.5m). Omission means unbounded backward as-of match.nulls-error,propagate,drop,fill, orpass.fill- scalar, required only whennullsisfill.
Identifiers and references
Identifiers begin with a letter and contain only letters, digits, _, or -. Dots are reserved as the separator in references (e.g. market.yes_price).
Outputs
The outputs map declares the final result frames. The result.parquet file contains the alphabetically-first declared output; every declared output is also returned as its own signed artifact.
Charts
Charts render only from metadata.charts declarations. Each declaration is bound to one declared output:
metadata:
charts:
- kind: line
name: price_line
output: price
columns: [yes_price]kind is line, bars, equity, orscatter; scatter takes x and y instead of columns. A chart that cannot render returns a placeholder SVG.
Notes
- Walk-forward models (e.g.
model.light_gbm@0.3.0) emitNaNpredictions until the first retrain boundary (with the default{ every: 100 }scheduler, the first 100 rows). Exclude those rows before computing metrics over the full series. - Model nodes fail loudly on nulls: any null in
features/targetaborts the run withNullPolicy.ERROR. Warm-up nulls from upstream transforms (e.g. the firstpct_changerow) are enough to trip it — declarenulls: dropornulls: fillon model inputs (see step 5 below). - Metric nodes (
metrics.mse,metrics.mae,metrics.max_drawdown,metrics.sharpe,metrics.total_return) produce a single row and are declared like any other output. They reject non-finite values and nulls by default; passdrop_nonfinite: trueto drop non-finite rows, and bind inputs withnulls: dropto skip warm-up nulls.
Strategy construction guide
This guide walks through building a strategy from scratch. A strategy is a directed acyclic graph of nodes: sources load data, transforms process it, and outputs declare which columns to return.
1. Pick a dataset
Call GET /api/datasets or list_datasets() to see available datasets. Each entry has a name. Pick one that matches your use case.
2. Declare a source node
Reference the dataset by name using source.dataset. The platform resolves the name to the actual storage location automatically, and derives the node's schema from the dataset manifest. Use time_range to bound the scan.
nodes:
market:
op: source.dataset
version: 0.1.0
params:
name: polymarket_weather
time_range:
start: 2026-09-01
end: 2026-09-303. Add transforms
Compute derived columns by chaining transforms. Each transform takes inputs from previous nodes and produces new outputs.
changes:
op: transform.pct_change
version: 0.1.0
inputs:
price: market.yes_price
params:
input_column: price
output_column: changes
zscore:
op: transform.rolling_z_score
version: 0.1.0
inputs:
changes: changes.changes
params:
input_column: changes
output_column: zscore
periods: 20
min_samples: 10The inputs map connects this node to upstream outputs. The key is the input name expected by the operation; the value is a node.output reference.
4. Declare outputs
The outputs map declares which columns appear in the resultparquet file. Reference node outputs as node.output.
outputs:
price: market.yes_price
zscore: zscore.zscore5. Optional: add a model
For machine learning workflows, add a model node that consumes features and produces predictions. Walk-forward models emit NaN until the first retrain boundary (with the default scheduler, the first 100 rows), so exclude warm-up rows before computing metrics.
Model nodes fail loudly on null input: a single null in features or target aborts the run with NullPolicy.ERROR. Upstream transforms almost always produce warm-up nulls (e.g. the first pct_change row), so declare per-input null handling — nulls: drop, or nulls: fill with a fill value:
model:
op: model.light_gbm
version: 0.3.0
inputs:
features:
from: zscore.zscore
nulls: drop
target:
from: changes.changes
nulls: drop
params:
num_boost_round: 20
seed: 0Putting it together
format: iosis.strategy
version: 0.1.0
name: market-zscore
description: Rolling z-scores of changes in market probability.
nodes:
market:
op: source.dataset
version: 0.1.0
params:
name: polymarket_weather
changes:
op: transform.pct_change
version: 0.1.0
inputs:
price: market.yes_price
params:
input_column: price
output_column: changes
zscore:
op: transform.rolling_z_score
version: 0.1.0
inputs:
changes: changes.changes
params:
input_column: changes
output_column: zscore
periods: 20
min_samples: 10
outputs:
price: market.yes_price
zscore: zscore.zscoreTips
- Browse available transforms with
GET /api/tsfnsorlist_tsfns(). - Validate strategies with
render_graph()before submitting. - Use an
Idempotency-Keyheader to make submission idempotent; reuse a key only when retrying the same YAML.
Backtest reference
The backtest.backtest operation runs a signal-driven backtest over historical data. It requires three declarative sub-mappings: feed, policy, and optionallyrisk_policy. Use kind to select the implementation.
Feed
Describes the market data source. Currently only l1 is supported.
feed:
kind: l1
venue:
name: polymarket
universe: [nyc-high-2026-09-24, london-low-2026-09-24]
bid_column: bid # optional, default "bid"
ask_column: ask # optional, default "ask"Policy
Controls order execution logic.
| Kind | Parameters | Description |
|---|---|---|
signal | signal_column (default "signal") | Generates orders proportional to the signal value. |
threshold | signal_column, threshold (default 0), long_qty (default 1), short_qty (default 1) | Generates fixed-size orders when signal exceeds threshold. |
policy:
kind: signal
signal_column: zscoreRisk policy
Optional position sizing guardrails.
| Kind | Parameters | Description |
|---|---|---|
fractional_limit | fraction (in (0, 1]) | Limits position to a fraction of portfolio value. |
fractional_kelly | custom_fraction (in (0, 1]) | Kelly-optimal sizing with a fractional multiplier. |
risk_policy:
kind: fractional_limit
fraction: 0.1Full backtest example
nodes:
book:
op: source.dataset
version: 0.1.0
params:
name: polymarket_weather_lob
odds:
op: transform.logit
version: 0.1.0
inputs:
value: book.mid
params:
output_column: log_odds
backtest:
op: backtest.backtest
version: 3.0.0
inputs:
bid: book.bid
ask: book.ask
signal: odds.log_odds
params:
feed:
kind: l1
venue:
name: polymarket
universe: [nyc-high-2026-09-24, london-low-2026-09-24]
policy:
kind: signal
signal_column: signal
initial_cash: 10000.0
outputs:
equity: backtest.equity