API reference

Every SDK method: signature, parameters, returns, errors and an example.

One entry per public method of the Python SDK, grouped by resource. Each entry links to the guide page that explains the concepts; this page only specifies. Start at Quickstart if you are new.

Conventions

Base URL and authentication

The API root is https://pulse-api.simudyne.com. Every endpoint except GET /health needs your key in the X-API-Key header; the SDK sets it for you. The API also serves an OpenAPI browser at /docs and the schema at /openapi.json.

bash
curl -H "X-API-Key: $SIMUDYNE_API_KEY" \
  https://pulse-api.simudyne.com/profile
PulseABM(api_key=None, base_url=None, timeout=None, max_retries=3)The client. Holds the key and HTTP session and exposes every resource as an attribute.Any tier

Parameters

api_key : str, optional
Your key (pk_live_…). Falls back to the SIMUDYNE_API_KEY environment variable.
base_url : str, optional
API root, no trailing slash. Falls back to SIMUDYNE_BASE_URL, then https://pulse-api.simudyne.com.
timeout : int, optional
Per-request timeout in seconds. Defaults to 30.
max_retries : int, default 3
Retry attempts, with backoff 1 s, 2 s, 4 s. Reads retry on 502/503/504 and transport errors; submits only on 503 or a connection that never opened; 429 only with Retry-After.

Returns

PulseABM

profile, api_keys, data, simulation, validation, fm, fix : resource
One attribute per resource, documented below.

Raises

ValueError
If no key is passed and SIMUDYNE_API_KEY is unset.

Examples

python
from simudyne import PulseABM

client = PulseABM(api_key="pk_live_...", timeout=60)
print(client.profile.get()["tier"])

Tier gates

GateWho passes
AnyAny active key: free, pro or demo
ProPro or demo tier. A free key gets 403
DemoDemo tier only
AdminKeys with the is_admin flag. A flag, not a tier: it does not bypass the Pro or Demo gates

The chip on each entry shows Any tier or Pro; demo-only and admin-only methods carry the Pro chip and say so in their summary. See Tiers.

Errors

Every non-2xx response raises simudyne.PulseAPIError with status_code, detail (the body's detail: a string, a list of validation errors on 422, or a dict) and errors (the body's top-level errors, or None). Reads are retried on 502, 503 and 504 first; submits only on 503, so a lost answer never queues a second job; a 429 only when it carries Retry-After. A timeout or connection failure that is not retried, or outlasts the retries, is not wrapped: it surfaces as requests.Timeout or requests.ConnectionError. See Errors.

python
from simudyne import PulseAPIError

try:
    client.simulation.get_sim_params("not-a-real-sim-id")
except PulseAPIError as exc:
    print(exc.status_code, exc.detail)

The sim_id

One Monte Carlo run is addressed by an 11-field colon-separated id. The last field is the zero-padded run index; the same id with run zeroed names its group (the unit the free-tier download quota counts). Start from an id the API gave you rather than assembling one. See Output format.

text
{provider}:{exchange}:{symbol}:{cal_date}:{gen_method}:{cal_hash}:{time_range}:{sim_hash}:{scenario}:{algo}:{run}

client.profile

client.profile.get()Return your account record, including the tier that gates every endpoint.Any tier

Returns

dict

user_id : str
Account id; also your FIX CompID.
email : str
Address the account was created with.
name : str
Display name.
organization : str
Empty string if never set.
is_admin : bool
Administrative flag. Not a tier bypass.
tier : str
free, pro or demo.
download_limit : int
New simulation groups per rolling 24 hours; enforced on free tier only.

Raises

PulseAPIError
401 if the key is invalid, revoked or expired.

See also

Examples

python
me = client.profile.get()
print(f"{me['email']} — tier {me['tier']}")
client.profile.usage()Return your request count for the current calendar month.Any tier

Returns

dict

user_id : str
Your account id.
month : str
YYYY-MM.
total_requests : int
Requests so far this month.
by_endpoint : dict
Counts keyed by route template, e.g. /simulation/jobs/{job_id}/status.

Raises

PulseAPIError
401 if the key is invalid, revoked or expired.

Notes

Counts requests, not simulations, and only on usage-logged routes (data, simulation, validation submits, FM live, historical). Profile, API-key, validation job reads, FM registry and FIX usage calls are not counted.

Examples

python
usage = client.profile.usage()
print(f"{usage['month']}: {usage['total_requests']} requests")
client.profile.downloads()Return the free-tier download allowance for the current 24-hour window.Any tier

Returns

dict

limit : int | None
Groups per window. None on pro and demo (unlimited).
used : int
New groups in the current window. 0 on pro and demo.
remaining : int | None
limit − used, floored at 0. None when unlimited.
window_hours : int
Length of the rolling window, 24.
downloaded_groups : list[str] | None
Every group ever downloaded. None when unlimited.
downloaded_group_details : list[dict] | None
The same groups, newest first, each with job_key and downloaded_at.

Raises

PulseAPIError
401 if the key is invalid, revoked or expired.

