better/compute
API

sandboxes

A sandbox is a hosted VPS instance with a template and a lifetime: `create` admits it after one IAM decision, then claims a warm instance of its template or creates one through the instance lifecycle, and the `vps.sandboxes` reconciler, which runs every 15 seconds, deletes it once `expiresAt` passes. A sandbox's `state` follows its instance: `starting`, `ready` while the instance runs, `failed` when it is in `error` or `lost`, `deleting` once deletion was asked, and `deleted` when the instance is gone. The instance belongs to the sandbox: `instances.start`, `stop`, `resize` and `delete` refuse it with `INVALID_STATE`, and `instances.get` still returns it. See [Sandboxes](/docs/guides/vps/sandboxes).

A sandbox is a hosted VPS instance with a template and a lifetime: create admits it after one IAM decision, then claims a warm instance of its template or creates one through the instance lifecycle, and the vps.sandboxes reconciler, which runs every 15 seconds, deletes it once expiresAt passes. A sandbox's state follows its instance: starting, ready while the instance runs, failed when it is in error or lost, deleting once deletion was asked, and deleted when the instance is gone. The instance belongs to the sandbox: instances.start, stop, resize and delete refuse it with INVALID_STATE, and instances.get still returns it. See Sandboxes.

All 7 methods are served by this release.

MethodKindAuthorizationPhaseStatus
createmutationcompute:sandboxes:create @ compute-sandboxv0.5registered
listreadcompute:sandboxes:read @ compute-tenant + itemsv0.5registered
getreadcompute:sandboxes:read @ compute-sandboxv0.5registered
extendmutationcompute:sandboxes:update @ compute-sandboxv0.5registered
snapshotmutationcompute:sandboxes:update @ compute-sandboxv0.5registered
execmutationcompute:sandboxes:exec @ compute-sandboxv0.5registered
deletemutationcompute:sandboxes:delete @ compute-sandboxv0.5registered

create

POST /api/compute/sandboxes/create

Starts a sandbox from a template after one IAM decision on the draft, claiming a warm instance when the template holds one and creating a new instance otherwise; the lifetime starts at once.

  • Idempotency. idempotencyKey is required. The sandbox id derives from the tenant, the caller's identity and the key: a retry of an admitted request returns the stored sandbox without a second decision, and a different request under the same key is CONFLICT with details.sandboxId, as is the key replayed by another agent or person. wait is not part of the compared request.
  • IAM. compute:sandboxes:create is decided on compute-sandbox/ plus the draft id, whose parent is the template's pool. Policies can test templateId, vcpu, memoryGiB, gpus, ttlSeconds and warm (true when the template keeps a warm pool). A refusal deletes the draft; a delegated session can get CONFIRMATION_REQUIRED. After an allow, the pool's budget check can answer SPEND_LIMIT_REACHED and keep the draft for a retry with the same key.
  • Start. The oldest stopped or paused warm instance of the template is claimed: it changes owner to the caller, is set to run, and publishes instance.starting; the claim places nothing new. Without one, a new instance is created and placed as instances.create would, publishing instance.created and, once placed, instance.scheduled; when no host fits it waits in scheduling and the sandbox stays starting. warm in the answer tells which path was taken.
  • Restore. fromSnapshot starts the sandbox from one of the tenant's own ready snapshots of this template, taken on its runtime (another tenant's is NOT_FOUND, one not ready is INVALID_STATE, one of another template is INVALID_INPUT). An agent restores only snapshots of sandboxes it created itself; any other is NOT_FOUND to it. A restore never claims a warm instance, and a snapshot that includes memory places only on nodes with the same cpu.model. Policies see it as the fromSnapshot attribute.
  • Lifetime. ttlSeconds defaults to the template's defaultTtlSeconds and may not exceed its maxTtlSeconds (INVALID_INPUT). expiresAt is set when the sandbox is admitted.
  • Errors. NOT_FOUND for a template missing or deleted in the tenant. INVALID_STATE when the template's flavor has been archived or removed. On the new-instance path the template's pool, flavor and image are checked again, with the errors of sandboxTemplates.create; QUOTA_EXCEEDED when the shape does not fit the tenant's quota and CAPACITY_UNAVAILABLE when the pool has no free IP address, both keeping the draft.
  • Waiting. wait.timeoutMs (at most 30 seconds) holds the answer until the sandbox leaves starting; a timeout returns it still starting, not an error.

