OOrchard

API Reference

Orchard exposes a REST API for programmatic management of groves.

Base URL

code
http://<your-orchard-host>:8080/api

When running locally via trowel dev-server start, the core API listens on port 7778 instead (http://localhost:7778/api).

Authentication

By default, authentication is disabled — all requests are accepted for local development and resolved to a single shared "local" cultivator; no client-supplied identity header is read. In production deployments with OAuth2 enabled, a JWT token is required instead — requests without a valid Authorization: Bearer token return 401. See the Authentication page for details.

Current Cultivator

code
GET /api/me

Returns the cultivator identified by the request's credentials — used by Canopy and the VS Code extension to show who you are signed in as.

code
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "octocat@example.com",
  "email": "octocat@example.com",
  "provider": "google",
  "avatarUrl": "https://avatars.example.com/octocat.png",
  "displayName": "The Octocat",
  "createdAt": "2026-05-01T12:00:00Z",
  "lastActiveAt": "2026-08-01T12:00:00Z"
}

Groves

List Groves

code
GET /api/groves

Query params: all=true to include CLEARED groves.

Response:

code
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "my-workspace",
    "repositoryUrl": "https://github.com/your-org/your-repo",
    "branch": "main",
    "commitSha": "a1b2c3d4e5f6",
    "state": "FLOURISHING",
    "sshConnectionString": "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /home/user/.ssh/orchard_ed25519 -p 22 cultivator@203.0.113.10",
    "seedling": {
      "id": "661f9511-f30c-52e5-b827-557766551111",
      "state": "SAPLING",
      "ipAddress": "203.0.113.10",
      "sshPort": 22,
      "cpuCores": 2,
      "memoryMb": 2048,
      "diskGb": 20
    },
    "fruits": [
      {
        "id": "772a0622-041d-63f6-c938-668877662222",
        "state": "RIPENING",
        "containerId": "abc123def456",
        "containerName": "my-workspace_app_1",
        "serviceName": "app"
      }
    ],
    "plantedAt": "2026-05-01T12:00:00Z",
    "lastAccessedAt": "2026-05-01T13:00:00Z"
  }
]

Plant Grove

code
POST /api/groves

Headers: Content-Type: application/json

Request body:

code
{
  "repositoryUrl": "https://github.com/your-org/your-repo",
  "branch": "main",
  "name": "my-workspace",
  "machineSize": "small",
  "spec": "auto"
}
FieldRequiredDescription
repositoryUrlYesGit repository URL
branchNoBranch to check out (default: main)
nameNoFriendly name for the grove
machineSizeNosmall, medium, or large (default: small)
specNoauto, devcontainer, or devfile — which workspace-config format to prefer when the repo has both a devcontainer.json and a devfile.yaml (default: auto, which prefers devcontainer.json)

Returns 201 Created with the grove object.

Get Grove

code
GET /api/groves/{groveId}

Clear Grove

code
DELETE /api/groves/{groveId}

Returns 204 No Content.

Get SSH Config

code
GET /api/groves/{groveId}/ssh-config

Returns a plain-text SSH config block for the grove's VM. Returns 503 Service Unavailable if the grove's seedling hasn't been assigned an IP address yet (typically before the grove reaches FLOURISHING).

sshConnectionString and ssh-config point directly at the seedling's SSH port. They're intended for local and dev setups. In production, cultivators connect through the SSH gateway (ssh -p 2222 <grove-id>@<gateway-host>) using a registered SSH key. See Deployment.

Stream Grove Events

code
GET /api/groves/{groveId}/events

A Server-Sent Events stream (text/event-stream) that emits a grove-state-changed event whenever the grove transitions between states. Canopy and the VS Code extension use this for live status updates.

code
event: grove-state-changed
data: {"groveId":"...","groveName":"my-workspace","previousState":"GROWING","newState":"FLOURISHING","changedAt":"2026-08-01T12:00:05Z"}

Stop Grove

code
POST /api/groves/{groveId}/actions/stop

