sandpi/ platform

Get started

Quick start

Create a workspace and complete your first Run.

On this page

Before you start

Open the Platform console, bind a card without payment to claim your $5 new-account Promotion Credits (valid until 30 days after registration) or add prepaid credit, and create a personal API key or a key for your Team Project. Copy the key when it is shown; it cannot be viewed again. These commands use curl.

sh
export SANDPI_API_TOKEN="your-platform-token"

Commands below come from the API contract. Replace placeholders such as YOUR_SUPPORTED_MODEL_ID and YOUR_SESSION_ID with your own values. Path variables use the returned resource IDs.

Choose a model and harness

Choose a native Harness and prepare your model API base URL and API key. Your gateway must support that Harness's native API. See model connections for the protocol mapping. Model charges go to your provider; Sandpi bills actual Sandbox runtime by the second, at a rate equivalent to $0.10/hour for a fixed 2 vCPU / 4 GiB Sandbox.

Create a persistent Environment

Create a managed Environment

POST /v1/environments

Optional networkPolicy is stored before Sandbox provisioning and repository initialization. If omitted, outbound access defaults to allow-all with no domain exceptions. Allow required repository and dependency hosts when initializing a restricted Environment; model connection URLs never automatically grant outbound access.

Authentication: Platform API bearer token

Request example

sh
curl -X POST \
    "https://api.sandpi.ai/v1/environments" \
    -H "Authorization: Bearer $SANDPI_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
  "name": "PR review workspace",
  "labels": {
    "repo": "sandpi",
    "purpose": "review"
  }
}'
Request body · required application/json
FieldType and constraintsRequiredDescription
namestring · min length 1; max length 100
Validation pattern.*\S.*
YesHuman-readable name for this resource.
repositoryobjectNoPublic HTTPS Git repository used to initialize the workspace.
repository.urlstring · format: uri; max length 2048
Validation pattern^https:\/\/.*
Yeswithin repositoryPublic HTTPS Git URL without credentials, a port, query, fragment, or private host.
repository.refstring
Validation pattern^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$
Nowithin repositoryOptional repository branch or tag to clone during initialization.
projectIdstring · min length 1NoOptional Project scope; must match the API token Project.
labelsobject · max 32 keysNoUser-defined labels for exact-match filtering. Up to 32 entries. Keys are 1–64 ASCII letters, digits, dots, underscores, slashes or hyphens, beginning with a letter or digit; constructor and prototype are reserved. Values are case-sensitive strings of up to 256 characters, including empty strings.
labels.{key}string · max length 256Noadditional keys—
networkPolicyobject · extra fields rejectedNoEnvironment-wide outbound policy, enforced by Sandbox0. Domain exceptions allow destinations in block-all mode and deny destinations in allow-all mode. Enabled egress credentials also allow their destinations in block-all mode. This policy does not restrict HTTP methods, paths, or local commands.
networkPolicy.modestring · values: "allow-all" | "block-all"Yeswithin networkPolicyDefault for outbound traffic not matched by a domain exception or enabled credential destination.
networkPolicy.domainExceptionsarray · max 500 itemsYeswithin networkPolicyDomains allowed in block-all mode or denied in allow-all mode. Exact names do not automatically include subdomains.
networkPolicy.domainExceptions[]string · min length 1; max length 255Yeseach array itemExact domain or wildcard suffix such as *.example.com. URLs, paths, ports and IP literals are not supported. Names are normalized to lowercase and deduplicated.
Example
Example
{
  "name": "PR review workspace",
  "labels": {
    "repo": "sandpi",
    "purpose": "review"
  }
}

Responses

