Skip to content

API · Containers & Snapshots

Container lifecycle, exec, file access, interactive shell sessions, stats streaming, and snapshot management. All endpoints are unauthenticated (see API conventions).

ContainerController

api/container — inspect, control, exec into, and stream from project containers.

Verb Route Request Response Async
GET /api/container — ContainerDashboardSummary yes
GET /api/container/{containerId} route containerId:string ContainerDetailedInfo / 404 yes
GET /api/container/{containerId}/stats route containerId ContainerStats / 404 yes
GET /api/container/{containerId}/logs route containerId; query tailLines:int=100 List<ContainerLogEntry> yes
GET /api/container/{containerId}/history route containerId; query limit:int=50, offset:int=0 List<ContainerCommandLog> yes
GET /api/container/project/{projectId}/history route projectId:int; query limit, offset List<ContainerCommandLog> yes
POST /api/container/{containerId}/exec route containerId; body ContainerExecRequest (Command, Arguments, WorkingDirectory?, TimeoutSeconds?) ContainerExecResult / 400 / 500 yes
GET /api/container/{containerId}/files route containerId; query path:string="/" List<FileSystemEntry> yes
GET /api/container/{containerId}/files/content route containerId; query path:string ContainerFileContentResponse yes
POST /api/container/{containerId}/start route containerId 200 / 500 yes
POST /api/container/{containerId}/stop route containerId 200 / 500 yes
POST /api/container/{containerId}/restart route containerId 200 / 500 yes
DELETE /api/container/{containerId} route containerId; query projectId:int? 200 / 500 yes
POST /api/container/{containerId}/shell/start route containerId; query shell:string="/bin/sh" ContainerExecSession / 500 yes
POST /api/container/shell/{sessionId}/input route sessionId:string; body ShellInputRequest (Input) ShellOutputResponse / 400 / 500 yes
POST /api/container/shell/{sessionId}/end route sessionId 200 / 500 no
POST /api/container/stats/start-polling query intervalSeconds:int=5 200 no
POST /api/container/stats/stop-polling — 200 no
POST /api/container/project/{projectId}/provision route projectId:int { containerId, containerName, status, image, projectId, hostPorts, declaredContainerPorts, initCommands } / 400 / 404 / 500 yes
POST /api/container/project/{projectId}/write-probe route projectId:int { projectId, containerId, path, writeResult, shellIdentity, fileOwner, shellCanModify, appendOutput } / 400 / 404 / 500 yes

provision creates a project's container without a model run, through the same GetOrCreateTeamContainerAsync the orchestrator uses, so the port allocator, the container user and the template's container_init_commands are the real ones. hostPorts reports the live binding read back from docker inspect, not the requested one — allocation can fail and the container still start with no ports. Each init command's result is reported rather than swallowed, as { command, ok, exitCode, output } — ok is exitCode == 0. (In a normal run, a non-zero exit fails the run.) It returns 400 when the project has no LocalPath or no team config, and also when the team declares a NetworkPolicy other than Unrestricted — provisioning such a team here would run it without the isolation it asked for, so it must go through a normal run instead.

write-probe answers one question: can the shell user modify what the file tool wrote? It writes a file through IWorkspaceExecutor.WriteFileAsync, then uses ExecuteShellAsync to report the shell's uid:gid, the file's owner and mode, and whether an append succeeds (shellCanModify). The two paths having different identities is a real failure mode — tar entries defaulting to uid 0 while the shell runs as the non-root container user makes npm install fail with EACCES on files the agent itself just wrote. It requires an existing container; provision first.

ContainerFileContentResponse, ShellInputRequest, and ShellOutputResponse are declared inline; ContainerExecRequest/Result/Session and FileSystemEntry are in Aetherium.Shared.Models. Stats and shell output also stream over ContainerHub (see API · SignalR hubs).

ContainerSnapshotController

api/container/snapshots — Docker-commit snapshots of container state (create, restore, validate, clean up).

Verb Route Request Response Async
GET /api/container/snapshots query projectId:int?, snapshotType:string?, limit:int=50, offset:int=0 SnapshotListResponse yes
GET /api/container/snapshots/{snapshotId:int} route snapshotId:int ContainerSnapshot / 404 yes
GET /api/container/snapshots/project/{projectId:int} route projectId:int List<ContainerSnapshot> yes
POST /api/container/snapshots body CreateSnapshotRequest (ContainerId, Name, Description?, CreatedBy?) ContainerSnapshot / 400 / 500 yes
POST /api/container/snapshots/{snapshotId:int}/restore route snapshotId:int; body RestoreSnapshotRequest? (ReplaceExisting) ContainerInfo / 400 / 500 yes
PUT /api/container/snapshots/{snapshotId:int} route snapshotId:int; body UpdateSnapshotRequest (Name, Description?) 204 / 400 / 404 yes
DELETE /api/container/snapshots/{snapshotId:int} route snapshotId:int; query removeImage:bool=true 204 / 404 yes
POST /api/container/snapshots/validate — SnapshotValidationResult yes
POST /api/container/snapshots/cleanup — SnapshotCleanupResult yes
GET /api/container/snapshots/storage — SnapshotStorageInfo yes

CreateSnapshotRequest, RestoreSnapshotRequest, UpdateSnapshotRequest, SnapshotListResponse, SnapshotValidationResult, SnapshotCleanupResult, and SnapshotStorageInfo are declared inline. Snapshot retention is governed by the Container_*Snapshot* settings in Configuration.

Where to go next