See also

Notes

The unit is a simulation group, and membership is permanent: a group downloaded once is free to re-fetch. See Account.

Examples

python
quota = client.profile.downloads()
if quota["limit"] is None:
    print("unlimited (pro or demo tier)")
else:
    print(f"{quota['used']}/{quota['limit']} used, {quota['remaining']} left")

client.api_keys

client.api_keys.create(name="")Mint a new API key. The key material is returned once and never again.Any tier

Parameters

name : str, default ""
Label for the key. Blank names it after the account.

Returns

dict

api_key : str
The key material, shown exactly once.
api_key_id : str
Handle to pass to revoke.
key_prefix : str
First 12 characters, as list shows it.
name : str
The stored label.
warning : str
Reminder that the key will not be shown again.

Raises

PulseAPIError
401 if the key is invalid, revoked or expired. There is no per-account key limit.

See also

Examples

python
new_key = client.api_keys.create(name="Training script")
print(new_key["api_key"])  # save it now; it is never shown again
client.api_keys.list()List your active API keys. Revoked keys are not listed; at most 100 are returned.Any tier

Returns

list[dict]

api_key_id : str
Handle to pass to revoke.
key_prefix : str
First 12 characters, e.g. pk_live_ab12.
name : str
The label.
is_active : bool
Always True; revoked keys are omitted.
expires_at : str | None
None for keys that do not expire.
created_at : str
Creation time.
last_used_at : str | None
None until first used.

Raises

PulseAPIError
401 if the key is invalid, revoked or expired.

Examples

python
keys = client.api_keys.list()
print(len(keys), "active keys")
for key in keys:
    print(key["name"], key["key_prefix"], key["created_at"])
client.api_keys.revoke(key_id)Invalidate a key. Requests using it return 401 from then on.Any tier

Parameters

key_id : str
The api_key_id, not the key material.

Returns

dict

{"message": "API key revoked"}

Raises

PulseAPIError
404 if the key does not exist, is not yours, or is already revoked; 401 for a bad calling key.

Notes

Revoking the key the client is using locks it out. Mint the replacement first. See API keys.

Examples

python
new_key = client.api_keys.create(name="Temporary")
client.api_keys.revoke(key_id=new_key["api_key_id"])

client.data

client.data.available_data( model_id=None, symbol=None, q=None, provider=None, exchange=None, date=None, include_calibration_state=False, limit=None, offset=None, )Page through every symbol-day present in the data registry.Pro

Parameters

model_id : str, optional
Keep only markets this model accepts. A production_name works too.
symbol : str, optional
Exact symbol, e.g. 700.
q : str, optional
Case-insensitive substring over the symbol.
provider : str, optional
omd or bmll.
exchange : str, optional
e.g. hkex_securities, lse.
date : str, optional
YYYY-MM-DD. Keeps symbols with data that day and returns only that day.
include_calibration_state : bool, default False
Add calibrated_dates and calibrated_count per symbol.
limit : int, optional
Symbols per page, 1–200. Server default 50.
offset : int, optional
Symbols to skip. Server default 0.

Returns

dict

total : int
Matches before paging.
limit, offset : int
The page returned.
symbols : list[dict]
Each {symbol, provider, exchange, dates}, plus calibrated_dates and calibrated_count with include_calibration_state.

Raises

PulseAPIError
403 below pro; 404 unknown model_id; 400 ambiguous model_id; 422 bad date, limit or offset; 502 model registry unreachable.

See also

Examples

python
page = client.data.available_data(q="700", exchange="hkex_securities", limit=5)
print(page["total"], "matching symbols")
for entry in page["symbols"]:
    print(entry["symbol"], entry["provider"], entry["dates"][:3])
client.data.calibrated_data( symbol=None, exchange=None, provider=None, date=None, q=None, limit=None, offset=None, )List instruments with calibrated days. Every date returned is a valid cal_date for an ABM run.Any tier

Parameters

symbol : str, optional
Exact ticker, e.g. 700.HK.
exchange : str, optional
e.g. hkex_securities.
provider : str, optional
omd or bmll.
date : str, optional
YYYY-MM-DD. Also narrows each available_dates to it. A malformed date matches nothing.
q : str, optional
Case-insensitive substring over ticker and company name.
limit : int, optional
1–1000. Omitted returns everything.
offset : int, optional
Instruments to skip.

Returns

dict

total, limit, offset : int
Match count and page.
symbols : list[dict]
Each with symbol, company_name, provider, exchange, instrument_type, currency, tick_size, lot_size, avg_number_of_msgs, avg_volume_traded and available_dates: a list of {date, status, stage, number_of_msgs, volume_traded, reference_price}.

Raises

PulseAPIError
422 if limit or offset is out of range.

See also

Notes

Field reference on Available symbols.

Examples

python
hits = client.data.calibrated_data(symbol="700.HK", provider="omd")
print(hits["total"], "instruments")
for sym in hits["symbols"]:
    print(sym["symbol"], [d["date"] for d in sym["available_dates"]][:5])