201 Created.
Response body application/json
FieldType and constraintsRequiredDescription
dataobjectYesRun completion settles the file diff and RootFS checkpoint, then immediately requests a memory pause when no Run, rollback, or checkpoint remains. File and other runtime access uses a one-minute inactivity fallback. Queued Runs continue without an intervening pause. The interactive TUI retains its separate fifteen-minute idle policy. On access after a confirmed memory restoration failure, Sandpi automatically requests a durable cold restart from committed files for a qualified stopped source. Processes restart and memory or uncommitted writes may be lost. Pending or uncertain restores retain their original operation.
data.idstringYeswithin dataResource identifier used to read or manage this resource.
data.namestringYeswithin dataHuman-readable name for this resource.
data.statusstring · values: "ready" | "updating" | "error"Yeswithin dataCurrent resource state; inspect the allowed values and the operation’s lifecycle notes.
data.platformobject · extra fields rejectedYeswithin dataPlatform checkpoint, workspace revision and initialization metadata.
data.platform.initialCheckpointStatusstring · values: "pending" | "ready" | "failed"Yeswithin dataInitialization checkpoint state. Wait for ready before submitting work.
data.platform.initialCheckpointSnapshotIdstringNowithin dataInitial immutable RootFS checkpoint identifier, present after capture succeeds.
data.platform.initialCheckpointErrorstringNowithin dataDiagnostic text for initialization checkpoint failure.
data.platform.workspaceRevisioninteger · ≥ -9007199254740991; ≤ 9007199254740991Yeswithin dataObserved workspace revision used to fence conflicting changes and fork operations.
data.platform.forkedFromobject · extra fields rejectedNowithin dataSource Environment and selected fork boundary; present for forked workspaces.
data.platform.forkedFrom.environmentIdstringYeswithin dataEnvironment identifier. It must belong to the caller’s account or Project.
data.platform.forkedFrom.modestring · values: "current" | "checkpoint"Yeswithin dataWhether the fork copied current workspace files or a selected immutable checkpoint.
data.platform.forkedFrom.snapshotIdstringNowithin dataSandbox0 checkpoint identifier. Use a returned initial or Run checkpoint when pinning a fork.
data.platform.forkedFrom.revisioninteger · ≥ -9007199254740991; ≤ 9007199254740991Yeswithin dataSource Environment Run revision observed when the fork was created.
data.platform.repositoryobject · extra fields rejectedNowithin dataPublic HTTPS Git repository used to initialize the workspace.
data.platform.repository.urlstring · format: uriYeswithin dataPublic HTTPS Git URL used to initialize the workspace.
data.platform.repository.refstringNowithin dataOptional repository branch or tag to clone during initialization.
data.platform.repository.initializedbooleanYeswithin dataWhether repository initialization has finished.
data.labelsobject · max 32 keysYeswithin dataUser-defined labels for exact-match filtering. Up to 32 entries. Keys are 1–64 ASCII letters, digits, dots, underscores, slashes or hyphens, beginning with a letter or digit; constructor and prototype are reserved. Values are case-sensitive strings of up to 256 characters, including empty strings.
data.labels.{key}string · max length 256Noadditional keys—
data.labelsRevisioninteger · ≥ 0; ≤ 2147483647Yeswithin dataIndependent labels revision. Supply the last read revision when replacing labels; a stale revision returns 409.
data.networkPolicyobject · extra fields rejectedYeswithin dataEnvironment-wide outbound policy, enforced by Sandbox0. Domain exceptions allow destinations in block-all mode and deny destinations in allow-all mode. Enabled egress credentials also allow their destinations in block-all mode. This policy does not restrict HTTP methods, paths, or local commands.
data.networkPolicy.modestring · values: "allow-all" | "block-all"Yeswithin dataDefault for outbound traffic not matched by a domain exception or enabled credential destination.
data.networkPolicy.domainExceptionsarray · max 500 itemsYeswithin dataDomains allowed in block-all mode or denied in allow-all mode. Exact names do not automatically include subdomains.
data.networkPolicy.domainExceptions[]string · min length 1; max length 255Yeseach array itemExact domain or wildcard suffix such as *.example.com. URLs, paths, ports and IP literals are not supported. Names are normalized to lowercase and deduplicated.
data.{key}anyNoadditional keys—
Example
Example
{
  "data": {
    "id": "",
    "name": "",
    "status": "ready",
    "platform": {
      "initialCheckpointStatus": "pending",
      "workspaceRevision": -9007199254740991
    },
    "labels": {},
    "labelsRevision": 0,
    "networkPolicy": {
      "mode": "allow-all",
      "domainExceptions": [
        "example"
      ]
    }
  }
}
Error responses
400 Invalid request.

application/json

401 Missing or invalid Platform API token.

application/json

402 Insufficient Platform credit.

application/json

403 Token cannot create in the requested Project.

application/json

415 Unsupported request media type.

application/json

503 Platform execution is unavailable.

application/json

default Sandpi error.
Error body application/json

Standard error envelope containing a stable code, diagnostic message and request identity.

FieldType and constraintsRequiredDescription
errorobject · extra fields rejectedYesStructured failure information. Inspect the stable code and retain the request ID for investigation.
error.codestringYeswithin errorStable error code used to distinguish failures programmatically.
error.messagestringYeswithin errorHuman-readable diagnostic text.
error.requestIdstringYeswithin errorRequest identifier for correlating diagnostics and support investigations.
error.detailsanyNowithin errorOptional structured error context. Its shape is specific to the service failure.
error.loginUrlstringNowithin errorAuthentication URL supplied when a browser login is required.
error.registrationOpenbooleanNowithin errorWhether new account registration is enabled for this deployment.
Example
Example
{
  "error": {
    "code": "platform_balance_insufficient",
    "message": "Top up Platform credits before submitting a Run.",
    "requestId": "req_example"
  }
}

The JSON result is wrapped in data. Save data.id as ENVIRONMENT_ID. Poll the Environment until its status and platform.initialCheckpointStatus are both ready. A successful creation response can precede initialization completion.

sh
export ENVIRONMENT_ID="returned-environment-id"

Get a managed Environment

GET /v1/environments/{environmentId}

Authentication: Platform API bearer token

