bus-api-provider-containers — container API provider
bus-api-provider-containers — container API provider
bus-api-provider-containers is the server-side provider for user-owned
container APIs. It owns cloud-neutral REST endpoints for container status and
run lifecycle requests.
In events mode, the provider sends container lifecycle requests through the Bus
Events API. A deployment can pair it with bus-integration-upcloud for
UpCloud runner lifecycle work and bus-integration-ssh-runner for SSH script
execution.
The public API is account-isolated. The provider derives the owner account from
the JWT sub; callers cannot choose an account ID in request metadata. Users
can list, read, and delete only runs owned by their own account.
Authentication
Public endpoints use Bearer JWT authentication with audience ai.hg.fi/api.
The provider derives the account from JWT sub.
container:read allows status reads. container:run allows run creation.
container:delete allows deleting owned runs.
Internal runner endpoints use audience ai.hg.fi/internal with
container:admin.
GET /api/v1/containers/status
Returns container status and runs visible to the authenticated account.
Success returns 200 OK with {"items":[...]} where each item has id,
state, optional owner_account_id, and optional details.
With --backend events, the provider sends
bus.containers.status.request and waits for
bus.containers.status.response.
POST /api/v1/containers/runs
Starts one foreground user-owned container run.
End users normally call this through bus containers run. The request can use
a named profile with args, or an explicit image and command.
Send Content-Type: application/json. A profile run looks like:
{"profile":"codex","args":["sh","-c","printf OK"],"timeout_seconds":300}
An explicit image run looks like:
{"image":"alpine:latest","command":["sh","-c","printf OK"],"timeout_seconds":300}
profile and image are alternatives; at least one is required. args is a
compatibility alias for command when command is omitted.
When image is used directly, provide command; there is no portable default
command across images.
timeout_seconds is optional and must be non-negative.
Direct HTTP example:
bus auth token --scope "container:read container:run"
TOKEN="$(cat ~/.config/bus/auth/api-token)"
curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
http://127.0.0.1:${LOCAL_AI_PLATFORM_PORT:-8080}/api/v1/containers/runs \
-d '{"profile":"codex","args":["sh","-c","printf OK"],"timeout_seconds":300}'
Successful responses include runner name, image, arguments, exit code, stdout,
stderr, duration, and runner status.
Success returns 200 OK with run_id, owner_account_id, runner_name,
image, args, exit_code, stdout, stderr, duration_ms, and runtime.
With --billing-backend events, the provider checks container:run
entitlement before usage recording or backend delegation. Billing failures
return HTTP 402 with bus billing ... guidance.
With --usage-backend events, the provider records
container_run_requested, then container_run_finished or
container_run_failed.
With --backend events, the provider sends bus.containers.run.request and
waits for bus.containers.run.response.
DELETE /api/v1/containers/runs/{run_id}
Deletes or cancels one user-owned container run when the backend supports it.
The provider must reject attempts to delete runs owned by another account.
Infrastructure runner deletion uses the internal runner endpoint instead.
Success returns 200 OK with {"deleted":true,"run_id":"...","owner_account_id":"..."}.
With --backend events, the provider sends
bus.containers.delete.request and waits for
bus.containers.delete.response.
GET /api/internal/containers/runner
Returns protected operational status for the configured runner.
This endpoint is for trusted service or operator tooling, not end-user
container clients. With --backend events, it sends
bus.containers.runner.status.request.
The request body is empty. Success returns 200 OK with runner state details,
for example {"status":{"state":"ready","provider":"docker","details":{}}}.
Missing or wrong internal-audience credentials return 401 or 403.
POST /api/internal/containers/runner
Starts and bootstraps the configured runner for internal operations.
With --backend events, it sends
bus.containers.runner.start.request.
The request body is empty for the default runner target. Success returns
202 Accepted or 200 OK with an accepted/start result such as
{"accepted":true,"action":"start"}.
DELETE /api/internal/containers/runner
Deletes the configured runner for internal cleanup.
With --backend events, it sends
bus.containers.runner.delete.request.
The request body is empty. Success returns 200 OK with a deletion result such
as {"deleted":true}. These endpoints require an internal token with
container:admin.
GET /readyz
Reports provider readiness.
When BUS_EVENTS_LISTENER_REQUIRED=1, readiness is unhealthy until required
container, usage, and billing response streams are connected.
Common Errors
These errors apply to the API endpoints above.
Common error bodies use
{"error":{"type":"...","message":"...","action":"...","command":"..."}}.
Invalid or missing bearer tokens return 401 invalid_auth. Missing scopes or
cross-account run results return 403 forbidden. Missing profile/image or
bad JSON returns 400 bad_request. Billing denial returns 402
billing_required with setup guidance. Backend timeouts or unavailable event
listeners return 503, and malformed integration responses return 502.
Billing And Quotas
Container plans use the same billing quota system as LLM plans. A common meter
is bus_container_runtime_seconds, produced from successful
container_run_finished usage events.
Operators can configure minute, hour, day, week, month, or total runtime limits in the billing catalog or quota config.
--backend <static|events>
Selects the container backend.
Use static for deterministic local checks. Use events for Bus Events
request/reply mode.
The default is static for standalone deterministic operation unless the
deployment overrides it.
--events-url <url>
Sets the Bus Events API URL used when --backend events, --usage-backend
events, or --billing-backend events is enabled.
Provide the provider’s Events token through deployment-managed configuration,
such as BUS_API_TOKEN. Do not pass bearer tokens as command-line arguments.
--usage-backend <none|events|memory>
Enables usage recording through Events.
Commercial deployments should use events so accepted container work is
available for billing and quota accounting.
Use memory only for deterministic local checks where usage records should not
leave the provider process.
The default is none.
--billing-backend <none|events>
Enables billing entitlement checks before container run creation.
Use events with bus-integration-billing for paid container plans.
The default is none.
BUS_EVENTS_LISTENER_REQUIRED
When set to 1, readiness requires the Events response listeners needed by the
enabled backends.
Use it in production so startup ordering problems do not leave the provider active but unable to complete request/reply work.
Local Compose Stack
The BusDK superproject compose.yaml starts this provider as bus-containers
with --backend events, --usage-backend memory, and --billing-backend
none. Nginx exposes public container APIs at /api/v1/containers/* and
internal runner APIs at /api/internal/containers/*. Container requests flow
through bus-integration-containers to the bus.docker.* backend events
handled by bus-integration-docker.
The local smoke path uses a local API token minted by bus-operator-token and
runs bus containers run --profile codex from the testing container. A
successful check returns a JSON run response with exit code 0 and captured
stdout from the Docker-backed container.
cd /path/to/busdk
docker compose up --build -d
docker compose exec -T testing-agent sh -lc \
'cd /workspace/bus-containers && go run ./cmd/bus-containers run --profile codex -- sh -lc "printf OK"'
End-User Access
Approved users request a Bus API token with the container scopes their plan and approval policy allow:
bus auth token --scope "container:read container:run container:delete billing:read"
The token can be used by bus containers and by direct HTTP clients. Billing
setup may still be required before run creation succeeds.
Operator Notes
Run this provider with Events listener retry enabled in production so startup ordering and Events API restarts do not leave request/reply paths broken:
BUS_EVENTS_LISTENER_RETRY=1
BUS_EVENTS_LISTENER_REQUIRED=1
Use internal runner endpoints only from trusted service or operator tooling. End-user container APIs are deliberately separate from runner lifecycle administration.