client.data.calibrate( symbol, cal_date, provider, exchange, simulations=None, batch_size=None, optimize_adj_params=True, )Demo tier only. Queue calibration of one symbol-day so the ABM can simulate it.Pro

Parameters

symbol : str
e.g. 700.HK.
cal_date : str
YYYY-MM-DD.
provider : str
omd or bmll.
exchange : str
e.g. hkex_securities.
simulations : int, optional
Simulations to run during calibration.
batch_size : int, optional
Batch size for calibration runs.
optimize_adj_params : bool, default True
Whether to optimise adjustment parameters.

Returns

dict

{"status": "calibration_queued", "symbol": …, "cal_date": …}. No job id: watch calibrated_data for the day to appear.

Raises

PulseAPIError
403 unless demo tier; 422 unrecognised provider or exchange; 503 calibration service unreachable. Missing raw data is not detected here: the job fails later and the day never appears.

See also

Examples

python
job = client.data.calibrate(
    symbol="700.HK",
    cal_date="2025-09-02",
    provider="omd",
    exchange="hkex_securities",
)
print(job["status"])  # calibration_queued

client.simulation

client.simulation.run( symbol="", cal_date="", provider="", exchange="", engine="abm", n_runs=None, seed=42, scenario=None, scenario_params=None, exec_algos=None, model_id="", duration_minutes=None, horizon=None, device="", model_args=None, )Submit n_runs independent Monte Carlo simulations, agent-based or foundation-model.Pro

Parameters

symbol : str, default ""
e.g. 700.HK. Required for ABM.
cal_date : str, default ""
YYYY-MM-DD. ABM: the calibrated day. FM: the day the prompt is sliced from.
provider : str, default ""
omd or bmll.
exchange : str, default ""
e.g. hkex_securities, lse.
engine : str, default "abm"
abm or fm.
n_runs : int, optional
Runs to queue. Server default 5 (ABM) or 1 (FM); FM maximum 8.
seed : int, default 42
Master seed; per-run seeds derive from it.
scenario : str, optional
ABM only, server default normal. One of list_scenarios().
scenario_params : dict, optional
ABM only. Keys impact_multiplier (> 0), order_size_ratio (0, 1], order_freq (e.g. 500ms, 5s, 1min), start_time (HH:MM[:SS]). Any other key is a 422.
exec_algos : list[dict], optional
TWAP/VWAP: type, order_size (lots; negative buys), horizon (seconds), optional start_time. CSS: type="css", orders. FM: at most one.
model_id : str, default ""
FM only, required for FM. A model_id or production_name from fm.models().
duration_minutes : float, optional
FM only. Horizon in wall-clock minutes from the first context row.
horizon : int, optional
FM only. Raw frame count; used only when duration_minutes is unset.
device : str, default ""
FM only. A compute profile: cpu, t4, l4, a100, or gpu for the default GPU; blank uses the model default. GET /fm/compute-profiles lists the ones your account can use.
model_args : dict, optional
FM only. Overrides of the model's declared knobs.

Returns

dict

job_id : str
Pass to get_job_status / get_job_results.
sim_ids : list[str]
One per run, known at submission.
n_runs : int
Runs queued.
engine : str
The engine that accepted the job.

Raises

PulseAPIError
403 below pro, or (FM) a model or GPU profile not enabled for your account. 422 for: a field belonging to the other engine; FM without model_id; a partial market identity on FM (all four or none); an ABM day that is not calibrated (detail lists calibrated dates); bad provider/exchange; n_runs < 1 or FM > 8; bad scenario_params or exec_algos (start_time must be HH:MM[:SS], side buy|sell). 400/404 ambiguous or unknown model_id. 502 model registry, 503 simulation service.

See also

Notes

Examples

python
result = client.simulation.run(
    symbol="700.HK",
    cal_date="2025-09-02",
    provider="omd",
    exchange="hkex_securities",
    n_runs=2,
)
job_id = result["job_id"]
print(job_id, result["sim_ids"])
python
# Models differ by environment, so take one this key can run rather than
# naming it. Prompt it with the busiest instrument in its market, on that
# instrument's latest calibrated day: a quiet name can have too few rows to start.
model = client.fm.models()["models"][0]
market = (model["supported_data"] or [{}])[0]
inst = max(
    client.data.calibrated_data(provider=market.get("provider"), exchange=market.get("exchange"))["symbols"],
    key=lambda s: s["avg_number_of_msgs"],
)
day = {"symbol": inst["symbol"], "provider": inst["provider"], "exchange": inst["exchange"],
       "dates": [d["date"] for d in inst["available_dates"]]}

job = client.simulation.run(
    engine="fm",
    model_id=model["model_id"],
    symbol=day["symbol"],
    cal_date=day["dates"][-1],
    provider=day["provider"],
    exchange=day["exchange"],
    duration_minutes=5,
    n_runs=2,
)
print(job["job_id"], job["sim_ids"])
client.simulation.run_lrm( symbol, cal_date, provider, exchange, order_sizes, n_runs=50, seed=42, horizon_mins=60, strategy="vwap", side=None, start_time=None, scenario=None, scenario_params=None, )Submit a liquidity-risk grid: market impact across a ladder of order sizes sharing one baseline.Pro