Parameters and headers
FieldType and constraintsRequiredDescription
path: environmentIdstring · min length 1YesEnvironment identifier. It must belong to the caller’s account or Project.

Request example

sh
curl \
    "https://api.sandpi.ai/v1/environments/$ENVIRONMENT_ID" \
    -H "Authorization: Bearer $SANDPI_API_TOKEN"

No request body.

Responses

200 Successful response.
Response body application/json
FieldType and constraintsRequiredDescription
dataobjectYesRun completion settles the file diff and RootFS checkpoint, then immediately requests a memory pause when no Run, rollback, or checkpoint remains. File and other runtime access uses a one-minute inactivity fallback. Queued Runs continue without an intervening pause. The interactive TUI retains its separate fifteen-minute idle policy. On access after a confirmed memory restoration failure, Sandpi automatically requests a durable cold restart from committed files for a qualified stopped source. Processes restart and memory or uncommitted writes may be lost. Pending or uncertain restores retain their original operation.
data.idstringYeswithin dataResource identifier used to read or manage this resource.
data.namestringYeswithin dataHuman-readable name for this resource.
data.statusstring · values: "ready" | "updating" | "error"Yeswithin dataCurrent resource state; inspect the allowed values and the operation’s lifecycle notes.
data.platformobject · extra fields rejectedYeswithin dataPlatform checkpoint, workspace revision and initialization metadata.
data.platform.initialCheckpointStatusstring · values: "pending" | "ready" | "failed"Yeswithin dataInitialization checkpoint state. Wait for ready before submitting work.
data.platform.initialCheckpointSnapshotIdstringNowithin dataInitial immutable RootFS checkpoint identifier, present after capture succeeds.
data.platform.initialCheckpointErrorstringNowithin dataDiagnostic text for initialization checkpoint failure.
data.platform.workspaceRevisioninteger · ≥ -9007199254740991; ≤ 9007199254740991Yeswithin dataObserved workspace revision used to fence conflicting changes and fork operations.
data.platform.forkedFromobject · extra fields rejectedNowithin dataSource Environment and selected fork boundary; present for forked workspaces.
data.platform.forkedFrom.environmentIdstringYeswithin dataEnvironment identifier. It must belong to the caller’s account or Project.
data.platform.forkedFrom.modestring · values: "current" | "checkpoint"Yeswithin dataWhether the fork copied current workspace files or a selected immutable checkpoint.
data.platform.forkedFrom.snapshotIdstringNowithin dataSandbox0 checkpoint identifier. Use a returned initial or Run checkpoint when pinning a fork.
data.platform.forkedFrom.revisioninteger · ≥ -9007199254740991; ≤ 9007199254740991Yeswithin dataSource Environment Run revision observed when the fork was created.
data.platform.repositoryobject · extra fields rejectedNowithin dataPublic HTTPS Git repository used to initialize the workspace.
data.platform.repository.urlstring · format: uriYeswithin dataPublic HTTPS Git URL used to initialize the workspace.
data.platform.repository.refstringNowithin dataOptional repository branch or tag to clone during initialization.
data.platform.repository.initializedbooleanYeswithin dataWhether repository initialization has finished.
data.labelsobject · max 32 keysYeswithin dataUser-defined labels for exact-match filtering. Up to 32 entries. Keys are 1–64 ASCII letters, digits, dots, underscores, slashes or hyphens, beginning with a letter or digit; constructor and prototype are reserved. Values are case-sensitive strings of up to 256 characters, including empty strings.
data.labels.{key}string · max length 256Noadditional keys—
data.labelsRevisioninteger · ≥ 0; ≤ 2147483647Yeswithin dataIndependent labels revision. Supply the last read revision when replacing labels; a stale revision returns 409.
data.networkPolicyobject · extra fields rejectedYeswithin dataEnvironment-wide outbound policy, enforced by Sandbox0. Domain exceptions allow destinations in block-all mode and deny destinations in allow-all mode. Enabled egress credentials also allow their destinations in block-all mode. This policy does not restrict HTTP methods, paths, or local commands.
data.networkPolicy.modestring · values: "allow-all" | "block-all"Yeswithin dataDefault for outbound traffic not matched by a domain exception or enabled credential destination.
data.networkPolicy.domainExceptionsarray · max 500 itemsYeswithin dataDomains allowed in block-all mode or denied in allow-all mode. Exact names do not automatically include subdomains.
data.networkPolicy.domainExceptions[]string · min length 1; max length 255Yeseach array itemExact domain or wildcard suffix such as *.example.com. URLs, paths, ports and IP literals are not supported. Names are normalized to lowercase and deduplicated.
data.{key}anyNoadditional keys—
Example
Example
{
  "data": {
    "id": "",
    "name": "",
    "status": "ready",
    "platform": {
      "initialCheckpointStatus": "pending",
      "workspaceRevision": -9007199254740991
    },
    "labels": {},
    "labelsRevision": 0,
    "networkPolicy": {
      "mode": "allow-all",
      "domainExceptions": [
        "example"
      ]
    }
  }
}
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.

