API Reference
Orchard exposes a REST API for programmatic management of groves.
Base URL
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
GET /api/meReturns the cultivator identified by the request's credentials — used by Canopy and the VS Code extension to show who you are signed in as.
{
"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
GET /api/grovesQuery params: all=true to include CLEARED groves.
Response:
[
{
"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
POST /api/grovesHeaders: Content-Type: application/json
Request body:
{
"repositoryUrl": "https://github.com/your-org/your-repo",
"branch": "main",
"name": "my-workspace",
"machineSize": "small",
"spec": "auto"
}| Field | Required | Description |
|---|---|---|
repositoryUrl | Yes | Git repository URL |
branch | No | Branch to check out (default: main) |
name | No | Friendly name for the grove |
machineSize | No | small, medium, or large (default: small) |
spec | No | auto, 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
GET /api/groves/{groveId}Clear Grove
DELETE /api/groves/{groveId}Returns 204 No Content.
Get SSH Config
GET /api/groves/{groveId}/ssh-configReturns 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
GET /api/groves/{groveId}/eventsA 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.
event: grove-state-changed
data: {"groveId":"...","groveName":"my-workspace","previousState":"GROWING","newState":"FLOURISHING","changedAt":"2026-08-01T12:00:05Z"}
Stop Grove
POST /api/groves/{groveId}/actions/stopStops 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
POST /api/groves/{groveId}/actions/startStarts 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
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.
{
"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
GET /api/ssh-keysReturns an array of the calling cultivator's registered keys, in the same shape as above.
Delete SSH Key
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
POST /api/groves/{groveId}/beesHeaders: Content-Type: application/json
Request body:
{
"beeType": "OPENCODE",
"version": "latest",
"configOverrides": {}
}Returns 202 Accepted with the bee object in HATCHING state; installation and startup continue asynchronously.
List Bees
GET /api/groves/{groveId}/beesResponse:
[
{
"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
GET /api/groves/{groveId}/bees/{beeId}Bee Status Summary
GET /api/groves/{groveId}/bees/statusResponse:
{
"groveId": "550e8400-e29b-41d4-a716-446655440000",
"totalBees": 2,
"byState": {
"BUZZING": 1,
"HIBERNATING": 1
}
}Wake Bee
POST /api/groves/{groveId}/bees/{beeId}/actions/wakeRestarts a HIBERNATING bee's agent process. Returns 200 OK with the updated bee object.
Smoke Bee
POST /api/groves/{groveId}/bees/{beeId}/actions/smokeShuts down the bee's agent process and transitions it to SMOKED. Returns 200 OK with the updated bee object.
Bee States
| State | Description |
|---|---|
HATCHING | Being installed and configured on the grove's VM |
HIBERNATING | Agent CLI installed but process not running |
BUZZING | Agent process running |
POLLINATING | Agent actively working on a task |
SMOKED | Stopped or crashed |
Prebuilds
Prebuilds (Greenhouse) cache a repository's container image ahead of time so groves plant faster. This subsystem is still maturing.
POST /api/prebuilds # body: { "repositoryUrl": "...", "branch": "main" }
GET /api/prebuilds # list prebuilds
GET /api/prebuilds/{id} # get a single prebuildGrove States
| State | Description |
|---|---|
PREPARING | Initial setup |
PLANTING | VM and container provisioning in progress |
GROWING | Environment starting up |
FLOURISHING | Ready — SSH access available |
DORMANT | Stopped — VM and containers destroyed; actions/start re-provisions from the repository |
CLEARING | Teardown in progress |
CLEARED | Deleted |
BLIGHTED | Error state |
Health
GET /api/health{
"status": "healthy",
"name": "Orchard",
"version": "0.4.0"
}GET /api/health/readyReturns 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:
{
"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.