Parameters

symbol, cal_date, provider, exchange : str
A calibrated symbol-day, as on run.
order_sizes : list[int]
Sizes in lots, one algo each. Negative buys, positive sells; empty or 0 is a 422.
n_runs : int, default 50
Runs per size and for the shared baseline.
seed : int, default 42
Master seed.
horizon_mins : int, default 60
Execution horizon in minutes.
strategy : str, default "vwap"
vwap or twap.
side : str, optional
buy or sell; overrides each size's sign.
start_time : str, optional
HH:MM; defaults to market open.
scenario : str, optional
As on run; server default normal.
scenario_params : dict, optional
As on run.

Returns

dict

job_id : str
Poll through the ordinary job methods.
order_sizes : list[int]
The ladder as submitted.
n_runs : int
Runs per arm.
total_jobs : int
Simulations queued: n_runs × (1 + len(order_sizes)). No sim_ids are returned.

Raises

PulseAPIError
403 below pro; 422 bad order_sizes, strategy, side, provider/exchange, scenario_params or an uncalibrated day; 503 simulation service.

See also

Examples

python
job = client.simulation.run_lrm(
    symbol="700.HK",
    cal_date="2025-09-02",
    provider="omd",
    exchange="hkex_securities",
    order_sizes=[-10, -50, -100],
    n_runs=10,
)
print(job["job_id"], job["total_jobs"])
client.simulation.get_jobs(limit=100, offset=0)List your simulation jobs, newest first.Pro

Parameters

limit : int, default 100
Jobs per page, 1–500.
offset : int, default 0
Jobs to skip.

Returns

dict

total : int
Your full job count.
limit, offset : int
The page returned.
jobs : list[dict]
Each {job_id, sim_ids, created_at}.

Raises

PulseAPIError
403 below pro; 422 limit or offset out of range.

Examples

python
result = client.simulation.get_jobs(limit=5)
print(f"{result['total']} jobs")
for job in result["jobs"]:
    print(job["job_id"], len(job["sim_ids"]), job["created_at"])
client.simulation.get_job_status(job_id)Return per-simulation status for one job, on either engine.Pro

Parameters

job_id : str
From run or get_jobs.

Returns

dict

job_id : str
The job.
engine : str
abm or fm.
total_simulations : int
Runs in the job.
status_summary : dict
Count per status string.
is_complete : bool
Every run has finished, successfully or not.
has_errors : bool
Any run failed (status error or failed).
simulations : list[dict]
Each {sim_id, status, error_message, symbol_id, timestamp}.
status, message, detail : str
FM only, when the orchestrator reports them: the inference pod's own state.

Raises

PulseAPIError
404 unknown or not yours; 403 below pro.

See also

Notes

Poll on is_complete, then check has_errors; both can be True. Do not compare status strings: success is complete (older completed).

Examples

python
jid = client.simulation.get_jobs(limit=1)["jobs"][0]["job_id"]

status = client.simulation.get_job_status(jid)
print(status["status_summary"], status["is_complete"], status["has_errors"])
client.simulation.get_job_results(job_id)Return params, metrics and available files for every simulation in a job.Pro

Parameters

job_id : str
From run or get_jobs.

Returns

dict

job_id : str
The job.
total_simulations : int
Runs in the job.
completed : int
Runs finished.
simulations : list[dict]
Each {sim_id, status, available_files, params, metrics}; an unreadable one carries error instead. Test completion with status in ("complete", "completed").

Raises

PulseAPIError
404 unknown or not yours; 403 below pro.

Examples

python
jid = client.simulation.get_jobs(limit=1)["jobs"][0]["job_id"]

results = client.simulation.get_job_results(jid)
print(f"{results['completed']}/{results['total_simulations']} complete")
client.simulation.get_job_logs(job_id)Return the worker's engine log for a job, as plain text.Pro

Parameters

job_id : str
From run or get_jobs.

Returns

str

The log text. Attach it when contacting support.

Raises

PulseAPIError
404 if the job does not exist, is not yours, or wrote no log; 403 below pro.

Examples

python
from simudyne import PulseAPIError

jid = client.simulation.get_jobs(limit=1)["jobs"][0]["job_id"]
try:
    print(client.simulation.get_job_logs(jid)[:500])
except PulseAPIError as exc:
    print(f"no log for {jid}: {exc.status_code}")
client.simulation.list_sim_files(sim_id)List the files one simulation wrote.Any tier

Parameters

sim_id : str
Your own run, or any cached baseline.

Returns

dict

sim_id : str
The simulation.
files : list[str]
Filenames present.
has_sim_data, has_params, has_results, has_mid_price, has_l2_by_second, has_exec_schedule, has_schedule_by_min, has_exec_results : bool
One flag per known file.