FieldType and constraintsRequiredDescription
errorobject · extra fields rejectedYesStructured failure information. Inspect the stable code and retain the request ID for investigation.
error.codestringYeswithin errorStable error code used to distinguish failures programmatically.
error.messagestringYeswithin errorHuman-readable diagnostic text.
error.requestIdstringYeswithin errorRequest identifier for correlating diagnostics and support investigations.
error.detailsanyNowithin errorOptional structured error context. Its shape is specific to the service failure.
error.loginUrlstringNowithin errorAuthentication URL supplied when a browser login is required.
error.registrationOpenbooleanNowithin errorWhether new account registration is enabled for this deployment.
Example
Example
{
  "error": {
    "code": "platform_balance_insufficient",
    "message": "Top up Platform credits before submitting a Run.",
    "requestId": "req_example"
  }
}

Create a harness Session

The Session retains native conversation history across Runs. Supply your native connection when creating the Session. Pi additionally requires its API type.

Create a harness Session

POST /v1/environments/{environmentId}/sessions

Create a Session using a model vendor’s native Harness and your own API or gateway. Pi requires connection.api. No Sandpi Token purchase or model-price catalog is needed.

Authentication: Platform API bearer token

Parameters and headers
FieldType and constraintsRequiredDescription
path: environmentIdstring · min length 1YesEnvironment identifier. It must belong to the caller’s account or Project.

Request example

sh
curl -X POST \
    "https://api.sandpi.ai/v1/environments/$ENVIRONMENT_ID/sessions" \
    -H "Authorization: Bearer $SANDPI_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
  "harness": "codex",
  "connection": {
    "baseUrl": "https://gateway.example.com/v1",
    "apiKey": "YOUR_GATEWAY_KEY"
  }
}'
Request body · required application/json
FieldType and constraintsRequiredDescription
harnessstring · values: "codex" | "claude-code" | "pi" | "kimi-code" | "zcode"YesNative coding-agent program used by the Session.
connectionobject · extra fields rejectedYes—
connection.baseUrlstring · format: uri; max length 2048Yeswithin connectionNative model API base URL, including its required path prefix. Harness connects directly; Sandpi does not translate model traffic.
connection.apiKeystring · min length 1; max length 8192; write onlyYeswithin connectionYour provider or gateway credential. Encrypted at rest, never returned by the API.
connection.headersobject · max 32 keys; write onlyNowithin connectionAdditional native HTTP headers. Stored with the encrypted credential.
connection.headers.{key}string · max length 8192Noadditional keys—
connection.apistring · values: "openai-responses" | "openai-completions" | "anthropic-messages"Nowithin connectionPi native API adapter. Required for Pi; other Harnesses use their native API.
connection.contextWindowinteger · ≥ 1024; ≤ 10000000Nowithin connectionPi custom model context capacity; defaults to 128000.
connection.maxOutputTokensinteger · ≥ 1; ≤ 1000000Nowithin connectionPi custom model output capacity; defaults to 16384.
sandboxBudgetUsdnumber · nullable; ≥ 0; ≤ 100000NoOptional lifetime Sandbox execution budget in USD. Converted to cumulative Harness execution time; does not add a Sandbox charge. Omitted or null is unlimited.
Example
Example
{
  "harness": "codex",
  "connection": {
    "baseUrl": "https://gateway.example.com/v1",
    "apiKey": "YOUR_GATEWAY_KEY"
  }
}

Responses

201 Created.
Response body application/json
FieldType and constraintsRequiredDescription
dataobject · extra fields rejectedYesPersistent native Harness conversation. Configure your own provider or gateway; Sandpi charges only Sandbox runtime.
data.idstringYeswithin dataResource identifier used to read or manage this resource.
data.environmentIdstringYeswithin dataEnvironment identifier. It must belong to the caller’s account or Project.
data.harnessstring · values: "codex" | "claude-code" | "pi" | "kimi-code" | "zcode"Yeswithin dataNative coding-agent program used by the Session.
data.providerstring · values: "openai" | "anthropic" | "kimi" | "zai" | "deepseek"Yeswithin dataNative model family associated with this Harness; not a Sandpi Token supplier.
data.nativeSessionIdstringNowithin dataNative harness conversation identifier retained for Session resume.
data.nativeHistoryStatestring · values: "new" | "available" | "unavailable"Yeswithin dataState of the Session’s native conversation history.
data.createdAtstring · format: date-time
Validation 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 dataCreation time as a UTC date-time string.
data.connectionobject · extra fields rejectedNowithin data—
data.connection.baseUrlstring · format: uri; max length 2048Yeswithin dataNative model API base URL, including its required path prefix. Harness connects directly; Sandpi does not translate model traffic.
data.connection.apistring · values: "openai-responses" | "openai-completions" | "anthropic-messages"Nowithin dataPi native API adapter. Required for Pi; other Harnesses use their native API.
data.connection.revisioninteger · ≥ 1Yeswithin dataImmutable connection revision captured by new Runs. Rotation does not change an existing Run.
Example
Example
{
  "data": {
    "id": "",
    "environmentId": "",
    "harness": "codex",
    "provider": "openai",
    "nativeHistoryState": "new",
    "createdAt": "2026-10-12T01:00:00Z"
  }
}
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

