Environments and Secrets

Published

Updated

An environment is the reusable runtime definition for a repo or set of repos. It answers: which platform should boot, which sources should clone, which setup commands should run once, which services should start every boot, and which agent or simulator features should be available.

VMs and sessions boot from environments. A good environment is the foundation for fast, repeatable API automation.

What Goes In An Environment

An environment has a name, visibility, and optional AES manifest.

The manifest can define:

  • platform: linux/amd64 or darwin/arm64, plus memory and vCPU settings.
  • sources: Git repositories and workspaces.
  • env: string environment variables, including ${secrets.NAME} references.
  • setup: sequential commands run while building the environment snapshot.
  • services: long-running processes started on every boot.
  • commands: named commands available to the ready phase, API command execution, or UI.
  • ready: lightweight named commands to run after services are healthy on each boot.
  • mounts: S3 buckets attached to the VM's filesystem, so output outlives the machine. Linux only, up to four. See Mounted S3 Storage.
  • simulators: iOS simulator configuration for macOS environments.
  • docker_compose: an alternative to setup and services for Docker Compose projects.
  • agent: instruction files and MCP servers available to coding sessions.
  • callbacks: lifecycle webhook configuration.

Use the Manifest Reference for field-level manifest details.

Visibility

New environments default to private.

VisibilityWho can use it
privateOnly the creator can see, use, or change it.
account_readAccount members can see and use it; only the creator can edit or delete it.
account_editAccount members can see, use, edit, and delete it.

Only the creator can change visibility.

List Environments

GET /v1/accounts/{account}/environments returns computers ordered by assigned, enabled agent count, highest first. Disabled agents do not add to the count, and unused computers follow those in use. Ties sort by updated_at descending, then ID descending. This order remains stable across cursor-paginated results.

Create An Environment

Creating or updating an environment with a manifest starts snapshot generation. The snapshot bakes in source checkout and setup so later VMs and sessions can boot quickly.

curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/environments" \
  -H "Authorization: Bearer $WALLFACER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: env-my-api-v1" \
  -d '{
    "name": "my-api",
    "visibility": "private",
    "manifest": {
      "version": 1,
      "platform": { "os": "linux/amd64" },
      "sources": [
        {
          "repo": "acme/my-api",
          "workspace": "/workspace/my-api",
          "branch": "main"
        }
      ],
      "setup": [
        {
          "name": "install",
          "run": "npm ci",
          "working_dir": "/workspace/my-api"
        }
      ],
      "services": [
        {
          "name": "web",
          "run": "npm run dev",
          "working_dir": "/workspace/my-api",
          "ports": [{ "port": 3000, "name": "web", "protocol": "http" }],
          "healthcheck": { "url": "http://localhost:3000" }
        }
      ]
    }
  }'

Store the returned environment ID. You pass it to tasks, sessions, and direct VM boots.

For each manifest source, omit branch to follow the repository default at snapshot generation. A branch you explicitly name stays pinned. Creating or updating an environment with a new or changed pin that does not exist returns 422 missing_branch. The error includes the repository, requested branch, and available branches. If a pinned branch disappears after the environment was saved, the next snapshot generation fails with the same diagnostic. Update the manifest to an existing branch before retrying. Empty repositories accept any branch. See Manifest Reference.

Copy an Environment

Pass source_environment_id when creating an environment to copy its manifest, MCP servers, and secrets. You need edit access to the source. Do not send manifest or mcp_servers in the same request. The API rejects that combination.

curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/environments" \
  -H "Authorization: Bearer $WALLFACER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: copy-my-api-v1" \
  -d '{"name":"my-api-copy","source_environment_id":"SOURCE_ENVIRONMENT_ID"}'

Secret values are rewrapped for the new environment, so copied ${secrets.NAME} references resolve without revealing the values. OAuth MCP servers keep their configuration but need a new sign-in on the copy. A source with more than 50 secrets returns too_many_secrets; copies are limited to five per minute per user. An inaccessible or uneditable source returns 404. If secret rewrapping fails, the API deletes the incomplete copy and returns duplicate_failed with status 502.

Add Secrets Before Referencing Them

Secret values are encrypted, write-only, and scoped to one environment. After creation, the API returns metadata only; you cannot read the value back. Rotate the secret if you need to replace it.

Secret names must start with an uppercase letter and contain only uppercase letters, digits, and underscores. Values can be up to 64 KiB and may contain multiple lines.

curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/environments/$ENVIRONMENT_ID/secrets" \
  -H "Authorization: Bearer $WALLFACER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: secret-db-password-v1" \
  -d '{
    "name": "DB_PASSWORD",
    "value": "replace-me"
  }'

Reference secrets from string-valued manifest fields:

{
  "env": {
    "DATABASE_URL": "postgres://app:${secrets.DB_PASSWORD}@localhost:5432/app"
  }
}