Raises

PulseAPIError
404 if unknown, or another account's and not in the cached library.

Examples

python
sim_id = client.simulation.list_cached(symbol="700.HK")["simulations"][0]["example_sim_id"]

files = client.simulation.list_sim_files(sim_id)
print({k: v for k, v in files.items() if k.startswith("has_")})
client.simulation.get_sim_params(sim_id)Return the parameters one simulation ran with (its params.json).Any tier

Parameters

sim_id : str
Your own run, or any cached baseline.

Returns

dict

sim_id : str
The simulation.
calibration_params, scenario_params, exec_algo_params, sim_params : dict
The configuration used.

Raises

PulseAPIError
404 unknown, not yours, or not yet written.

Examples

python
sim_id = client.simulation.list_cached(symbol="700.HK", date="2025-09-02", scenario="normal")["simulations"][0]["example_sim_id"]

params = client.simulation.get_sim_params(sim_id)
print(sorted(params))
client.simulation.get_sim_metrics(sim_id)Return one simulation's summary metrics (its results.json).Any tier

Parameters

sim_id : str
Your own run, or any cached baseline.

Returns

dict

Engine summary statistics, e.g. total_orders, total_trades, cancel_rate.

Raises

PulseAPIError
404 unknown, not yours, or not finished (no results.json).

Examples

python
sim_id = client.simulation.list_cached(symbol="700.HK", date="2025-09-02", scenario="normal")["simulations"][0]["example_sim_id"]

if client.simulation.list_sim_files(sim_id)["has_results"]:
    print(client.simulation.get_sim_metrics(sim_id))
else:
    print(f"no results.json for {sim_id}")
client.simulation.get_sim_data(sim_id, filename="sim_data.parquet")Download one output file of one simulation as a polars DataFrame.Any tier

Parameters

sim_id : str
Your own run, or any cached baseline.
filename : str, default "sim_data.parquet"
One of sim_data.parquet, mid_price_by_min.parquet, l2_by_second.parquet, and for algo runs exec_schedule.parquet, schedule_by_min.parquet, exec_results.parquet.

Returns

polars.DataFrame

Schemas on Output format.

Raises

PulseAPIError
400 filename not in the list; 404 unknown sim or file not written (detail says why); 429 free-tier quota exhausted (raised at once).

See also

Examples

python
import time

# Run a baseline on a day this environment has calibrated, then read its output.
inst = client.data.calibrated_data(limit=1)["symbols"][0]
job = client.simulation.run(
    symbol=inst["symbol"],
    cal_date=inst["available_dates"][-1]["date"],
    provider=inst["provider"],
    exchange=inst["exchange"],
    n_runs=1,
)
while not (status := client.simulation.get_job_status(job["job_id"]))["is_complete"]:
    time.sleep(20)
assert not status["has_errors"], client.simulation.get_job_logs(job["job_id"])
sim_id = job["sim_ids"][0]

df = client.simulation.get_sim_data(sim_id, "mid_price_by_min.parquet")
print(df.shape)
print(df.head())
client.simulation.get_bulk_data( sim_ids, include_sim_data=True, include_mid_price=False, include_l2_by_second=False, )Download up to 100 simulations as one ZIP, one directory per simulation.Any tier

Parameters

sim_ids : list[str]
1–100 simulation ids.
include_sim_data : bool, default True
Include sim_data.parquet.
include_mid_price : bool, default False
Include mid_price_by_min.parquet.
include_l2_by_second : bool, default False
Include l2_by_second.parquet.

Returns

bytes

ZIP content.

Raises

PulseAPIError
400 empty, more than 100 ids, or no file type; 404 an unknown sim or no requested file exists; 429 free-tier quota would be exceeded.

Notes

A file that is missing or fails to read is skipped silently, with no marker in the archive. Compare the ZIP against what you asked for.

Examples

python
import io
import zipfile

cached = client.simulation.list_cached(symbol="700.HK", scenario="normal")["simulations"][0]
base = cached["example_sim_id"].rsplit(":", 1)[0]
sim_ids = [f"{base}:{i:04d}" for i in range(min(cached["n_runs"], 2))]

zip_bytes = client.simulation.get_bulk_data(
    sim_ids, include_sim_data=False, include_mid_price=True
)
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
    print(zf.namelist())
client.simulation.list_cached(symbol=None, date=None, scenario=None)List the shared library of pre-run baseline simulations, one entry per group.Any tier

Parameters

symbol : str, optional
e.g. 700.HK.
date : str, optional
YYYY-MM-DD.
scenario : str, optional
e.g. normal, flash_crash.

Returns

dict

total : int
Number of groups.
simulations : list[dict]
Each {example_sim_id, provider, exchange, symbol, date, scenario, n_runs, cal_hash, sim_hash, time_range}. example_sim_id is run 0000.

Raises

PulseAPIError
401 if the key is invalid, revoked or expired.

Notes

No provider filter; a symbol cached under two providers appears twice. Only baselines (no execution algos) are cached.

