Observe & control
Events & results
Stream execution, reconnect and inspect output, diffs and usage.
On this page
Stream a Run
Replay Run events over SSE
/v1/environments/{environmentId}/sessions/{sessionId}/runs/{runId}/eventsAuthentication: Platform API bearer token
Parameters and headers
| Field | Type and constraints | Required | Description |
|---|---|---|---|
query: after | integer · ≥ 0; ≤ 9007199254740991 | No | Last processed event ID or pagination cursor. Return only subsequent records; retain it when reconnecting or fetching another page. |
path: environmentId | string · min length 1 | Yes | Environment identifier. It must belong to the caller’s account or Project. |
path: sessionId | string · min length 1 | Yes | Existing Session identifier within the named Environment. |
path: runId | string · min length 1 | Yes | Run identifier within the named Environment and, where applicable, Session. |
header: last-event-id | stringValidation pattern^[0-9]+$ | No | Last received event ID for replay. |
Request example
curl -N \
"https://api.sandpi.ai/v1/environments/$ENVIRONMENT_ID/sessions/$SESSION_ID/runs/$RUN_ID/events" \
-H "Authorization: Bearer $SANDPI_API_TOKEN" \
-H "last-event-id: 42"No request body.
Responses
200 Successful response.Response body text/event-stream
| Field | Type and constraints | Required | Description |
|---|---|---|---|
body | string | Yes | — |
Example
id: 42
event: run.output
data: {"id":42,"type":"run.output"}
Stream event names
run.queuedrun.runningrun.nativerun.outputrun.completedrun.failedrun.stoppedrun.stop_requestedrun.revertingrun.revertedrun.file_diff_readyrun.file_diff_failedrun.checkpoint_readyrun.message.queuedrun.message.acceptedrun.message.rejectedrun.message.unconfirmedrun.native_finishedrun.runtime_unavailablerun.runtime_recoveredrun.resource_observationError responses
400 Invalid request.application/json
401 Missing or invalid Platform API token.application/json
404 Resource not found or outside token scope.application/json
default Sandpi error.Error body application/json
Standard error envelope containing a stable code, diagnostic message and request identity.
| Field | Type and constraints | Required | Description |
|---|---|---|---|
error | object · extra fields rejected | Yes | Structured failure information. Inspect the stable code and retain the request ID for investigation. |
error.code | string | Yeswithin error | Stable error code used to distinguish failures programmatically. |
error.message | string | Yeswithin error | Human-readable diagnostic text. |
error.requestId | string | Yeswithin error | Request identifier for correlating diagnostics and support investigations. |
error.details | any | Nowithin error | Optional structured error context. Its shape is specific to the service failure. |
error.loginUrl | string | Nowithin error | Authentication URL supplied when a browser login is required. |
error.registrationOpen | boolean | Nowithin error | Whether new account registration is enabled for this deployment. |
Example
{
"error": {
"code": "platform_balance_insufficient",
"message": "Top up Platform credits before submitting a Run.",
"requestId": "req_example"
}
}Run events use Server-Sent Events. Each durable event has an ID; store the last processed ID together with your application’s processing progress. Native payloads remain harness-specific. Use normalized Run state for application status, and preserve native payloads when inspecting detailed execution.
Reconnect without repeating work
Send Last-Event-ID or after with the last received ID to replay later events. The tutorial’s generated example shows the replay header; omit it for a first connection. Heartbeats keep an idle observer active, but proxies and networks can still disconnect.
Closing the stream does not stop the Run. Keep the existing Run ID and reconnect. Use the Run detail API to reconcile state independently of the stream connection.
Get Run state and Sandbox usage
/v1/environments/{environmentId}/sessions/{sessionId}/runs/{runId}Authentication: Platform API bearer token
Parameters and headers
| Field | Type and constraints | Required | Description |
|---|---|---|---|
path: environmentId | string · min length 1 | Yes | Environment identifier. It must belong to the caller’s account or Project. |
path: sessionId | string · min length 1 | Yes | Existing Session identifier within the named Environment. |
path: runId | string · min length 1 | Yes | Run identifier within the named Environment and, where applicable, Session. |
Request example
curl \
"https://api.sandpi.ai/v1/environments/$ENVIRONMENT_ID/sessions/$SESSION_ID/runs/$RUN_ID" \
-H "Authorization: Bearer $SANDPI_API_TOKEN"No request body.
Responses
200 Successful response.Response body application/json
| Field | Type and constraints | Required | Description |
|---|---|---|---|
data | object · extra fields rejected | Yes | Run details with posted Sandbox runtime and retained historical managed-model settlement; BYOK model costs are billed by your provider. |
data.id | string | Yeswithin data | Resource identifier used to read or manage this resource. |
data.environmentId | string | Yeswithin data | Environment identifier. It must belong to the caller’s account or Project. |
data.sessionId | string | Yeswithin data | Existing Session identifier within the named Environment. |
data.environmentSequence | integer · ≥ -9007199254740991; ≤ 9007199254740991 | Yeswithin data | Environment-wide Run ordering across all Sessions; rollback uses this ordering. |
data.model | string | Yeswithin data | Exact native model ID served by your provider or gateway. |
data.input | string | Yeswithin data | Instructions supplied to the agent for this task. |
data.files | array | Yeswithin data | Files associated with this resource; each item is described below. |
data.files[] | object · extra fields rejected | Yeseach array item | Reference to an existing regular workspace file checked before execution. |
data.files[].path | string | Yeseach array item | Canonical absolute path inside /workspace. Protected state, Git metadata and symlinks are not accepted file targets. |
data.files[].sha256 | stringValidation pattern^[a-f0-9]{64}$ | Noeach array item | Lowercase hexadecimal SHA-256 digest of the file bytes. |
data.status | string · values: "queued" | "running" | "completed" | "failed" | "stopped" | "reverting" | "reverted" | Yeswithin data | Current resource state; inspect the allowed values and the operation’s lifecycle notes. |
data.stopRequested | boolean | Yeswithin data | Whether a client has requested that the native task stop. |
data.output | string | Nowithin data | Final agent output when available. Content retention may erase this field. |
data.fileDiff | array | Yeswithin data | Manifest of added, modified and deleted regular workspace files. It does not contain file bytes. |
data.fileDiff[] | object · extra fields rejected | Yeseach array item | Regular workspace file change with before and after content digests. |
data.fileDiff[].path | string | Yeseach array item | Canonical absolute path inside /workspace. Protected state, Git metadata and symlinks are not accepted file targets. |
data.fileDiff[].kind | string · values: "added" | "modified" | "deleted" | Yeseach array item | Whether the Run added, modified or deleted the regular file. |
data.fileDiff[].beforeSha256 | stringValidation pattern^[a-f0-9]{64}$ | Noeach array item | Content digest before the Run; absent for a newly added file. |
data.fileDiff[].afterSha256 | stringValidation pattern^[a-f0-9]{64}$ | Noeach array item | Content digest after the Run; absent for a deleted file. |
data.fileDiffStatus | string · values: "pending" | "ready" | "failed" | Yeswithin data | File-diff computation state, separate from execution and checkpoint state. |
data.fileDiffError | string | Nowithin data | Diagnostic text when file-diff computation fails. |
data.checkpoint | object · extra fields rejected | Yeswithin data | RootFS checkpoint state and identifier, tracked separately from native execution. |
data.checkpoint.status | string · values: "pending" | "ready" | "failed" | "skipped" | Yeswithin data | Current resource state; inspect the allowed values and the operation’s lifecycle notes. |
data.checkpoint.snapshotId | string | Nowithin data | Sandbox0 checkpoint identifier. Use a returned initial or Run checkpoint when pinning a fork. |
data.checkpoint.error | string | Nowithin data | Structured failure information. Inspect the stable code and retain the request ID for investigation. |
data.createdAt | string · format: date-timeValidation pattern^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$ | Yeswithin data | Creation time as a UTC date-time string. |
data.startedAt | string · format: date-timeValidation pattern^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$ | Nowithin data | Execution start time as a UTC date-time string. |
data.finishedAt | string · format: date-timeValidation pattern^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$ | Nowithin data | Terminal execution time as a UTC date-time string. |
data.usage | object · extra fields rejected | Yeswithin data | Posted Sandbox runtime usage and retained historical managed-model settlement. New BYOK Runs have no rated model-call ledger; provider usage and cost are recorded by the customer’s provider or gateway. |
data.usage.calls | array | Yeswithin data | Historical rated managed-model calls only. Empty for new BYOK Runs; does not measure native model requests. |
data.usage.calls[] | object · extra fields rejected | Yeseach array item | — |
data.usage.calls[].id | string | Yeseach array item | Resource identifier used to read or manage this resource. |
data.usage.calls[].provider | string · values: "openai" | "anthropic" | "kimi" | "zai" | "deepseek" | Yeseach array item | Native model family associated with this Harness; not a Sandpi Token supplier. |
data.usage.calls[].model | string | Yeseach array item | Exact native model ID served by your provider or gateway. |
data.usage.calls[].priceVersion | string | Yeseach array item | Historical managed-model settlement only. Immutable version of the published price captured for rating. |
data.usage.calls[].priceSourceUrl | string · format: uri | Yeseach array item | Historical managed-model settlement only. Official provider pricing reference captured with the immutable price version. |
data.usage.calls[].usageCategories | object | Yeseach array item | Historical managed-model settlement only. Provider-reported usage by category, such as uncached input, cached input and output tokens. |
data.usage.calls[].usageCategories.{key} | number | Noadditional keys | — |
data.usage.calls[].ratedLines | array | Yeseach array item | Historical managed-model settlement only. Rated accounting line items for the provider call. |
data.usage.calls[].ratedLines[] | any | Yeseach array item | — |
data.usage.calls[].upstreamAmountUsd | string | Yeseach array item | Historical managed-model settlement only. Published base-rate usage amount in USD, represented as a decimal string. |
data.usage.calls[].serviceFeeUsd | string | Yeseach array item | Historical managed-model settlement only. Itemized service fee in USD, represented as a decimal string. |
data.usage.calls[].totalAmountUsd | string | Yeseach array item | Historical managed-model settlement only. Total rated charge in USD, represented as a decimal string. |
data.usage.calls[].createdAt | string · format: date-timeValidation pattern^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$ | Yeseach array item | Creation time as a UTC date-time string. |
data.usage.calls[].serviceFeePercent | number · values: 0 | 5 | 10 | 15 | Yeseach array item | Historical managed-model settlement only. Service fee captured by this call’s immutable price version; zero for legacy calls. |
data.usage.serviceFeePercent | number · values: 0 | 5 | 10 | 15 | null | Yeswithin data | Historical managed-model fee shared by all calls, or null for mixed historical tiers. Zero for new BYOK Runs; Sandpi adds no model service fee. |
data.usage.upstreamAmountUsd | string | Yeswithin data | Historical managed-model base charges in USD. Zero for new BYOK Runs; excludes charges billed directly by your model provider. |
data.usage.serviceFeeUsd | string | Yeswithin data | Historical managed-model service fees in USD. Zero for new BYOK Runs. |
data.usage.totalAmountUsd | string | Yeswithin data | Posted Sandbox runtime plus retained historical managed-model charges in USD. Excludes model charges billed directly by your provider or gateway. |
data.usage.sandboxAmountUsd | string | Yeswithin data | Posted Sandbox runtime charge attributed to this Run. Environment initialization and file access may incur separate Environment charges. |
data.usage.sandboxRuntimeSeconds | number · ≥ 0 | Nowithin data | Authoritative billed Sandbox time attributed to this Run. |
Example
{
"data": {
"id": "",
"environmentId": "",
"sessionId": "",
"environmentSequence": -9007199254740991,
"model": "",
"input": "",
"files": [
{
"path": ""
}
],
"status": "queued",
"stopRequested": false,
"fileDiff": [
{
"path": "",
"kind": "added"
}
],
"fileDiffStatus": "pending",
"checkpoint": {
"status": "pending"
},
"createdAt": "2026-10-12T01:00:00Z",
"usage": {
"calls": [],
"serviceFeePercent": 0,
"upstreamAmountUsd": "",
"serviceFeeUsd": "",
"totalAmountUsd": "",
"sandboxAmountUsd": ""
}
}
}Error responses
400 Invalid request.application/json
401 Missing or invalid Platform API token.application/json
404 Resource not found or outside token scope.application/json
default Sandpi error.Error body application/json
Standard error envelope containing a stable code, diagnostic message and request identity.
| Field | Type and constraints | Required | Description |
|---|---|---|---|
error | object · extra fields rejected | Yes | Structured failure information. Inspect the stable code and retain the request ID for investigation. |
error.code | string | Yeswithin error | Stable error code used to distinguish failures programmatically. |
error.message | string | Yeswithin error | Human-readable diagnostic text. |
error.requestId | string | Yeswithin error | Request identifier for correlating diagnostics and support investigations. |
error.details | any | Nowithin error | Optional structured error context. Its shape is specific to the service failure. |
error.loginUrl | string | Nowithin error | Authentication URL supplied when a browser login is required. |
error.registrationOpen | boolean | Nowithin error | Whether new account registration is enabled for this deployment. |
Example
{
"error": {
"code": "platform_balance_insufficient",
"message": "Top up Platform credits before submitting a Run.",
"requestId": "req_example"
}
}Separate completion from checkpoint readiness
A terminal execution event means the native task ended. Output can be available while the file diff, checkpoint or accounting is still settling. checkpoint.status and fileDiffStatus are separate from Run status. Watch checkpoint events or poll the Run before accessing files or forking.
Transient Sandbox0 conflicts, rate limits and service errors can leave checkpoints pending with diagnostic text. The worker retries with backoff; checkpoint recovery does not execute the model task again.
Inspect changed files
The Run diff lists added, modified and deleted regular files under /workspace with before/after SHA-256 identities. Sandpi-managed state, Git metadata and generated caches are excluded. Diff computation can fail on a large workspace while Run output and the checkpoint remain valid.
Retrieve actual file content through the file API; the diff is a manifest, not a patch download.
Interpret usage evidence
Run usage reports posted Sandbox runtime charges. The model-call and service-fee fields are retained for historical managed-model settlement; new BYOK Runs do not populate that ledger. Model token usage and cost must come from native Harness events where available and your provider or gateway records. See billing for attribution and settlement timing. Record commands, exit codes and validation results in a workspace artifact when your Controller requires a reproducible execution report.
Account for retention
Content retention can clear output and replayable content events while leaving minimal Run identity and settlement facts. Check contentDeletedAt when historical content is missing. See retention and deletion and billing.
Runtime availability
When Sandbox execution or observation times out, the Run exposes error.code = platform_runtime_unavailable and emits run.runtime_unavailable. The console displays this diagnostic even while the Run remains running: the native task may still be executing. High CPU or memory load is one possible cause; a timeout alone does not establish OOM.
Keep the same Run ID. Reconciliation retries with backoff and reconnects to the original execution. Successful runtime access clears the transient error and emits run.runtime_recovered. These events do not stop, restart or duplicate the task. Use the Environment's Settings to check Sandbox health and resource usage; release unused browser tabs or processes before retrying access.
Runtime warnings appear before any optional resource query. A background telemetry-only query has a shared two-second deadline and is limited to once per Environment every 30 seconds. If supported, run.resource_observation adds timestamped CPU and memory samples to the existing warning. The same observations appear after a TUI connection error. Normal connections issue no additional diagnostic requests.
Only fresh, continuous samples from the current runtime are shown. A single spike, missing bucket, collector reset, stale/partial response, changed generation or mismatched memory limit does not establish sustained load. “Near its allocated limit” requires every sample in a sequence spanning at least 15 seconds to be at least 95%. This describes quota utilization, not an abnormal task, an OOM event, or proof that resource use caused the timeout. Missing or slow telemetry leaves the cause unconfirmed.