API reference
Simulator endpoints
A simulator sends a heartbeat every few seconds and a log chunk whenever it has output. It can also send step timing and upload its output files before its final heartbeat. Every call is safe to retry. Bring your own simulator puts them together into a working client.
Send a heartbeat#
Records that the sim is alive and, if state differs, moves the sim to it when SIM_TRANSITIONS allows the move. A running heartbeat brings a lost sim back. A heartbeat for a sim that already finished changes nothing and returns its final state. Send one every few seconds. A sim with no heartbeat for CASCADE_HEARTBEAT_TIMEOUT_S (60 by default) becomes lost.
Path parameters#
| name | type | description |
|---|---|---|
sim_id | str | The sim's id, such as sim-6b4c91e71437. Find it in RunDetail.sims. |
Request body#
JSON, Heartbeat.
| field | type | description |
|---|---|---|
state | Literal["running", "succeeded", "failed"] | What the simulator reports. Send running while working and succeeded or failed once at the end. |
exit_code | int | None | Your process's exit code. Send it with the final heartbeat. default null |
error | str | None | A short reason, with a failed heartbeat. default null |
Returns#
200 with a HeartbeatAck. If cancel is true, stop simulating and exit.
Errors#
| status | when |
|---|---|
401 | The Authorization header is missing, is not Bearer <token>, or the token is wrong. |
404 | No sim has this id. |
422 | state is not running, succeeded or failed. |
Example#
curl -X POST "$CASCADE_URL/api/sims/sim-1047ddbdf5b1/heartbeat" \
-H "Authorization: Bearer $CASCADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"state": "running"}'import os
import httpx
headers = {"Authorization": f"Bearer {os.environ['CASCADE_TOKEN']}"}
resp = httpx.post(f"{os.environ['CASCADE_URL']}/api/sims/sim-1047ddbdf5b1/heartbeat", headers=headers, json={"state": "running"})
resp.raise_for_status()
print(resp.json())const resp = await fetch(`${CASCADE_URL}/api/sims/sim-1047ddbdf5b1/heartbeat`, {
method: "POST",
headers: { Authorization: `Bearer ${CASCADE_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({"state": "running"}),
});
console.log(await resp.json());{
"state": "succeeded",
"cancel": false
}{
"state": "cancelled",
"cancel": true
}{
"detail": "sim sim-000000000000 not found"
}Send log lines#
Appends lines to the sim's log and parses any finished Accel-Sim kernel blocks out of them. Number chunks from 0 and add one each time. The app stores the first chunk it gets for each seq and ignores repeats, so a retry after a timeout is safe. Send lines without their trailing newline.
Path parameters#
| name | type | description |
|---|---|---|
sim_id | str | The sim's id, such as sim-6b4c91e71437. Find it in RunDetail.sims. |
Request body#
JSON, LogChunk.
| field | type | description |
|---|---|---|
seq | int | Per-sim, increasing; a repeated seq is ignored. at least 0 |
lines | list[str] | Lines of simulator stdout, without trailing newlines. |
Returns#
204 with no body, whether the chunk was new or a repeat.
Errors#
| status | when |
|---|---|
401 | The Authorization header is missing, is not Bearer <token>, or the token is wrong. |
404 | No sim has this id. |
422 | seq is negative or lines is not a list of strings. |
Example#
curl -X POST "$CASCADE_URL/api/sims/sim-1047ddbdf5b1/logs" \
-H "Authorization: Bearer $CASCADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"seq": 0, "lines": ["replayed line"]}'import os
import httpx
headers = {"Authorization": f"Bearer {os.environ['CASCADE_TOKEN']}"}
resp = httpx.post(f"{os.environ['CASCADE_URL']}/api/sims/sim-1047ddbdf5b1/logs", headers=headers, json={"seq": 0, "lines": ["replayed line"]})
resp.raise_for_status()const resp = await fetch(`${CASCADE_URL}/api/sims/sim-1047ddbdf5b1/logs`, {
method: "POST",
headers: { Authorization: `Bearer ${CASCADE_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({"seq": 0, "lines": ["replayed line"]}),
});(empty body)Send step timing#
Stores the output of cascade simulate and cascade compare for this sim. cascade worker sends it after a successful sim on a trace with step timing, before its final heartbeat. Sending again replaces the earlier upload, so a retry is safe. The app checks only that simulated has a steps list and a summary object and stores both objects as they are.
Path parameters#
| name | type | description |
|---|---|---|
sim_id | str | The sim's id, such as sim-6b4c91e71437. Find it in RunDetail.sims. |
Request body#
JSON, StepTimelineUpload.
| field | type | description |
|---|---|---|
simulated | dict | `cascade simulate` output (simulated_timeline.json), with steps and summary. |
compare | dict | None | `cascade compare` output (compare.json) against the real timeline. |
Returns#
204 with no body.
Errors#
| status | when |
|---|---|
401 | The Authorization header is missing, is not Bearer <token>, or the token is wrong. |
404 | No sim has this id. |
422 | simulated lacks a steps list or a summary object, or compare is neither an object nor null. |
Example#
curl -X POST "$CASCADE_URL/api/sims/sim-57c51b464fc9/step-timeline" \
-H "Authorization: Bearer $CASCADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"simulated": {"meta": {"run_name": "empty"}, "steps": [], "requests": [], "summary": {"simulation": {"host_model": "scaled-gaps", "host_fit": {}, "coverage": {"kernels": 0, "simulated_kernels": 0, "kernel_fraction": 0, "gpu_time_fraction": 0, "measured_gpu_ns": 0.0, "covered_measured_gpu_ns": 0.0, "unmodeled_gpu_ns": 0.0, "steps": []}, "request_assumption": "one request per repeat; queue + prefill/decode walls"}, "per_kind": {}}}, "compare": {"steps": [], "requests": [], "summary": {"wall_ns": {"count": 0, "mean_error": null, "mean_abs_error": null, "p50_abs_error": null, "p95_abs_error": null, "mean_error_pct": null, "mean_abs_error_pct": null, "p50_abs_error_pct": null, "p95_abs_error_pct": null}, "gpu_busy_ns": {"count": 0, "mean_error": null, "mean_abs_error": null, "p50_abs_error": null, "p95_abs_error": null, "mean_error_pct": null, "mean_abs_error_pct": null, "p50_abs_error_pct": null, "p95_abs_error_pct": null}}}}'import os
import httpx
headers = {"Authorization": f"Bearer {os.environ['CASCADE_TOKEN']}"}
resp = httpx.post(f"{os.environ['CASCADE_URL']}/api/sims/sim-57c51b464fc9/step-timeline", headers=headers, json={"simulated": {"meta": {"run_name": "empty"}, "steps": [], "requests": [], "summary": {"simulation": {"host_model": "scaled-gaps", "host_fit": {}, "coverage": {"kernels": 0, "simulated_kernels": 0, "kernel_fraction": 0, "gpu_time_fraction": 0, "measured_gpu_ns": 0.0, "covered_measured_gpu_ns": 0.0, "unmodeled_gpu_ns": 0.0, "steps": []}, "request_assumption": "one request per repeat; queue + prefill/decode walls"}, "per_kind": {}}}, "compare": {"steps": [], "requests": [], "summary": {"wall_ns": {"count": 0, "mean_error": None, "mean_abs_error": None, "p50_abs_error": None, "p95_abs_error": None, "mean_error_pct": None, "mean_abs_error_pct": None, "p50_abs_error_pct": None, "p95_abs_error_pct": None}, "gpu_busy_ns": {"count": 0, "mean_error": None, "mean_abs_error": None, "p50_abs_error": None, "p95_abs_error": None, "mean_error_pct": None, "mean_abs_error_pct": None, "p50_abs_error_pct": None, "p95_abs_error_pct": None}}}})
resp.raise_for_status()const resp = await fetch(`${CASCADE_URL}/api/sims/sim-57c51b464fc9/step-timeline`, {
method: "POST",
headers: { Authorization: `Bearer ${CASCADE_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({"simulated": {"meta": {"run_name": "empty"}, "steps": [], "requests": [], "summary": {"simulation": {"host_model": "scaled-gaps", "host_fit": {}, "coverage": {"kernels": 0, "simulated_kernels": 0, "kernel_fraction": 0, "gpu_time_fraction": 0, "measured_gpu_ns": 0.0, "covered_measured_gpu_ns": 0.0, "unmodeled_gpu_ns": 0.0, "steps": []}, "request_assumption": "one request per repeat; queue + prefill/decode walls"}, "per_kind": {}}}, "compare": {"steps": [], "requests": [], "summary": {"wall_ns": {"count": 0, "mean_error": null, "mean_abs_error": null, "p50_abs_error": null, "p95_abs_error": null, "mean_error_pct": null, "mean_abs_error_pct": null, "p50_abs_error_pct": null, "p95_abs_error_pct": null}, "gpu_busy_ns": {"count": 0, "mean_error": null, "mean_abs_error": null, "p50_abs_error": null, "p95_abs_error": null, "mean_error_pct": null, "mean_abs_error_pct": null, "p50_abs_error_pct": null, "p95_abs_error_pct": null}}}}),
});(empty body)Upload an output file#
Stores the request body as the sim's file name, in $CASCADE_DATA_DIR/files/<sim_id>/. Putting the same name again replaces the file, so a retry is safe. cascade worker uploads every file the simulator wrote, then its own log as accelsim.log.gz, before its final heartbeat.
Path parameters#
| name | type | description |
|---|---|---|
sim_id | str | The sim's id, such as sim-6b4c91e71437. Find it in RunDetail.sims. |
name | str | The file's name, such as kernel-stats.csv. Letters, digits, ., _ and -, 1 to 128 characters, and not . or ... |
Request body#
The file's raw bytes, sent as Content-Type: application/octet-stream. Not JSON and not a form.
Returns#
200 with the stored SimFile.
Errors#
| status | when |
|---|---|
401 | The Authorization header is missing, is not Bearer <token>, or the token is wrong. |
404 | No sim has this id. |
413 | The file is over 64 MiB. |
413 | The sim's files would pass 256 MiB in all. The file a put replaces doesn't count. |
422 | name has a character other than letters, digits, ., _ and -, is over 128 characters, or is . or ... |
Example#
curl -X PUT "$CASCADE_URL/api/sims/sim-57c51b464fc9/files/kernel-stats.csv" \
-H "Authorization: Bearer $CASCADE_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @kernel-stats.csvimport os
import httpx
headers = {"Authorization": f"Bearer {os.environ['CASCADE_TOKEN']}", "Content-Type": "application/octet-stream"}
resp = httpx.put(f"{os.environ['CASCADE_URL']}/api/sims/sim-57c51b464fc9/files/kernel-stats.csv", headers=headers, content=open("kernel-stats.csv", "rb").read())
resp.raise_for_status()
print(resp.json())import { readFile } from "node:fs/promises";
const resp = await fetch(`${CASCADE_URL}/api/sims/sim-57c51b464fc9/files/kernel-stats.csv`, {
method: "PUT",
headers: { Authorization: `Bearer ${CASCADE_TOKEN}`, "Content-Type": "application/octet-stream" },
body: await readFile("kernel-stats.csv"),
});
console.log(await resp.json());{
"name": "kernel-stats.csv",
"size": 3829,
"content_type": "text/csv",
"created_at": "2026-11-08T08:59:06.686299Z"
}{
"detail": "file is over the 64 MiB per-file limit"
}{
"detail": "file name 'my file.txt' must match ^(?!\\.\\.?$)[A-Za-z0-9._-]{1,128}$"
}