Examples

python
cached = client.simulation.list_cached(symbol="700.HK")
print(cached["total"], "groups")
for group in cached["simulations"][:5]:
    print(group["date"], group["scenario"], group["n_runs"], group["example_sim_id"])
client.simulation.get_sample_data(path="simulation_sample.zip")Download a sample ZIP for 700.HK (5 runs, sim_data and mid price). Charged against nothing.Any tier

Parameters

path : str | None, default "simulation_sample.zip"
Where to write the ZIP. None returns the bytes instead.

Returns

None | bytes

None when written to path; the ZIP bytes when path is None.

Raises

PulseAPIError
404 if no sample is published on this deployment.

Examples

python
import zipfile

client.simulation.get_sample_data("sample.zip")
with zipfile.ZipFile("sample.zip") as zf:
    print(zf.namelist()[:4])
client.simulation.list_scenarios()Return scenario names and descriptions. Local: no request, no key needed.Any tier

Returns

dict[str, str]

Scenario name to description. A copy.

Examples

python
for name, desc in client.simulation.list_scenarios().items():
    print(f"{name}: {desc}")
client.simulation.get_scenario_defaults(scenario)Return the default scenario_params for one scenario. Local: no request.Any tier

Parameters

scenario : str
e.g. flash_crash.

Returns

dict

Default parameters. Empty for normal and for an unknown name (no error).

Examples

python
print(client.simulation.get_scenario_defaults("flash_crash"))

client.validation

client.validation.run( symbol, date, provider, exchange, *, sim_ids=None, sim_files=None, statistical=None, stylised_facts=None, impact=None, volume_correlation=None, fid=None, mind=None, lob=None, resample_method=None, sessions=None, match_window=None, n_levels=10, plots=None, )Submit a validation job scoring simulated runs against a real trading day.Pro

Parameters

symbol : str
e.g. 700.HK.
date : str
The historical day, YYYY-MM-DD.
provider : str
omd or bmll.
exchange : str
Tick size is read from this instrument's day.
sim_ids : list[str] | dict, optional
Platform runs. A dict such as {"abm": [...], "fm": [...]} compares named populations.
sim_files : list | dict, optional
Your own runs: paths, (filename, bytes) pairs or DataFrames in pulse format. Grouped like sim_ids. At least one of sim_ids/sim_files; at most 25 runs in total.
statistical, stylised_facts, impact, volume_correlation, fid, mind : bool, optional
Run each area. None keeps the server default, which is on for every tier; False skips it.
lob : bool, optional
Frames are L2 snapshots; message-stream areas are skipped.
resample_method : str, optional
lob mode: last (default) or pulseflow (needs sessions).
sessions : list[list[int]], optional
[start_h, start_m, end_h, end_m] per session, for pulseflow only.
match_window : bool | [str, str], optional
Which stretch of the day is scored, matched on time of day. None/False: the whole historical day. True: the historical day is cut to the runs' own span (earliest to latest timestamp across runs), for runs shorter than the day. [start, end]: both sides cut to that interval, ends inclusive; each end is HH:MM, HH:MM:SS[.fff] or an ISO timestamp's time part, e.g. ["10:00", "10:30"]. The result's metadata.historical_window reports what was applied.
n_levels : int, default 10
Book levels measured.
plots : bool | list[str], optional
True draws every figure, a list draws those ids (e.g. statistical.radar). Written to disk by get_job.

Returns

dict

job_id : str
Starts val_. Pass to get_job_status.
status : str
pending.
message : str
Human-readable summary.

Raises

ValueError
Neither or an empty sim_ids/sim_files; more than 25 runs; n_levels < 1; bad resample_method; pulseflow without sessions or vice versa; match_window not a bool or two-item list.
PulseAPIError
403 below pro; 422 config rejected (unknown field, unreadable or reversed match_window interval); 400 malformed upload.

See also

Notes

Missing historical data, an unknown sim_id, or an interval holding no rows fail later as status: "failed", not here. Areas and figures are described on Pulse-Check.

Examples

python
import time

cached = client.simulation.list_cached(symbol="700.HK", date="2025-09-02", scenario="normal")["simulations"][0]
base = cached["example_sim_id"].rsplit(":", 1)[0]

job = client.validation.run(
    symbol="700.HK",
    date="2025-09-02",
    provider="omd",
    exchange="hkex_securities",
    sim_ids=[f"{base}:{i:04d}" for i in range(min(cached["n_runs"], 2))],
    match_window=True,
    impact=False,
    fid=False,
    mind=False,
)
while not client.validation.get_job_status(job["job_id"])["is_complete"]:
    time.sleep(10)

result = client.validation.get_job(job["job_id"])
print(result["status"], (result.get("metadata") or {}).get("historical_window"))
python
job = client.validation.run(
    symbol="700.HK",
    date="2025-09-02",
    provider="omd",
    exchange="hkex_securities",
    sim_files={"mine": ["my_sims/run_0000.parquet", "my_sims/run_0001.parquet"]},
    match_window=["10:00", "11:30"],
)
print(job["job_id"])
client.validation.get_job_status(job_id)Return a validation job's status without its result. Use this to poll.Pro

