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_kJ9zT2vQ5xDn8sLmP4wRbYcHgNfE1aUb
  • iosis_ - 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_kJ9zT2vQ5xDn8sLmP4wRbYcHgNfE1aUb

The Python client handles this automatically from the IOSIS_API_KEY environment variable:

from iosisclient import IosisClient

client = IosisClient()  # reads IOSIS_API_KEY from environment

Invalid 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 401 right 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.

BucketEndpointsLimit
GetGET endpoints (runs, charts, datasets, tsfns)20/min
RenderPOST /api/graphs/render10/min
RunsPOST /api/runs10/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 iosisclient

Requires Python 3.11+. The client module uses only stdlib; the package depends on iosislib.

Endpoints

All endpoints accept API key authentication unless noted otherwise.

MethodEndpointDescription
POST/api/runsSubmit a strategy run
GET/api/runs/:runIdGet a run with signed artifact URLs
GET/api/runs/:runId/chartsGet chart URLs for a run
GET/api/runs/chartsList succeeded runs with charts
POST/api/graphs/renderRender a strategy graph as SVG
GET/api/creditsGet remaining credit balance
GET/api/versionsOldest supported client/library versions (no auth)
GET/api/datasetsList published datasets
GET/api/datasets/lookupLook up a dataset by name/version
GET/api/datasets/manifestList datasets with full manifests
GET/api/tsfnsList allowed time-series functions
GET/api/schema/strategyStrategy 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.yaml

The 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.svg

The 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

StatusCause
400Malformed or empty strategy graph
401Missing or invalid API key
413Request body too large
415Non-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_weather

Look 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:

StatusMeaning
400Malformed request (invalid YAML, missing parameter, bad ID)
401Missing, invalid, or revoked API key
403Account not approved, workspace not active, or monthly credits exhausted
404Run or dataset not found
409Idempotency key reused with different YAML (idempotency_conflict); strategy integrity failure (strategy_integrity_failed)
413Request body too large
415Unsupported media type
429Rate limit exceeded (see Retry-After header)
500Internal 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.zscore

Fields

  • format - must be exactly iosis.strategy.
  • version - SemVer document contract version. Current: 0.1.0.
  • name - human-readable strategy name.
  • description - optional, no execution semantics.
  • metadata - optional. metadata.charts declares the run's charts; metadata.metrics is rejected.
  • nodes - maps stable identifiers to node declarations. Declaration order has no meaning.
  • outputs - gives public names to node.output references 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 from op.
  • params - operation-specific, JSON-compatible values.
  • inputs - maps the operation's input names to node.output references.
  • 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-30

Input forms

The short form is normally enough:

inputs:
  probability: market.yes_price

Use the expanded form when an input needs consumer-owned behavior:

inputs:
  probability:
    from: market.yes_price
    tolerance: 5m
    nulls: fill
    fill: 0.0

Expanded 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, or pass.
  • fill - scalar, required only when nulls is fill.

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) emit NaN predictions 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/target aborts the run with NullPolicy.ERROR. Warm-up nulls from upstream transforms (e.g. the first pct_change row) are enough to trip it — declare nulls: drop or nulls: fill on 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; pass drop_nonfinite: true to drop non-finite rows, and bind inputs with nulls: drop to 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-30

3. 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: 10

The 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.zscore

5. 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: 0

Putting 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.zscore

Tips

  • Browse available transforms with GET /api/tsfns or list_tsfns().
  • Validate strategies with render_graph() before submitting.
  • Use an Idempotency-Key header 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.

KindParametersDescription
signalsignal_column (default "signal")Generates orders proportional to the signal value.
thresholdsignal_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: zscore

Risk policy

Optional position sizing guardrails.

KindParametersDescription
fractional_limitfraction (in (0, 1])Limits position to a fraction of portfolio value.
fractional_kellycustom_fraction (in (0, 1])Kelly-optimal sizing with a fractional multiplier.
risk_policy:
  kind: fractional_limit
  fraction: 0.1

Full 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