Stops a grove by tearing down its containers and VM (including the VM's disk), then transitioning it to DORMANT state. Unpushed work on the VM is lost. Returns 200 OK with the updated grove object. If the grove isn't in GROWING or FLOURISHING state, this is a no-op — the response reflects the grove's current, unchanged state.

Start Grove

code
POST /api/groves/{groveId}/actions/start

Starts a dormant grove by re-provisioning its VM and containers. Returns 200 OK with the updated grove object. If the grove isn't in DORMANT state, this is a no-op — the response reflects the grove's current, unchanged state.

The CLI wraps both endpoints: see trowel grove stop / trowel grove start in the CLI Reference.

SSH Keys

SSH public keys registered to the calling cultivator. The SSH gateway accepts any of these keys when that cultivator connects to one of their groves.

Register SSH Key

code
POST /api/ssh-keys
Content-Type: application/json
 
{
  "name": "laptop",
  "publicKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@laptop"
}

Returns 201 Created with the registered key. Returns 400 Bad Request if name or publicKey is blank, or the key isn't in OpenSSH <type> <base64> format.

code
{
  "id": "883b1733-152c-74a7-d049-779988773333",
  "name": "laptop",
  "publicKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@laptop",
  "fingerprint": "SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8",
  "createdAt": "2026-09-29T12:00:00Z"
}

List SSH Keys

code
GET /api/ssh-keys

Returns an array of the calling cultivator's registered keys, in the same shape as above.

Delete SSH Key

code
DELETE /api/ssh-keys/{keyId}

Returns 204 No Content, or 404 Not Found if the key doesn't exist or belongs to another cultivator.

/api/gateway/** endpoints are an internal interface used by the SSH gateway service to resolve groves and keys. They aren't a supported public API.

Bees

Bees are AI coding assistant processes attached to a grove, provisioned over SSH by a BeeKeeper adapter. A grove must be FLOURISHING before a bee can be attached.

beeType accepts any of CLAUDE_CODE, GEMINI, CODEX, KIRO, CUSTOM, OPENCODE, but only OPENCODE has a registered BeeKeeper today. Requests for any other type return 400 Bad Request ("No BeeKeeper registered for bee type: ..."). The others are reserved names for future adapters.

Attach Bee

code
POST /api/groves/{groveId}/bees

Headers: Content-Type: application/json

Request body:

code
{
  "beeType": "OPENCODE",
  "version": "latest",
  "configOverrides": {}
}

Returns 202 Accepted with the bee object in HATCHING state; installation and startup continue asynchronously.

List Bees

code
GET /api/groves/{groveId}/bees

Response:

code
[
  {
    "id": "772a0622-041d-63f6-c938-668877662222",
    "groveId": "550e8400-e29b-41d4-a716-446655440000",
    "type": "OPENCODE",
    "state": "BUZZING",
    "processId": "12345",
    "hatchedAt": "2026-08-01T12:00:00Z",
    "startedAt": "2026-08-01T12:00:05Z",
    "stoppedAt": null
  }
]

Get Bee

code
GET /api/groves/{groveId}/bees/{beeId}

Bee Status Summary

code
GET /api/groves/{groveId}/bees/status

Response:

code
{
  "groveId": "550e8400-e29b-41d4-a716-446655440000",
  "totalBees": 2,
  "byState": {
    "BUZZING": 1,
    "HIBERNATING": 1
  }
}

Wake Bee

code
POST /api/groves/{groveId}/bees/{beeId}/actions/wake

Restarts a HIBERNATING bee's agent process. Returns 200 OK with the updated bee object.

Smoke Bee

code
POST /api/groves/{groveId}/bees/{beeId}/actions/smoke

Shuts down the bee's agent process and transitions it to SMOKED. Returns 200 OK with the updated bee object.

Bee States

StateDescription
HATCHINGBeing installed and configured on the grove's VM
HIBERNATINGAgent CLI installed but process not running
BUZZINGAgent process running
POLLINATINGAgent actively working on a task
SMOKEDStopped or crashed

Prebuilds

Prebuilds (Greenhouse) cache a repository's container image ahead of time so groves plant faster. This subsystem is still maturing.

code
POST /api/prebuilds        # body: { "repositoryUrl": "...", "branch": "main" }
GET  /api/prebuilds        # list prebuilds
GET  /api/prebuilds/{id}   # get a single prebuild

Grove States

StateDescription
PREPARINGInitial setup
PLANTINGVM and container provisioning in progress
GROWINGEnvironment starting up
FLOURISHINGReady — SSH access available
DORMANTStopped — VM and containers destroyed; actions/start re-provisions from the repository
CLEARINGTeardown in progress
CLEAREDDeleted
BLIGHTEDError state

Health

code
GET /api/health
code
{
  "status": "healthy",
  "name": "Orchard",
  "version": "0.4.0"
}
code
GET /api/health/ready

Returns 200 when the configured seedling provider (QEMU, AWS, etc.) is available, 503 otherwise.

Error Responses

The API returns standard HTTP status codes. Handled errors (validation failures, invalid state transitions, unrecognized types) return a JSON body from a global exception handler:

code
{
  "status": 400,
  "error": "Bad Request",
  "message": "...",
  "timestamp": "2026-08-01T12:00:00Z"
}

Simple not-found lookups (e.g. GET /api/groves/{groveId} for a grove that doesn't exist) return a bare 404 with no body.