415 Unsupported request media type.

application/json

503 The selected harness or model route is temporarily unavailable.

application/json

default Sandpi error.
Error body application/json

Standard error envelope containing a stable code, diagnostic message and request identity.

FieldType and constraintsRequiredDescription
errorobject · extra fields rejectedYesStructured failure information. Inspect the stable code and retain the request ID for investigation.
error.codestringYeswithin errorStable error code used to distinguish failures programmatically.
error.messagestringYeswithin errorHuman-readable diagnostic text.
error.requestIdstringYeswithin errorRequest identifier for correlating diagnostics and support investigations.
error.detailsanyNowithin errorOptional structured error context. Its shape is specific to the service failure.
error.loginUrlstringNowithin errorAuthentication URL supplied when a browser login is required.
error.registrationOpenbooleanNowithin errorWhether new account registration is enabled for this deployment.
Example
Example
{
  "error": {
    "code": "platform_balance_insufficient",
    "message": "Top up Platform credits before submitting a Run.",
    "requestId": "req_example"
  }
}

Save data.id as SESSION_ID. A Platform Session is a headless conversation; it is separate from an interactive TUI terminal.

sh
export SESSION_ID="returned-session-id"
export REQUEST_KEY="$(uuidgen)"

Submit your first Run

Queue an idempotent headless Run

POST /v1/environments/{environmentId}/sessions/{sessionId}/runs

Requires Idempotency-Key. Runs execute serially within an Environment.

Authentication: Platform API bearer token

Parameters and headers
FieldType and constraintsRequiredDescription
path: environmentIdstring · min length 1YesEnvironment identifier. It must belong to the caller’s account or Project.
path: sessionIdstring · min length 1YesExisting Session identifier within the named Environment.
header: idempotency-keystring · min length 1; max length 128YesStable key for one logical operation. Retry with the same key and exact request body; a new key can admit new work.

Request example

sh
curl -X POST \
    "https://api.sandpi.ai/v1/environments/$ENVIRONMENT_ID/sessions/$SESSION_ID/runs" \
    -H "Authorization: Bearer $SANDPI_API_TOKEN" \
    -H "idempotency-key: $REQUEST_KEY" \
    -H "Content-Type: application/json" \
    -d '{
  "model": "YOUR_SUPPORTED_MODEL_ID",
  "input": "Fix the login error"
}'
Request body · required application/json
FieldType and constraintsRequiredDescription
modelstring · min length 1; max length 200
Validation pattern.*\S.*
YesExact native model ID served by your provider or gateway.
inputstring · min length 1YesInstructions supplied to the agent for this task.
filesarray · max 20 itemsNoFiles associated with this resource; each item is described below.
files[]objectYeseach array item—
files[].pathstring · min length 1; max length 2048Yeseach array itemCanonical absolute path inside /workspace. Protected state, Git metadata and symlinks are not accepted file targets.
files[].sha256string
Validation pattern^[a-f0-9]{64}$
Noeach array itemLowercase hexadecimal SHA-256 digest of the file bytes.
Example
Example
{
  "model": "YOUR_SUPPORTED_MODEL_ID",
  "input": "Fix the login error"
}

Responses