Parameters

job_id : str
From run or list_jobs.

Returns

dict

job_id : str
The job.
status : str
pending, running, completed or failed.
message : str
Progress text.
is_complete : bool
True once completed or failed.
created_at, updated_at : str
Timestamps.

Raises

PulseAPIError
404 unknown or not yours; 403 below pro.

Examples

python
vid = client.validation.list_jobs(limit=1)["jobs"][0]["job_id"]

status = client.validation.get_job_status(vid)
print(status["status"], status["is_complete"])
client.validation.get_job(job_id, plot_dir=None)Return a validation job's full result, writing any rendered figures to disk.Pro

Parameters

job_id : str
From run or list_jobs.
plot_dir : str | Path, optional
Where figures are written. Defaults to the current directory.

Returns

dict

job_id, status, symbol, date, created_at, completed_at : str
Job identity and timing.
metadata : dict
Run details, including ticksize, areas_skipped and historical_window: {mode, start, end, …row counts} with mode one of off, simulation, interval.
distances, distributions, impact_response, impact_response_error, stylised_facts, stylised_fact_verdicts, volume_correlation, fid_scores, mind_scores : dict | None
Per-area results, keyed by population when sim_ids was a dict.
errors : dict | None
Area → error for areas that failed in a completed job.
error : str | None
Why the whole job failed.
plots : list[dict] | None
Figures as {id, name, content_base64}.
plot_paths : list[str]
Added by the SDK when figures were written.

Raises

PulseAPIError
404 unknown or not yours; 403 below pro.

See also

Examples

python
vid = client.validation.list_jobs(limit=1)["jobs"][0]["job_id"]

result = client.validation.get_job(vid, plot_dir="figures")
print(result["status"], result.get("errors"))
print((result.get("metadata") or {}).get("historical_window"))
client.validation.list_jobs(limit=50, offset=0)List your validation jobs, newest first.Pro

Parameters

limit : int, default 50
Jobs per page, 1–200.
offset : int, default 0
Jobs to skip.

Returns

dict

total, limit, offset : int
Full job count and the page.
jobs : list[dict]
Each {job_id, user_id, status, params, error, created_at, updated_at, completed_at}. Symbol and date are under params ({provider, exchange, symbol, date, sim_ids, sim_groups, config, source}).

Raises

PulseAPIError
403 below pro; 422 limit or offset out of range.

Examples

python
jobs = client.validation.list_jobs(limit=5)
print(jobs["total"], "validation jobs")
for job in jobs["jobs"]:
    print(job["job_id"], job["status"], job["params"]["symbol"], job["params"]["date"])

client.fm

Running a foundation model is simulation.run(engine="fm"). This resource is the model catalogue and live sessions. Guide: Foundation models.

client.fm.models()List the foundation models active in this environment that your account can run: the public ones and any enabled for you.Pro

Returns

dict

models : list[dict]
Each with model_id, version, production_name, supported_data ({provider, exchange} list; empty means any), min_prompt_rows, model_args ({type, default, min, max, choices, description} per knob) and resources.

Raises

PulseAPIError
403 below pro; 502 model registry unreachable.

See also

Examples

python
models = client.fm.models()["models"]
print(len(models), "active models")
for m in models:
    print(m["model_id"], m["version"], m["production_name"])
client.fm.live(model_id, horizon=None, seed=42, model_args=None)Start a live streaming session. Not a job: no sim_id is minted and there is nothing to poll.Pro

Parameters

model_id : str
From models().
horizon : int, optional
Frames to generate.
seed : int, default 42
Master seed.
model_args : dict, optional
Overrides of the model's knobs.

Returns

dict

token : str
The token a live chart client presents to connect.
job_id : str
Session id.
model_id : str
The resolved model.

Raises

PulseAPIError
403 below pro; 404 unknown model_id; 400 bad model_args.

Examples

python
model_id = client.fm.models()["models"][0]["model_id"]
session = client.fm.live(model_id=model_id, horizon=600)
print(session["token"])

Administration

These four need a key with the is_admin flag; any other key gets 403. Promotion normally runs them through the pulse-fm-inference workflow.

client.fm.registry()Admin flag required. Return the latest registry row for every model, active or not.Pro

Returns

dict

{env, models}; each model has the models() fields plus active and updated_by.

Raises

PulseAPIError
403 without the admin flag.

Examples

python
for m in client.fm.registry()["models"]:
    print(m["model_id"], m["version"], m["active"])
client.fm.register( model_id, version, image, *, resources=None, model_args=None, supported_data=None, min_prompt_rows=None, production_name="", active=True, )Admin flag required. Register a model image, or a new version of one. The latest row per model wins.Pro

Parameters