Publishes sandbox.created with warm and expiresAt. sandbox.ready follows from the reconciler once the instance first runs.

  • Kind: mutation
  • Authorization: compute:sandboxes:create @ compute-sandbox
  • Module: vps
  • Phase: v0.5 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
templateIdstringyesat most 57 characters; matches ^sbxt_[0-9a-hjkmnp-tv-z]{26,52}$
ttlSecondsinteger60-2592000
fromSnapshotstringat most 57 characters; matches ^snap_[0-9a-hjkmnp-tv-z]{26,52}$
idempotencyKeystringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
waitobject
wait.timeoutMsintegeryes0-30000

list

POST /api/compute/sandboxes/list

Returns a page of the tenant's sandboxes, newest first, with a cursor when more remain.

scope defaults to mine, the caller's own sandboxes, which needs no per-item check. all lists every sandbox of the tenant, each one checked for compute:sandboxes:read; those the caller may not read are left out, so a page can hold fewer than limit items. Deleted sandboxes are left out unless deleted is true, and templateId keeps one template's sandboxes. limit is 1 to 200 (default 50); cursor is the last sandbox id of the previous page. Drafts are never listed. Each item carries identityRebound as get describes.

  • Kind: read
  • Authorization: compute:sandboxes:read @ compute-tenant + items
  • Module: vps
  • Phase: v0.5 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
scopeone of mine, alldefault "mine"
deletedbooleandefault false
templateIdstringat most 57 characters; matches ^sbxt_[0-9a-hjkmnp-tv-z]{26,52}$
limitinteger1-200
cursorstringat most 56 characters; matches ^sbx_[0-9a-hjkmnp-tv-z]{26,52}$

get

POST /api/compute/sandboxes/get

Returns one sandbox of the tenant with its state, its instance's state, addresses and node, and its expiresAt.

readyAt is set once the reconciler saw the instance run, and expiredAt when the sandbox ended by reaching expiresAt. snapshots lists the sandbox's snapshots with their state. Drafts and sandboxes of other tenants are NOT_FOUND.

identityRebound appears only on a memory restore that was given its own guest identity. It is false until the host has installed that identity; until then the guest still answers to its source's identity, and exec is refused with restore-shares-identity while another guest shares it. It is true once the identity is installed, from which point exec signs for this sandbox alone. It is absent when there is nothing to install: the sandbox is not a memory restore, or exec is not configured.

  • Kind: read
  • Authorization: compute:sandboxes:read @ compute-sandbox
  • Module: vps
  • Phase: v0.5 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
sandboxIdstringyesat most 56 characters; matches ^sbx_[0-9a-hjkmnp-tv-z]{26,52}$

extend

POST /api/compute/sandboxes/extend

Moves a sandbox's expiresAt later after one IAM decision, within its template's maxTtlSeconds counted from creation; returns the sandbox.

  • Give exactly one of until (the new expiry, epoch milliseconds) or by (added to the current expiry: 30m, 8h, 7d, or milliseconds, at most 30 days); both or neither is INVALID_INPUT. A lifetime past the template's maxTtlSeconds is INVALID_INPUT, and a sandbox whose deletion has started, by delete or by expiry, is INVALID_STATE.
  • IAM. compute:sandboxes:update is decided on the sandbox with a pending ttlSeconds equal to the whole lifetime after the change, so a policy that caps ttlSeconds on create also caps extensions. A refusal is ACCESS_DENIED and clears the pending change.
  • Idempotency. An until that is not later than the current expiry returns the sandbox unchanged without a decision, so a retried until is safe. There is no idempotencyKey: a repeated by extends again.
  • While another extend of the same sandbox is being decided, the call is CONFLICT with details.reason: 'extend-in-flight' and retryAfterSeconds. A spent budget is SPEND_LIMIT_REACHED.

The stored ttlSeconds becomes the new whole lifetime. Publishes sandbox.extended with the new expiresAt.

  • Kind: mutation
  • Authorization: compute:sandboxes:update @ compute-sandbox
  • Module: vps
  • Phase: v0.5 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
sandboxIdstringyesat most 56 characters; matches ^sbx_[0-9a-hjkmnp-tv-z]{26,52}$
untilinteger0-140737488355328
bystring,integer

snapshot

POST /api/compute/sandboxes/snapshot