202 Accepted.
Response body application/json
FieldType and constraintsRequiredDescription
dataobject · extra fields rejectedYesNative task execution, output, file diff and checkpoint state. These states settle independently.
data.idstringYeswithin dataResource identifier used to read or manage this resource.
data.environmentIdstringYeswithin dataEnvironment identifier. It must belong to the caller’s account or Project.
data.sessionIdstringYeswithin dataExisting Session identifier within the named Environment.
data.environmentSequenceinteger · ≥ -9007199254740991; ≤ 9007199254740991Yeswithin dataEnvironment-wide Run ordering across all Sessions; rollback uses this ordering.
data.modelstringYeswithin dataExact native model ID served by your provider or gateway.
data.inputstringYeswithin dataInstructions supplied to the agent for this task.
data.filesarrayYeswithin dataFiles associated with this resource; each item is described below.
data.files[]object · extra fields rejectedYeseach array itemReference to an existing regular workspace file checked before execution.
data.files[].pathstringYeseach array itemCanonical absolute path inside /workspace. Protected state, Git metadata and symlinks are not accepted file targets.
data.files[].sha256string
Validation pattern^[a-f0-9]{64}$
Noeach array itemLowercase hexadecimal SHA-256 digest of the file bytes.
data.statusstring · values: "queued" | "running" | "completed" | "failed" | "stopped" | "reverting" | "reverted"Yeswithin dataCurrent resource state; inspect the allowed values and the operation’s lifecycle notes.
data.stopRequestedbooleanYeswithin dataWhether a client has requested that the native task stop.
data.outputstringNowithin dataFinal agent output when available. Content retention may erase this field.
data.errorobject · extra fields rejectedNowithin dataExecution failure or transient runtime availability diagnostic. While status is running, platform_runtime_unavailable means observation or preparation is blocked; the original task may still be executing. Reuse this Run instead of submitting it again. The diagnostic clears after successful runtime access. Optional timestamped resource samples may be appended asynchronously; these measurements do not establish the timeout cause or OOM.
data.error.codestringNowithin dataStable code identifying an execution failure or transient runtime availability diagnostic.
data.error.messagestringNowithin dataHuman-readable failure or availability diagnostic; it does not by itself establish that the original task stopped.
data.fileDiffarrayYeswithin dataManifest of added, modified and deleted regular workspace files. It does not contain file bytes.
data.fileDiff[]object · extra fields rejectedYeseach array itemRegular workspace file change with before and after content digests.
data.fileDiff[].pathstringYeseach array itemCanonical absolute path inside /workspace. Protected state, Git metadata and symlinks are not accepted file targets.
data.fileDiff[].kindstring · values: "added" | "modified" | "deleted"Yeseach array itemWhether the Run added, modified or deleted the regular file.
data.fileDiff[].beforeSha256string
Validation pattern^[a-f0-9]{64}$
Noeach array itemContent digest before the Run; absent for a newly added file.
data.fileDiff[].afterSha256string
Validation pattern^[a-f0-9]{64}$
Noeach array itemContent digest after the Run; absent for a deleted file.
data.fileDiffStatusstring · values: "pending" | "ready" | "failed"Yeswithin dataFile-diff computation state, separate from execution and checkpoint state.
data.fileDiffErrorstringNowithin dataDiagnostic text when file-diff computation fails.
data.checkpointobject · extra fields rejectedYeswithin dataRootFS checkpoint state and identifier, tracked separately from native execution.
data.checkpoint.statusstring · values: "pending" | "ready" | "failed" | "skipped"Yeswithin dataCurrent resource state; inspect the allowed values and the operation’s lifecycle notes.
data.checkpoint.snapshotIdstringNowithin dataSandbox0 checkpoint identifier. Use a returned initial or Run checkpoint when pinning a fork.
data.checkpoint.errorstringNowithin dataStructured failure information. Inspect the stable code and retain the request ID for investigation.
data.createdAtstring · format: date-time
Validation 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 dataCreation time as a UTC date-time string.
data.startedAtstring · format: date-time
Validation 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 dataExecution start time as a UTC date-time string.
data.finishedAtstring · format: date-time
Validation 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 dataTerminal execution time as a UTC date-time string.
data.steeringEnabledbooleanYeswithin dataWhether this Run supports native steering during execution.
data.contentDeletedAtstring · format: date-timeNowithin dataPlatform content was erased; native history and checkpoints remain until Environment deletion.
Example
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",
    "steeringEnabled": false
  }
}
Error responses
400 Invalid request.

application/json

401 Missing or invalid Platform API token.

application/json

402 Insufficient Platform credit.

application/json

404 Resource not found or outside token scope.

application/json

409 Resource state conflict.

application/json

415 Unsupported request media type.

application/json

503 Platform execution is unavailable.

application/json

default Sandpi error.
Error body application/json

Standard error envelope containing a stable code, diagnostic message and request identity.

FieldType and constraintsRequiredDescription
errorobject · extra fields rejectedYesStructured failure information. Inspect the stable code and retain the request ID for investigation.
error.codestringYeswithin errorStable error code used to distinguish failures programmatically.
error.messagestringYeswithin errorHuman-readable diagnostic text.
error.requestIdstringYeswithin errorRequest identifier for correlating diagnostics and support investigations.
error.detailsanyNowithin errorOptional structured error context. Its shape is specific to the service failure.
error.loginUrlstringNowithin errorAuthentication URL supplied when a browser login is required.
error.registrationOpenbooleanNowithin errorWhether new account registration is enabled for this deployment.
Example
Example
{
  "error": {
    "code": "platform_balance_insufficient",
    "message": "Top up Platform credits before submitting a Run.",
    "requestId": "req_example"
  }
}

Save the returned data.id as RUN_ID. Keep the same request key and body if the connection fails during submission. A new key means new work. Runs in one Environment execute serially, including Runs from different Sessions.

sh
export RUN_ID="returned-run-id"

Follow execution and collect the result

Replay Run events over SSE

GET /v1/environments/{environmentId}/sessions/{sessionId}/runs/{runId}/events

Authentication: Platform API bearer token

