Skip to content

REST API (v1) reference

The AWC control plane exposes a public HTTPS JSON API at /v1/*. This page lists every endpoint, its request and response fields, its auth and idempotency rules, and the error model. Where the shipped build lags the design, the page says so.

The host-local and internal gRPC methods (mount, unmount, checkpoint, lease renew, publish) are not REST. See gRPC services.

Transport

Property Value
Wire format JSON (Content-Type: application/json)
Transport HTTPS (spec §13.1)
Base path /v1
Framework axum
Body decoding serde_json; a malformed body returns INVALID_ARGUMENT
Timestamps RFC 3339 UTC strings, for example 2026-08-05T15:00:00Z

Authentication

Every endpoint except GET /v1/health requires a bearer token (spec §13.2, §16.4).

Header Required Value
Authorization Yes (except health) Bearer <token>

The control plane branches on the token shape (spec §13.2):

Principal kind Token form How it is verified
USER OIDC id_token OIDC verifier (awc-auth)
SERVICE_ACCOUNT awcsat1_<b64url(payload)>.<b64url(sig)> HMAC against the configured service-token key

A token that starts with awcsat1_ is parsed as a service-account token. Any other bearer token is treated as an OIDC id_token. If service-account tokens are not configured, an awcsat1_ token returns UNAUTHENTICATED.

Authorization is checked per request against the principal's project role (spec §16.4). A principal with no membership for the target project receives PERMISSION_DENIED. Cross-tenant access returns PERMISSION_DENIED without leaking existence (spec §16.5).

Common headers

Header Direction Purpose Spec
Authorization Request Bearer credential (see above) §13.2
idempotency-key Request Deduplicates a mutation (mutations only) §13.1
x-request-id Both Tracing id; honored inbound, minted if absent, echoed outbound §13.1

Idempotency

All mutation endpoints accept the idempotency-key header (spec §13.1). A retry that carries the same key replays the committed result.

Property Value
Replay window 24 hours (IDEMPOTENCY_TTL)
Key scope tenant_id + principal + operation + key
Conflict response IDEMPOTENCY_CONFLICT (HTTP 409)
Idempotent operations create import session, complete import, create workspace, fork workspace, issue mount ticket

POST :batchCheckObjects and all GET endpoints are reads. They do not read the idempotency-key header.

Rate limiting

Every authenticated request passes through a per-principal rate limiter keyed by tenant_id/subject (spec §16.10). When the bucket is empty the request returns RESOURCE_EXHAUSTED (HTTP 429).

Resource identifiers

Human-facing ids are opaque, prefix-qualified ULIDs of the form <prefix>_<ulid> (spec §9.1).

Type Prefix Appears in
TenantId ten_ resource records
ProjectId prj_ requests and records
SnapshotId snap_ snapshots, workspaces
WorkspaceId ws_ workspaces
ImportSessionId import_ import sessions
HostId host_ mount tickets
LeaseId lease_ writer leases
MountSessionId mount_ mount sessions

A mount ticket id uses tkt_. The opaque signed ticket string uses the awcmt1_ prefix (see Mount tickets).

Endpoints

The status column marks what the shipped build serves. "Implemented" means the route is served. "Specified" means a wire contract defines it but the router does not serve it.

Import sessions

Method Path Status Spec
POST /v1/import-sessions Implemented §13.3
POST /v1/import-sessions/{id}:batchCheckObjects Implemented §13.4
POST /v1/import-sessions/{id}:complete Implemented §13.5

The verb suffix travels in the same path segment, for example import_01J...:complete. The router captures the whole segment and splits on :. An unknown suffix returns NOT_FOUND.

POST /v1/import-sessions

Opens a snapshot import session. Idempotent.

Request fields (CreateImportSessionRequest):

Field Type Required Notes
project_id ProjectId Yes
source_type enum Yes directory, git, tar, checkpoint
normalization_policy enum Yes source-normalized, environment-preserving
name string No
labels map No Omitted when empty

Response (CreateImportSessionResponse):

Field Type Notes
id ImportSessionId import_<ulid>
state SnapshotState UPLOADING
upload_endpoint string Opaque capability scoped to the tenant CAS prefix and session
expires_at timestamp

The upload capability is limited to the tenant CAS prefix and the session (spec §13.3).

POST /v1/import-sessions/{id}:batchCheckObjects

Checks which referenced objects are missing from the CAS. A read; not idempotency-keyed.

Request fields (BatchCheckObjectsRequest):

Field Type Required
objects array of ObjectRef Yes

ObjectRef:

Field Type Notes
digest Digest sha256:<hex>
size_bytes u64
kind enum BLOB, FILE, TREE, SNAPSHOT

Response (BatchCheckObjectsResponse):

Field Type Notes
missing array of Digest Digests absent from the CAS
upload_urls map Optional. Digest string to presigned upload URL. Omitted when not offered

The service MAY return presigned upload URLs (spec §13.4). The shipped in-memory and database backends report missing only.

POST /v1/import-sessions/{id}:complete

Completes the import and starts closure validation. Idempotent.

Request fields (CompleteImportRequest):

Field Type Required
snapshot_manifest_digest Digest Yes
declared_logical_bytes u64 Yes
declared_file_count u64 Yes

Response (CompleteImportResponse):

Field Type Notes
snapshot_id SnapshotId snap_<ulid>
state SnapshotState VALIDATING

The snapshot becomes mountable only after closure validation sets it to READY (spec §13.5).

Snapshots

Method Path Status Spec
GET /v1/snapshots Implemented §13 (list)
GET /v1/snapshots/{id} Implemented §13 (get)
GET /v1/snapshots/{left}:diff?right={right} Implemented (JSON only) §13.14

GET /v1/snapshots

Lists snapshot metadata. The project_id query parameter is required.

Query parameters (ListSnapshotsRequest):

Parameter Type Required Notes
project_id ProjectId Yes
state SnapshotState No Client-side filter
parent_snapshot_id SnapshotId No Children of this snapshot

Response (ListSnapshotsResponse):

Field Type
snapshots array of Snapshot

The state and parent_snapshot_id filters are applied in the handler after the project listing, because the persistence port lists by project and tenant only.

GET /v1/snapshots/{id}

Returns one snapshot record. An unknown id returns NOT_FOUND. A known id that belongs to another tenant returns PERMISSION_DENIED (the snapshot store fetches by id, then the handler enforces tenant match).

Snapshot resource:

Field Type Notes
id SnapshotId
tenant_id TenantId
project_id ProjectId
manifest_digest Digest Content identity of the snapshot manifest
root_tree_digest Digest Root tree the manifest references
state SnapshotState See Snapshot states
logical_bytes u64 Logical, de-duplicated-summed size
file_count u64
parent_snapshot_id SnapshotId Optional. None for an imported root
source enum directory, git, tar, checkpoint
normalization_policy enum source-normalized, environment-preserving
created_by string
created_at timestamp
labels map Optional. Omitted when empty

GET /v1/snapshots/{left}:diff

Compares two snapshot root trees and returns an inline change list. The right parameter is required.

Query parameters (DiffRequest):

Parameter Type Required Notes
right SnapshotId Yes
format enum No Default json

The shipped handler accepts format=json only. format=patch-bundle and format=tar return INVALID_ARGUMENT (spec §13.14).

Response (DiffResponse), tagged by kind:

kind Body Status
result DiffResult Implemented (always returned)
job DiffJob Specified for large diffs, not emitted

DiffResult:

Field Type Notes
left SnapshotId
right SnapshotId
changes array of FileChange
patch_bundle_digest Digest Optional. Set only for format=patch-bundle

FileChange:

Field Type Notes
path string POSIX-style, relative to the snapshot root
kind enum See Change kinds
old_mode u32 Optional. Previous mode
new_mode u32 Optional. New mode
size_bytes u64 Optional. New content size
content_digest Digest Optional. New content digest (file adds and modifies)
unified_diff string Optional. Unified text diff

Workspaces

Method Path Status Spec
POST /v1/workspaces Implemented §13.6
GET /v1/workspaces/{id} Implemented §13.6
POST /v1/workspaces/{id}:fork Implemented §13.7
POST /v1/workspaces/{id}:issueMountTicket Implemented §13.8
POST /v1/workspaces/{id}:unmount Specified, not served §13.13
POST /v1/workspaces/{id}:acquireLease Specified, not served gRPC only
POST /v1/workspaces/{id}:releaseLease Specified, not served gRPC only
POST /v1/workspaces/{id}:revokeLease Specified, not served gRPC only

The router registers GET and POST on /v1/workspaces/{ws}. A GET with a verb suffix, or a POST verb other than fork and issueMountTicket, returns NOT_FOUND. The unmount and lease operations live on the gRPC services instead (see Current scope).

POST /v1/workspaces

Creates a workspace from a READY snapshot. Idempotent. Requires the CreateWorkspace permission.

Request fields (CreateWorkspaceRequest):

Field Type Required Notes
project_id ProjectId Yes
snapshot_id SnapshotId Yes Must be READY and same-tenant
name string No
ttl_seconds u64 Yes Sets expires_at
labels map No Omitted when empty

A non-READY source snapshot returns SNAPSHOT_NOT_READY.

GET /v1/workspaces/{id}

Returns one workspace record. Requires ViewMetadata.

Workspace resource:

Field Type Notes
id WorkspaceId
tenant_id TenantId
project_id ProjectId
base_snapshot_id SnapshotId
head_snapshot_id SnapshotId
version u64 Optimistic-concurrency version
state WorkspaceState See Workspace states
writer_lease_generation u64 0 when no lease is held
expires_at timestamp
created_by string
created_at timestamp
labels map Optional. Omitted when empty

POST /v1/workspaces/{id}:fork

Forks one or more workspaces from the base, head, or a specific retained snapshot. Idempotent. Requires ForkWorkspace.

Request fields (ForkWorkspaceRequest):

Field Type Required Notes
from string Yes Wire key is from. Value is base, head, or a SnapshotId
count u32 Yes Batch size
ttl_seconds u64 Yes Sets expires_at for each child
name_prefix string No

The API caps batch size at MAX_FORK_BATCH (1000). Larger fan-out uses repeated idempotent batches (spec §13.7).

Response (ForkWorkspaceResponse):

Field Type
workspaces array of Workspace

Mount tickets

Method Path Status Spec
POST /v1/workspaces/{id}:issueMountTicket Implemented §13.8

POST /v1/workspaces/{id}:issueMountTicket

Mints a short-lived, signed mount ticket. Idempotent. For READ_WRITE the control plane acquires a single-writer lease and stamps its generation into the ticket. For READ_ONLY no lease is acquired. Permission maps to the mode: READ_WRITE needs MountWorkspace; READ_ONLY needs MountSnapshotReadOnly.

Request fields (IssueMountTicketRequest):

Field Type Required Notes
host_id HostId Yes
mode enum Yes READ_ONLY, READ_WRITE
requested_ttl_seconds u64 No Default 300 seconds
execution_cgroup string No For host-side accounting

An expired workspace (ttl_expires_at <= now) returns WORKSPACE_EXPIRED. A workspace that already holds a writer lease returns WRITER_LEASE_HELD.

Response (IssueMountTicketResponse):

Field Type Notes
ticket MountTicket
mount_session_id MountSessionId mount_<ulid>
state MountSessionState AUTHORIZED

MountTicket:

Field Type Notes
ticket_id string tkt_<ulid>
tenant_id TenantId
workspace_id WorkspaceId
snapshot_id SnapshotId Head snapshot the mount is sealed against
root_digest Digest Sealed root tree digest
host_id HostId
mode enum READ_ONLY, READ_WRITE
lease_generation u64 Optional. Omitted for READ_ONLY
target_policy TargetPolicy
issued_at timestamp
expires_at timestamp
nonce string Single-use, defeats replay
protocol_version u32 2
signed_ticket string awcmt1_<b64url(payload)>.<b64url(sig)>, Ed25519-signed

TargetPolicy:

Field Type Notes
allowed_target_roots array of string The host refuses targets outside these roots

Defaults used by the control plane:

Constant Value
DEFAULT_TICKET_TTL_SECONDS 300
DEFAULT_LEASE_TTL_SECONDS 90
TICKET_PROTOCOL_VERSION 2

Health

Method Path Status Auth
GET /v1/health Implemented None

Returns the literal string ok. No bearer token required. Use it as a liveness probe.

Enums

Snapshot states

SnapshotState serializes as SCREAMING_SNAKE_CASE (spec §12.1).

Value Mountable Readable
UPLOADING No No
VALIDATING No No
READY Yes Yes
FAILED No No
DELETING No No
DELETED No No
DEGRADED No Yes

Only READY snapshots may be mounted or forked. DEGRADED snapshots are readable for reads that tolerate missing objects.

Workspace states

WorkspaceState serializes as SCREAMING_SNAKE_CASE (spec §12.2).

Value
CREATING
FAILED
IDLE
MOUNTED
CHECKPOINTING
EXPIRING
DELETED

Mount session states

MountSessionState serializes as SCREAMING_SNAKE_CASE (spec §12.3).

Value
REQUESTED
AUTHORIZED
PREPARING
MOUNTED
UNMOUNTING
CLOSED
FAILED

CLOSED and FAILED are terminal.

Change kinds

ChangeKind serializes as kebab-case (spec §9.12).

Value Meaning
add-file A new file was added
modify-file An existing file changed
delete-path A path was deleted
create-directory A new directory was created
delete-directory A directory was deleted
change-mode Only the mode changed
change-symlink A symlink was created or retargeted

Diff formats

DiffFormat serializes as kebab-case (spec §13.14).

Value Status
json Implemented (default)
patch-bundle Specified, rejected by the handler
tar Specified, rejected by the handler

Error model

Errors use a stable, machine-readable code and include a request id (spec §13.1, §13.15). The top-level document is { "error": { ... } }.

ErrorBody:

Field Type Notes
code ErrorCode Clients branch on this
message string Human-readable; contains no secrets (spec §16.5)
request_id string Optional. Omitted when absent
retryable bool Whether bounded backoff may succeed

The HTTP status is advisory; the code is authoritative. The mapping below is what the shipped handler emits. For the full code reference, see Error codes.

Code HTTP Retryable
UNAUTHENTICATED 401 No
PERMISSION_DENIED 403 No
HOST_NOT_AUTHORIZED 403 No
NOT_FOUND 404 No
INVALID_ARGUMENT 422 No
OBJECT_MISSING 422 No
OBJECT_CORRUPT 422 No
MOUNT_UNSUPPORTED 422 No
ALREADY_EXISTS 409 No
IDEMPOTENCY_CONFLICT 409 No
SNAPSHOT_NOT_READY 409 Yes
WORKSPACE_BUSY 409 Yes
STALE_LEASE_GENERATION 409 No
WORKSPACE_VERSION_CONFLICT 409 No
QUIESCE_FAILED 409 Yes
WRITER_LEASE_HELD 409 Yes
WORKSPACE_EXPIRED 410 No
RESOURCE_EXHAUSTED 429 Yes
LOCAL_IO_ERROR 500 Yes
CHECKPOINT_PUBLICATION_FAILED 500 Yes
OBJECT_STORE_UNAVAILABLE 503 Yes
CONTROL_PLANE_UNAVAILABLE 503 Yes

Current scope

This section records where the shipped build differs from the spec.

Implemented REST endpoints

The shipped router serves these routes:

  • POST /v1/import-sessions
  • POST /v1/import-sessions/{id}:batchCheckObjects
  • POST /v1/import-sessions/{id}:complete
  • GET /v1/snapshots
  • GET /v1/snapshots/{id}
  • GET /v1/snapshots/{left}:diff (JSON inline only)
  • POST /v1/workspaces
  • GET /v1/workspaces/{id}
  • POST /v1/workspaces/{id}:fork
  • POST /v1/workspaces/{id}:issueMountTicket
  • GET /v1/health

Specified but not served as REST

The wire contracts for these operations exist, but the router does not register them as REST. The live transport is gRPC (see gRPC services):

  • POST /v1/workspaces/{id}:unmount (spec §13.13). The shipped path is the host-local gRPC method UnmountWorkspace.
  • POST /v1/workspaces/{id}:acquireLease, :releaseLease, :revokeLease. The spec does not define REST endpoints for these. A READ_WRITE mount acquires its writer lease implicitly through :issueMountTicket (spec §13.8). Lease activation, renewal, and release run over the control-plane gRPC LeaseService (ActivateWorkspaceLease, RenewWorkspaceLease, ReleaseWorkspaceLease). Spec §13.10 covers renewal, and §12.4 enumerates the lease states.

Specified but not emitted

  • Diff formats patch-bundle and tar (spec §13.14). The handler accepts format=json only and rejects the others with INVALID_ARGUMENT.
  • The deferred diff job (DiffResponse::Job, spec §13.14). The service always returns an inline DiffResponse::Result. The DiffJob contract exists but is not produced.
  • Batch-check presigned upload URLs (spec §13.4 "MAY"). The contract field upload_urls exists. The shipped backends report missing only and omit upload_urls.