Saves a running or paused sandbox's state (disk, and memory unless includeMemory is false) as a tenant-private snapshot; returns it pending until the host's instance.snapshot command settles.

  • Where. The deployment's snapshot store (vps({ snapshots })) presigns an https upload URL, and the host uploads straight to it. Without a store the call is INVALID_CONFIG. A sandbox that is not running or paused, or whose deletion has started, is INVALID_STATE.

  • Settling. The vps.sandboxes reconciler, woken when the command settles, makes the snapshot ready (with its sha256, size, runtime and CPU model) and publishes sandbox.snapshot.ready, or makes it failed and publishes sandbox.snapshot.failed.

  • Idempotency. With idempotencyKey, a retry returns the same snapshot; the key used for another sandbox is CONFLICT. Without one, every call takes a new snapshot.

  • Lifetime. A snapshot is deleted with its sandbox unless keep is true. A tenant teardown deletes all of them, kept ones included. Snapshots are never promoted to shared images.

  • Kind: mutation

  • Authorization: compute:sandboxes:update @ compute-sandbox

  • Module: vps

  • Phase: v0.5 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
sandboxIdstringyesat most 56 characters; matches ^sbx_[0-9a-hjkmnp-tv-z]{26,52}$
includeMemorybooleandefault true
keepbooleandefault false
idempotencyKeystringat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$

exec

POST /api/compute/sandboxes/exec

Opens SSH into a running sandbox: returns a single-use ssh tunnel ticket for its instance and a short OpenSSH user certificate from the deployment's sandbox SSH CA for the principal <account>@<instanceId>.

  • How. A sandbox has no in-guest agent: the node's daemon dials the instance's own address, and the instance's sshd trusts the sandbox CA and accepts only its own principal, so a certificate for one sandbox opens no other. Open GET on the returned path with the ticket before expiresAt, then speak SSH over it. better-compute sandbox exec does both.
  • Host key. The answer tells whether the guest can be pinned. hostCertificate is valid when the guest presents a host certificate from the sandbox CA, still good for at least a minute (then pin it with @cert-authority for hostPrincipal and HostKeyAlias). It is expired when that certificate ended, and none when the instance was never certified. hostCertified is true only when valid.
  • Certificate. publicKey must be an Ed25519 or ECDSA P-256 key. ttlSeconds is 60 to 900 (default 600) and never past the caller's session. Guests receive no revocation list, so the short lifetime is the revocation control, and tunnel re-authorization closes the transport.
  • Errors. UNSUPPORTED when the deployment has no sandbox SSH CA (tunnels.sandboxSsh). ACCESS_DENIED for an API key (reason session-required) or a session ending within a minute. INVALID_STATE when the sandbox is not running or is being deleted, and with details.reason: 'restore-shares-identity' while another running guest shares its SSH identity (a memory restore of the same snapshot, or the sandbox it was taken from): a certificate for one would open the others, so exec waits until only one of them runs. The node must be connected and advertise tunnel.instance-dial. Exec shares the per-identity tunnel rate limit with tunnels.open (RATE_LIMITED).

Hosted by the tunnels module. Publishes tunnel.issued.

  • Kind: mutation
  • Authorization: compute:sandboxes:exec @ compute-sandbox
  • Module: tunnels
  • Phase: v0.5 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
sandboxIdstringyesat most 56 characters; matches ^sbx_[0-9a-z]{26,52}$
publicKeystringyesat most 4096 characters
ttlSecondsinteger60-900; default 600

delete

POST /api/compute/sandboxes/delete

Asks for the sandbox's instance to be deleted and marks the sandbox deleting; returns the sandbox at once.

The instance's desiredState becomes deleted and the vps.instances reconciler releases its allocation, IP lease and quota as for any instance, publishing instance.deleting with the sandboxId. The call is idempotent: a sandbox already deleting is returned in its current state. Drafts and missing sandboxes are NOT_FOUND.

When the instance is gone, the vps.sandboxes reconciler marks the sandbox deleted, removes it from the authorization projection and publishes sandbox.deleted with expired: false. Expiry takes the same path after publishing sandbox.expired, and its sandbox.deleted carries expired: true.

  • Kind: mutation
  • Authorization: compute:sandboxes:delete @ compute-sandbox
  • Module: vps
  • Phase: v0.5 (registered)

Input

FieldTypeRequiredConstraints
tenantIdstringyesat most 128 characters; matches ^[A-Za-z0-9._:-]{1,128}$
sandboxIdstringyesat most 56 characters; matches ^sbx_[0-9a-hjkmnp-tv-z]{26,52}$

Was this page helpful?

Better Compute is created by Sean Filimon

Last updated

On this page