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.
curl -H "X-API-Key: $SIMUDYNE_API_KEY" \
https://pulse-api.simudyne.com/profilePulseABM(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 theSIMUDYNE_API_KEYenvironment variable. - base_url : str, optional
- API root, no trailing slash. Falls back to
SIMUDYNE_BASE_URL, thenhttps://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_KEYis unset.
Examples
from simudyne import PulseABM
client = PulseABM(api_key="pk_live_...", timeout=60)
print(client.profile.get()["tier"])Tier gates
| Gate | Who passes |
|---|---|
| Any | Any active key: free, pro or demo |
| Pro | Pro or demo tier. A free key gets 403 |
| Demo | Demo tier only |
| Admin | Keys 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.
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.
{provider}:{exchange}:{symbol}:{cal_date}:{gen_method}:{cal_hash}:{time_range}:{sim_hash}:{scenario}:{algo}:{run}client.profile
Methods on this page
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,proordemo.- 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
- profile.usage — Requests this month.
- profile.downloads — Download allowance.
Examples
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
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_keyanddownloaded_at.
Raises
- PulseAPIError
- 401 if the key is invalid, revoked or expired.
See also
- simulation.get_sim_data — Download one file.
- simulation.get_bulk_data — Download many runs.
Notes
The unit is a simulation group, and membership is permanent: a group downloaded once is free to re-fetch. See Account.
Examples
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
Methods on this page
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
listshows 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
- api_keys.revoke — Deactivate a key.
Examples
new_key = client.api_keys.create(name="Training script")
print(new_key["api_key"]) # save it now; it is never shown againclient.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
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
new_key = client.api_keys.create(name="Temporary")
client.api_keys.revoke(key_id=new_key["api_key_id"])client.data
Methods on this page
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_nameworks too. - symbol : str, optional
- Exact symbol, e.g.
700. - q : str, optional
- Case-insensitive substring over the symbol.
- provider : str, optional
omdorbmll.- 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_datesandcalibrated_countper 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}, pluscalibrated_datesandcalibrated_countwithinclude_calibration_state.
Raises
- PulseAPIError
- 403 below pro; 404 unknown
model_id; 400 ambiguousmodel_id; 422 baddate,limitoroffset; 502 model registry unreachable.
See also
- data.calibrated_data — The calibrated subset.
- fm.models — Models to filter by.
Examples
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
omdorbmll.- date : str, optional
YYYY-MM-DD. Also narrows eachavailable_datesto 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_tradedandavailable_dates: a list of{date, status, stage, number_of_msgs, volume_traded, reference_price}.
Raises
- PulseAPIError
- 422 if
limitoroffsetis out of range.
See also
- data.available_data — Everything that exists.
- simulation.run — Simulate a calibrated day.
Notes
Field reference on Available symbols.
Examples
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
omdorbmll.- 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
providerorexchange; 503 calibration service unreachable. Missing raw data is not detected here: the job fails later and the day never appears.
See also
- data.calibrated_data — Where the day lands.
Examples
job = client.data.calibrate(
symbol="700.HK",
cal_date="2025-09-02",
provider="omd",
exchange="hkex_securities",
)
print(job["status"]) # calibration_queuedclient.simulation
Methods on this page
- simulation.runSubmit Monte Carlo runs on the ABM or a foundation model.Pro
- simulation.run_lrmSubmit a liquidity-risk grid across order sizes.Pro
- simulation.get_jobsYour jobs, newest first.Pro
- simulation.get_job_statusPer-simulation status for one job.Pro
- simulation.get_job_resultsParams, metrics and files for every run in a job.Pro
- simulation.get_job_logsThe worker's engine log for a job.Pro
- simulation.list_sim_filesWhich files one simulation wrote.Any tier
- simulation.get_sim_paramsThe parameters one simulation ran with.Any tier
- simulation.get_sim_metricsOne simulation's summary metrics.Any tier
- simulation.get_sim_dataOne output file as a polars DataFrame.Any tier
- simulation.get_bulk_dataUp to 100 simulations as one ZIP.Any tier
- simulation.list_cachedThe shared library of pre-run baselines.Any tier
- simulation.get_sample_dataA sample ZIP for 700.HK, no quota charged.Any tier
- simulation.list_scenariosScenario names and descriptions (local).Any tier
- simulation.get_scenario_defaultsDefault parameters for one scenario (local).Any tier
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 ""
omdorbmll.- exchange : str, default ""
- e.g.
hkex_securities,lse. - engine : str, default "abm"
abmorfm.- 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 oflist_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), optionalstart_time. CSS:type="css",orders. FM: at most one. - model_id : str, default ""
- FM only, required for FM. A
model_idorproduction_namefromfm.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_minutesis unset. - device : str, default ""
- FM only. A compute profile:
cpu,t4,l4,a100, orgpufor the default GPU; blank uses the model default.GET /fm/compute-profileslists 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); badprovider/exchange;n_runs< 1 or FM > 8; badscenario_paramsorexec_algos(start_timemust beHH:MM[:SS],sidebuy|sell). 400/404 ambiguous or unknownmodel_id. 502 model registry, 503 simulation service.
See also
- simulation.get_job_status — Poll the job.
- simulation.get_job_results — Read results.
- data.calibrated_data — Days the ABM can run.
- fm.models — Models and their arguments.
Notes
Guides: Simulations and Foundation models.
Examples
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"])# 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"
vwaportwap.- side : str, optional
buyorsell; overrides each size's sign.- start_time : str, optional
HH:MM; defaults to market open.- scenario : str, optional
- As on
run; server defaultnormal. - 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_paramsor an uncalibrated day; 503 simulation service.
See also
- simulation.get_job_status — Poll the job; sim_ids appear here.
Examples
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
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
runorget_jobs.
Returns
dict
- job_id : str
- The job.
- engine : str
abmorfm.- 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
errororfailed). - 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
- simulation.get_job_logs — Why a run failed.
Notes
Poll on is_complete, then check has_errors; both can be True. Do not compare status strings: success is complete (older completed).
Examples
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
runorget_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 carrieserrorinstead. Test completion withstatus in ("complete", "completed").
Raises
- PulseAPIError
- 404 unknown or not yours; 403 below pro.
Examples
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
runorget_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
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
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
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
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 runsexec_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
- simulation.list_sim_files — Which files exist.
- profile.downloads — Free-tier quota.
Examples
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
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_idis 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
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
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
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
print(client.simulation.get_scenario_defaults("flash_crash"))client.validation
Methods on this page
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
omdorbmll.- 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) orpulseflow(needssessions). - sessions : list[list[int]], optional
[start_h, start_m, end_h, end_m]per session, forpulseflowonly.- 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 isHH:MM,HH:MM:SS[.fff]or an ISO timestamp's time part, e.g.["10:00", "10:30"]. The result'smetadata.historical_windowreports 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 byget_job.
Returns
dict
- job_id : str
- Starts
val_. Pass toget_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; badresample_method;pulseflowwithoutsessionsor vice versa;match_windownot a bool or two-item list. - PulseAPIError
- 403 below pro; 422 config rejected (unknown field, unreadable or reversed
match_windowinterval); 400 malformed upload.
See also
- validation.get_job_status — Poll the job.
- validation.get_job — Read the result.
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
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"))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
runorlist_jobs.
Returns
dict
- job_id : str
- The job.
- status : str
pending,running,completedorfailed.- 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
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
runorlist_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_skippedandhistorical_window:{mode, start, end, …row counts}withmodeone ofoff,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
- validation.get_job_status — Poll until is_complete first.
Examples
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 underparams({provider, exchange, symbol, date, sim_ids, sim_groups, config, source}).
Raises
- PulseAPIError
- 403 below pro; 422 limit or offset out of range.
Examples
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.
Methods on this page
- fm.modelsActive models and what each accepts.Pro
- fm.liveStart a live streaming session.Pro
- fm.registryAdmin flag required. Every registered model, active or not.Pro
- fm.registerAdmin flag required. Register a model image.Pro
- fm.activateAdmin flag required. List a deactivated model again.Pro
- fm.deactivateAdmin flag required. Withdraw a model.Pro
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) andresources.
Raises
- PulseAPIError
- 403 below pro; 502 model registry unreachable.
See also
- data.available_data — Days a model accepts, via model_id.
Examples
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 badmodel_args.
Examples
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
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_idfromfm.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
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
Methods on this page
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
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.
| Method | Path | Tier | SDK |
|---|---|---|---|
| GET | /profile | Any | profile.get() |
| GET | /profile/usage | Any | profile.usage() |
| GET | /profile/downloads | Any | profile.downloads() |
| POST | /api-keys | Any | api_keys.create(name) |
| GET | /api-keys | Any | api_keys.list() |
| DELETE | /api-keys/{api_key_id} | Any | api_keys.revoke(key_id) |
| GET | /data/available-data | Pro | data.available_data(…) |
| GET | /data/calibrated-data | Any | data.calibrated_data(…) |
| POST | /data/calibrate | Demo | data.calibrate(…) |
| GET | /historical/mid-price | Demo | — |
| GET | /historical/l2-by-second | Demo | — |
| POST | /simulation/run | Pro | simulation.run(…) |
| POST | /simulation/lrm/run | Pro | simulation.run_lrm(…) |
| GET | /simulation/jobs | Pro | simulation.get_jobs(limit, offset) |
| GET | /simulation/jobs/{job_id}/status | Pro | simulation.get_job_status(job_id) |
| GET | /simulation/jobs/{job_id}/results | Pro | simulation.get_job_results(job_id) |
| GET | /simulation/jobs/{job_id}/logs | Pro | simulation.get_job_logs(job_id) |
| GET | /simulation/results/{sim_id}/files | Any | simulation.list_sim_files(sim_id) |
| GET | /simulation/results/{sim_id}/params | Any | simulation.get_sim_params(sim_id) |
| GET | /simulation/results/{sim_id}/metrics | Any | simulation.get_sim_metrics(sim_id) |
| GET | /simulation/results/{sim_id}/data/{filename} | Any | simulation.get_sim_data(sim_id, filename) |
| POST | /simulation/results/bulk | Any | simulation.get_bulk_data(sim_ids, …) |
| GET | /simulation/cached | Any | simulation.list_cached(…) |
| POST | /simulation/cached/{sim_id}/download | Any | — |
| GET | /simulation/sample | Any | simulation.get_sample_data(path) |
| POST | /validation/run | Pro | validation.run(…, sim_ids=…) |
| POST | /validation/run/upload | Pro | validation.run(…, sim_files=…) |
| GET | /validation/jobs | Pro | validation.list_jobs(limit, offset) |
| GET | /validation/jobs/{job_id} | Pro | validation.get_job(job_id) |
| GET | /validation/jobs/{job_id}/status | Pro | validation.get_job_status(job_id) |
| GET | /fm/models | Pro | fm.models() |
| GET | /fm/compute-profiles | Pro | — |
| POST | /fm/live | Pro | fm.live(…) |
| GET | /fm/models/registry | Admin | fm.registry() |
| POST | /fm/models | Admin | fm.register(…) |
| POST | /fm/models/{model_id}/activate | Admin | fm.activate(model_id) |
| POST | /fm/models/{model_id}/deactivate | Admin | fm.deactivate(model_id) |
| GET | /fix/usage | Any | fix.usage(user_id) |
| GET | /health | None | — |