If a manifest references a missing secret, environment creation, environment update, or snapshot generation fails with missing_secrets and includes the unresolved names and field paths.

For a brand-new environment, create it without secret references, create the secrets, then patch the manifest in. A copy is different: its secrets arrive with it, so existing references resolve immediately.

A mount's credentials go through the same store: put ${secrets.NAME} in a mount's access_key_id and secret_access_key rather than the key itself, and the manifest comes back with those fields redacted.

Deleting a secret is rejected with secret_in_use while the environment manifest still references it. Remove the ${secrets.NAME} references from the manifest first, patch the environment, then delete the secret.

MCP Servers

mcp_servers sits on the environment beside manifest, not inside it, and maps each server's name to its definition. Changing it never regenerates the snapshot; the next session picks it up.

curl -s -X PATCH "$WALLFACER_API/accounts/$ACCOUNT_ID/environments/$ENVIRONMENT_ID" \
  -H "Authorization: Bearer $WALLFACER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mcp_servers": {
      "issues": {
        "url": "https://mcp.example.com/mcp",
        "headers": { "Authorization": "Bearer ${secrets.ISSUES_TOKEN}" }
      },
      "observability": {
        "url": "https://mcp.example.com/o/mcp",
        "auth": { "type": "oauth", "scopes": ["project:read"] }
      },
      "database": {
        "command": "npx",
        "args": ["-y", "@anthropic/mcp-postgres", "$DATABASE_URL"]
      }
    }
  }'
  • A remote server is {url, headers}: a hosted https endpoint the session calls with the headers you supply. Header values may reference environment secrets as ${secrets.NAME} and are returned as written, never as the value.
  • A remote server that signs in with OAuth is {url, auth: {"type": "oauth", scopes}}. It carries no header you supply, and auth and headers are mutually exclusive.
  • A sandbox server is {command, args, env}: a stdio MCP server the platform runs inside the session VM.

Server names are 1-64 characters of lowercase letters, digits, hyphens, and underscores. sandbox, simulator, comms, and submit_result are reserved.

Sending mcp_servers replaces the whole map, so patch the full set each time; pass null to remove every server. At most 20 servers, and each kind of definition must serialize to at most 32 KiB, measured after ${secrets.NAME} references are substituted.

Sign In To An OAuth MCP Server

A server configured with auth cannot be used until somebody has signed in to it once. The environment's read-only mcp_connections map reports where each one stands: disconnected, connected, or needs_reconnect.

Start a sign-in by creating a connection:

curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/environments/$ENVIRONMENT_ID/connections" \
  -H "Authorization: Bearer $WALLFACER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: connect-observability-v1" \
  -d '{ "server_name": "observability" }'

The response carries authorization_url. Open it in a browser: the provider shows its own consent screen and, on approval, redirects back to Wallfacer, which completes the exchange. The link is good for one use and expires in ten minutes. The server reads connected in mcp_connections once that lands, and the platform renews the grant in the background from then on. Signing in again to a connected server replaces the grant rather than adding a second one.

If the provider requires a client you registered yourself, pass client_id and client_secret alongside server_name. If the server publishes no usable sign-in metadata, the request fails with mcp_discovery_failed; configure that server with a header instead.

GET .../connections lists the sign-ins established for the environment, oldest first. DELETE .../connections/{connection_id} discards the stored grant and returns the server to disconnected. The provider is not told, so revoke the grant there too if you want it gone everywhere.

Sessions reach an OAuth server through the platform, which unseals the token for each request and forwards the call. The VM the agent works on never receives the token, and the agent cannot tell an OAuth server from any other. needs_reconnect means the grant stopped working, revoked upstream or expired with no way to renew: sign in again to repair it, and no session can use the server until you do.

Wait For The Base Snapshot

The environment response includes base_snapshot. While generation runs, its status is generating. A successful capture makes the new snapshot ready. A failed first build sets failed. A failed rebuild with an older usable snapshot returns to ready on the previous ID and sets base_snapshot.error; check that field before treating ready as a successful rebuild.

while true; do
  status=$(curl -s "$WALLFACER_API/accounts/$ACCOUNT_ID/environments/$ENVIRONMENT_ID" \
    -H "Authorization: Bearer $WALLFACER_TOKEN" \
    | jq -r 'if .data.base_snapshot.error != null then "failed" else .data.base_snapshot.status // "none" end')

  echo "snapshot: $status"

  case "$status" in
    ready) break ;;
    failed) exit 1 ;;
    *) sleep 10 ;;
  esac
done

A ready base snapshot is what makes later VM and session boots fast.

Update Carefully

Patching the manifest starts a new snapshot generation cycle. Manifest-changing edits can be rejected while generation is already in flight.

Use the snapshot and log routes to inspect failed generations rather than repeatedly patching the environment. See Snapshots and Logs.