model_id : str
A model_id from fm.models(); models differ by environment.
version : str
Build version.
image : str
Container image to run.
resources : dict, optional
{"cpu": "2", "memory": "4Gi", "gpu": 0}.
model_args : dict, optional
Knobs the model accepts.
supported_data : list[dict], optional
{provider, exchange} markets; empty means any.
min_prompt_rows : int, optional
Context rows needed to start.
production_name : str, default ""
Name to promote it under.
active : bool, default True
Whether models() lists it.

Returns

dict

{registered, image_verified}.

Raises

PulseAPIError
403 without the admin flag; 400 or 422 a field rejected.

Examples

python
client.fm.register(
    model_id="flob-adaln-rf-1s",
    version="1.0.14",
    image="registry.example.com/flob:1.0.14",
    supported_data=[{"provider": "bmll", "exchange": "hkex_securities"}],
)
client.fm.activate(model_id)Admin flag required. List a deactivated model again.Pro

Parameters

model_id : str
Registry model_id, not the production name.

Returns

dict

{registered}: the row written.

Raises

PulseAPIError
403 without the admin flag; 404 unknown model.
client.fm.deactivate(model_id)Admin flag required. Withdraw a model from models(); history stays and activate restores it.Pro

Parameters

model_id : str
Registry model_id, not the production name.

Returns

dict

{registered}: the row written.

Raises

PulseAPIError
403 without the admin flag; 404 unknown model.

client.fix

client.fix.usage(user_id=None)Summarise the simulations started over the FIX venue: parameters only, never orders or fills.Any tier

Parameters

user_id : str, optional
Another account to read; needs the admin flag. An admin omitting it gets estate-wide totals.

Returns

dict

total_runs : int
FIX sessions recorded.
simulated_seconds : float
Total horizon across them.
by_symbol, by_scenario, by_cal_date : dict
Run counts.
runs : list[dict]
Each {symbol, cal_date, scenario, runs}, most-run first.
user_id : str | None
The account summarised.
truncated : bool
History hit the server cap.
last_run_at : str | None
Most recent start, or None.

Raises

PulseAPIError
403 if user_id names another account without the admin flag. An unknown user_id returns an empty summary, not an error.

Notes

The venue itself is raw TCP, not this SDK. See FIX.

Examples

python
usage = client.fix.usage()
print(f"{usage['total_runs']} FIX runs, {usage['simulated_seconds']:.0f} simulated seconds")

All endpoints

Every public HTTP route, its gate, and the SDK method that wraps it. User-administration routes are omitted. GET /health needs no key.

MethodPathTierSDK
GET/profileAnyprofile.get()
GET/profile/usageAnyprofile.usage()
GET/profile/downloadsAnyprofile.downloads()
POST/api-keysAnyapi_keys.create(name)
GET/api-keysAnyapi_keys.list()
DELETE/api-keys/{api_key_id}Anyapi_keys.revoke(key_id)
GET/data/available-dataProdata.available_data(…)
GET/data/calibrated-dataAnydata.calibrated_data(…)
POST/data/calibrateDemodata.calibrate(…)
GET/historical/mid-priceDemo—
GET/historical/l2-by-secondDemo—
POST/simulation/runProsimulation.run(…)
POST/simulation/lrm/runProsimulation.run_lrm(…)
GET/simulation/jobsProsimulation.get_jobs(limit, offset)
GET/simulation/jobs/{job_id}/statusProsimulation.get_job_status(job_id)
GET/simulation/jobs/{job_id}/resultsProsimulation.get_job_results(job_id)
GET/simulation/jobs/{job_id}/logsProsimulation.get_job_logs(job_id)
GET/simulation/results/{sim_id}/filesAnysimulation.list_sim_files(sim_id)
GET/simulation/results/{sim_id}/paramsAnysimulation.get_sim_params(sim_id)
GET/simulation/results/{sim_id}/metricsAnysimulation.get_sim_metrics(sim_id)
GET/simulation/results/{sim_id}/data/{filename}Anysimulation.get_sim_data(sim_id, filename)
POST/simulation/results/bulkAnysimulation.get_bulk_data(sim_ids, …)
GET/simulation/cachedAnysimulation.list_cached(…)
POST/simulation/cached/{sim_id}/downloadAny—
GET/simulation/sampleAnysimulation.get_sample_data(path)
POST/validation/runProvalidation.run(…, sim_ids=…)
POST/validation/run/uploadProvalidation.run(…, sim_files=…)
GET/validation/jobsProvalidation.list_jobs(limit, offset)
GET/validation/jobs/{job_id}Provalidation.get_job(job_id)
GET/validation/jobs/{job_id}/statusProvalidation.get_job_status(job_id)
GET/fm/modelsProfm.models()
GET/fm/compute-profilesPro—
POST/fm/liveProfm.live(…)
GET/fm/models/registryAdminfm.registry()
POST/fm/modelsAdminfm.register(…)
POST/fm/models/{model_id}/activateAdminfm.activate(model_id)
POST/fm/models/{model_id}/deactivateAdminfm.deactivate(model_id)
GET/fix/usageAnyfix.usage(user_id)
GET/healthNone—