Parameters and headers
FieldType and constraintsRequiredDescription
query: afterinteger · ≥ 0; ≤ 9007199254740991NoLast processed event ID or pagination cursor. Return only subsequent records; retain it when reconnecting or fetching another page.
path: environmentIdstring · min length 1YesEnvironment identifier. It must belong to the caller’s account or Project.
path: sessionIdstring · min length 1YesExisting Session identifier within the named Environment.
path: runIdstring · min length 1YesRun identifier within the named Environment and, where applicable, Session.
header: last-event-idstring
Validation pattern^[0-9]+$
NoLast received event ID for replay.

Request example

sh
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
FieldType and constraintsRequiredDescription
bodystringYes—
Example
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_observation
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.

FieldType and constraintsRequiredDescription
errorobject · extra fields rejectedYesStructured failure information. Inspect the stable code and retain the request ID for investigation.
error.codestringYeswithin errorStable error code used to distinguish failures programmatically.
error.messagestringYeswithin errorHuman-readable diagnostic text.
error.requestIdstringYeswithin errorRequest identifier for correlating diagnostics and support investigations.
error.detailsanyNowithin errorOptional structured error context. Its shape is specific to the service failure.
error.loginUrlstringNowithin errorAuthentication URL supplied when a browser login is required.
error.registrationOpenbooleanNowithin errorWhether new account registration is enabled for this deployment.
Example
Example
{
  "error": {
    "code": "platform_balance_insufficient",
    "message": "Top up Platform credits before submitting a Run.",
    "requestId": "req_example"
  }
}

The SSE connection can be closed without stopping the Run. Reconnect using the last event ID rather than resubmitting the task.

Get Run state and Sandbox usage

GET /v1/environments/{environmentId}/sessions/{sessionId}/runs/{runId}

Authentication: Platform API bearer token

Parameters and headers
FieldType and constraintsRequiredDescription
path: environmentIdstring · min length 1YesEnvironment identifier. It must belong to the caller’s account or Project.
path: sessionIdstring · min length 1YesExisting Session identifier within the named Environment.
path: runIdstring · min length 1YesRun identifier within the named Environment and, where applicable, Session.

Request example

sh
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
FieldType and constraintsRequiredDescription
dataobject · extra fields rejectedYesRun details with posted Sandbox runtime and retained historical managed-model settlement; BYOK model costs are billed by your provider.
data.idstringYeswithin dataResource identifier used to read or manage this resource.
data.environmentIdstringYeswithin dataEnvironment identifier. It must belong to the caller’s account or Project.
data.sessionIdstringYeswithin dataExisting Session identifier within the named Environment.
data.environmentSequenceinteger · ≥ -9007199254740991; ≤ 9007199254740991Yeswithin dataEnvironment-wide Run ordering across all Sessions; rollback uses this ordering.
data.modelstringYeswithin dataExact native model ID served by your provider or gateway.
data.inputstringYeswithin dataInstructions supplied to the agent for this task.
data.filesarrayYeswithin dataFiles associated with this resource; each item is described below.
data.files[]object · extra fields rejectedYeseach array itemReference to an existing regular workspace file checked before execution.
data.files[].pathstringYeseach array itemCanonical absolute path inside /workspace. Protected state, Git metadata and symlinks are not accepted file targets.
data.files[].sha256string
Validation pattern^[a-f0-9]{64}$
Noeach array itemLowercase hexadecimal SHA-256 digest of the file bytes.
data.statusstring · values: "queued" | "running" | "completed" | "failed" | "stopped" | "reverting" | "reverted"Yeswithin dataCurrent resource state; inspect the allowed values and the operation’s lifecycle notes.
data.stopRequestedbooleanYeswithin dataWhether a client has requested that the native task stop.
data.outputstringNowithin dataFinal agent output when available. Content retention may erase this field.
data.fileDiffarrayYeswithin dataManifest of added, modified and deleted regular workspace files. It does not contain file bytes.
data.fileDiff[]object · extra fields rejectedYeseach array itemRegular workspace file change with before and after content digests.
data.fileDiff[].pathstringYeseach array itemCanonical absolute path inside /workspace. Protected state, Git metadata and symlinks are not accepted file targets.
data.fileDiff[].kindstring · values: "added" | "modified" | "deleted"Yeseach array itemWhether the Run added, modified or deleted the regular file.
data.fileDiff[].beforeSha256string
Validation pattern^[a-f0-9]{64}$
Noeach array itemContent digest before the Run; absent for a newly added file.
data.fileDiff[].afterSha256string
Validation pattern^[a-f0-9]{64}$
Noeach array itemContent digest after the Run; absent for a deleted file.
data.fileDiffStatusstring · values: "pending" | "ready" | "failed"Yeswithin dataFile-diff computation state, separate from execution and checkpoint state.
data.fileDiffErrorstringNowithin dataDiagnostic text when file-diff computation fails.
data.checkpointobject · extra fields rejectedYeswithin dataRootFS checkpoint state and identifier, tracked separately from native execution.
data.checkpoint.statusstring · values: "pending" | "ready" | "failed" | "skipped"Yeswithin dataCurrent resource state; inspect the allowed values and the operation’s lifecycle notes.
data.checkpoint.snapshotIdstringNowithin dataSandbox0 checkpoint identifier. Use a returned initial or Run checkpoint when pinning a fork.
data.checkpoint.errorstringNowithin dataStructured failure information. Inspect the stable code and retain the request ID for investigation.
data.createdAtstring · format: date-time
Validation 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 dataCreation time as a UTC date-time string.
data.startedAtstring · format: date-time
Validation 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 dataExecution start time as a UTC date-time string.
data.finishedAtstring · format: date-time
Validation 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 dataTerminal execution time as a UTC date-time string.
data.usageobject · extra fields rejectedYeswithin dataPosted 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.callsarrayYeswithin dataHistorical rated managed-model calls only. Empty for new BYOK Runs; does not measure native model requests.
data.usage.calls[]object · extra fields rejectedYeseach array item—
data.usage.calls[].idstringYeseach array itemResource identifier used to read or manage this resource.
data.usage.calls[].providerstring · values: "openai" | "anthropic" | "kimi" | "zai" | "deepseek"Yeseach array itemNative model family associated with this Harness; not a Sandpi Token supplier.
data.usage.calls[].modelstringYeseach array itemExact native model ID served by your provider or gateway.
data.usage.calls[].priceVersionstringYeseach array itemHistorical managed-model settlement only. Immutable version of the published price captured for rating.
data.usage.calls[].priceSourceUrlstring · format: uriYeseach array itemHistorical managed-model settlement only. Official provider pricing reference captured with the immutable price version.
data.usage.calls[].usageCategoriesobjectYeseach array itemHistorical managed-model settlement only. Provider-reported usage by category, such as uncached input, cached input and output tokens.
data.usage.calls[].usageCategories.{key}numberNoadditional keys—
data.usage.calls[].ratedLinesarrayYeseach array itemHistorical managed-model settlement only. Rated accounting line items for the provider call.
data.usage.calls[].ratedLines[]anyYeseach array item—
data.usage.calls[].upstreamAmountUsdstringYeseach array itemHistorical managed-model settlement only. Published base-rate usage amount in USD, represented as a decimal string.
data.usage.calls[].serviceFeeUsdstringYeseach array itemHistorical managed-model settlement only. Itemized service fee in USD, represented as a decimal string.
data.usage.calls[].totalAmountUsdstringYeseach array itemHistorical managed-model settlement only. Total rated charge in USD, represented as a decimal string.
data.usage.calls[].createdAtstring · format: date-time
Validation 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 itemCreation time as a UTC date-time string.
data.usage.calls[].serviceFeePercentnumber · values: 0 | 5 | 10 | 15Yeseach array itemHistorical managed-model settlement only. Service fee captured by this call’s immutable price version; zero for legacy calls.
data.usage.serviceFeePercentnumber · values: 0 | 5 | 10 | 15 | nullYeswithin dataHistorical 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.upstreamAmountUsdstringYeswithin dataHistorical managed-model base charges in USD. Zero for new BYOK Runs; excludes charges billed directly by your model provider.
data.usage.serviceFeeUsdstringYeswithin dataHistorical managed-model service fees in USD. Zero for new BYOK Runs.
data.usage.totalAmountUsdstringYeswithin dataPosted Sandbox runtime plus retained historical managed-model charges in USD. Excludes model charges billed directly by your provider or gateway.
data.usage.sandboxAmountUsdstringYeswithin dataPosted Sandbox runtime charge attributed to this Run. Environment initialization and file access may incur separate Environment charges.
data.usage.sandboxRuntimeSecondsnumber · ≥ 0Nowithin dataAuthoritative billed Sandbox time attributed to this Run.
Example
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.

FieldType and constraintsRequiredDescription
errorobject · extra fields rejectedYesStructured failure information. Inspect the stable code and retain the request ID for investigation.
error.codestringYeswithin errorStable error code used to distinguish failures programmatically.
error.messagestringYeswithin errorHuman-readable diagnostic text.
error.requestIdstringYeswithin errorRequest identifier for correlating diagnostics and support investigations.
error.detailsanyNowithin errorOptional structured error context. Its shape is specific to the service failure.
error.loginUrlstringNowithin errorAuthentication URL supplied when a browser login is required.
error.registrationOpenbooleanNowithin errorWhether new account registration is enabled for this deployment.
Example
Example
{
  "error": {
    "code": "platform_balance_insufficient",
    "message": "Top up Platform credits before submitting a Run.",
    "requestId": "req_example"
  }
}

Inspect the Run status and output, then its separate checkpoint, file-diff and usage state. Output can be available while checkpoint capture is still pending. Wait for the checkpoint and lifecycle lock to settle before retrieving files or forking.

Next steps

Add shared instructions, Skills or MCP tools. Use schedules for timed work and inbound webhooks for external events. See errors and retries if admission fails.