Skip to content
github-actions[bot] edited this page Oct 11, 2026 · 3 revisions

API

Homestead has an HTTP API for Home Assistant, scripts and AI agents: read the cluster's status, and start, stop and restart containers and virtual machines. It is reached with an API key, and every key expires, can do only what its scopes allow, and works on this API alone.

The API describes itself: GET /api/v1/openapi.json on your Homestead returns an OpenAPI 3.1 document of every endpoint, its scope, its parameters and the shape of its answer. The same document is in the repository as docs/api/openapi.json, for tools that read a spec from GitHub - load it into Swagger UI, Postman, or an agent's tool list.

Making a key

Settings › Users and access › API keys › + New key (administrators only):

  • Name - what will use it, so you know which to revoke.

  • What it may do - its scopes:

    Scope Allows
    read Read status: the cluster, nodes, containers, VMs, alerts and jobs
    containers:control Start, stop and restart containers
    vms:control Start, stop and restart virtual machines
    logs:read Read containers' logs (AI agents, over MCP)
    containers:manage Deploy, edit, scale and move containers (AI agents, over MCP)
    vms:manage Create, edit and migrate virtual machines (AI agents, over MCP)
    storage:manage Create and resize volumes, snapshot and back them up (AI agents, over MCP)
    network:manage Expose apps and keep the IP address plan (AI agents, over MCP)
    cluster:manage Namespaces and raw Kubernetes objects; each change waits for an admin (AI agents, over MCP)
  • Expires after - a day to a year. There are no keys that never expire.

  • Only from (optional) - addresses or networks it may be used from, such as your Home Assistant's address. A key copied anywhere else is refused. Homestead sees the address a request arrives from. Its Service is a load balancer that, as Kubernetes sets it up by default, rewrites that to the address of the node it came through - on k3s one like 10.42.0.0. Then Homestead cannot tell clients apart, a limit refuses every client, and the wrong-password lockout is shared by everyone coming through that node. The API keys card says so when it is the case, a key's "used … from" is marked node, and a refused key's error says why. Leave Only from empty there. Keeping clients' own addresses is planned.

The key is shown once, as hsk_<id>_<secret>. Homestead keeps only a hash of it; if you lose it, revoke it and make another. Revoking stops a key within a couple of seconds on every copy of Homestead. Removing a user removes their keys.

Edit on a key's row changes its name, scopes, networks, MCP use or expiry (counted again from now). The key itself stays the same, so whatever uses it carries on, and the change takes effect within a couple of seconds. Each change is in the sign-in history.

A scope includes the ones it builds on: every scope includes read, and containers:manage and vms:manage include containers:control and vms:control. The dialog's Read only, Operate and Manage everything set the boxes for you.

The card lists each key's scopes, when it expires, and when and from where it was last used. Making, revoking and refusing keys are recorded in the sign-in history (Events › Sign-ins).

Using a key

Send it in the Authorization header:

curl -H "Authorization: Bearer hsk_…" https://homestead.example.com/api/v1/status
{"version": "2.8.273",
 "nodes": {"total": 3, "ready": 3},
 "containers": {"total": 24, "running": 22, "stopped": 2, "failing": 0},
 "vms": {"total": 4, "running": 3},
 "alerts": {"active": 1, "critical": 0}}

Starting a container:

curl -X POST -H "Authorization: Bearer hsk_…" \
  https://homestead.example.com/api/v1/containers/lab/jellyfin/start
{"ok": true, "action": "start", "warnings": [], "job": null}

Endpoints

Method Path Scope What
GET /api/v1/whoami any key The key making the request, its scopes and expiry
GET /api/v1/status read Counts of nodes, containers, VMs and alerts
GET /api/v1/nodes read Each node: ready, CPU and memory use, pods
GET /api/v1/alerts read What is wrong right now
GET /api/v1/containers read Every container (?namespace= to narrow)
GET /api/v1/containers/{namespace}/{name} read One container
POST /api/v1/containers/{namespace}/{name}/start containers:control Start with one replica
POST /api/v1/containers/{namespace}/{name}/stop containers:control Scale to zero
POST /api/v1/containers/{namespace}/{name}/restart containers:control Restart its pods
GET /api/v1/vms read Every virtual machine (?namespace= to narrow)
GET /api/v1/vms/{namespace}/{name} read One virtual machine
POST /api/v1/vms/{namespace}/{name}/start vms:control Start it
POST /api/v1/vms/{namespace}/{name}/stop vms:control Shut it down (its power button)
POST /api/v1/vms/{namespace}/{name}/restart vms:control Restart it
GET /api/v1/jobs/{id} read A background job an action started
GET /api/v1/openapi.json any key This API's own description

A start goes through the same capacity check as the app. One that cannot fit is refused with 409 and the reasons; one that fits with warnings goes ahead and returns them - the key's control scope stands in for the confirmation the app asks a person for. A VM start that needs a person (setting up missing TPM or EFI state) is refused; do it in the app. Homestead's own containers cannot be stopped through the API.

Errors

Every error is JSON, {"error": "what happened"}:

Status Meaning
400 The request is malformed (a name that cannot be one)
401 No key, or one that is not valid, has expired, or is used from a network it is not allowed
403 The key does not have the scope this needs
404 No such endpoint, container, VM or job
405 That method is not used here
409 Refused: it cannot be done now - not enough room, Homestead itself, or it needs a person
429 Too many refused keys from this address; wait a few minutes (a valid key is never refused this way)

Home Assistant

A sensor for active alerts, and buttons for a container - in configuration.yaml:

sensor:
  - platform: rest
    name: Homestead alerts
    resource: https://homestead.example.com/api/v1/status
    headers:
      Authorization: !secret homestead_api_key
    value_template: "{{ value_json.alerts.active }}"
    json_attributes: [nodes, containers, vms]
    scan_interval: 60

rest_command:
  jellyfin_start:
    url: https://homestead.example.com/api/v1/containers/lab/jellyfin/start
    method: post
    headers:
      Authorization: !secret homestead_api_key
  jellyfin_stop:
    url: https://homestead.example.com/api/v1/containers/lab/jellyfin/stop
    method: post
    headers:
      Authorization: !secret homestead_api_key

and in secrets.yaml:

homestead_api_key: "Bearer hsk_…"

Give the key read and containers:control, and limit it to Home Assistant's address.

AI agents

If the agent speaks the Model Context Protocol (Claude Code, Claude Desktop and others), use AI agents: MCP is the easier way, and adds previews, approvals for anything destructive, and an audit. A key for it is made with Allow AI agents (MCP) ticked. For other agent frameworks the OpenAPI description remains.

Hand the agent a key and the description:

  • the OpenAPI document - GET /api/v1/openapi.json with the key, or docs/api/openapi.json from the repository - lists every operation with an operationId, a summary and its answer's schema, which agent frameworks turn into tools directly;
  • give it read alone until you want it to act, and the control scopes for only what it should touch. GET /api/v1/whoami tells it what it may do.

An action returns a job id when it starts something that takes a while (a VM starting); GET /api/v1/jobs/{id} follows it to succeeded or failed.

What a key can never do

A key works on /api/v1 and nothing else - not on the routes the web app uses - so whatever its scopes, it cannot:

  • manage users or other keys, or change any setting;
  • open a shell on a host, read a Secret, or see Kubernetes objects;
  • act on a linked cluster: a key is for the Homestead that made it.

A key also acts for the administrator who made it, never beyond them: if they are removed their keys are removed with them, and if they are demoted a key keeps only the scopes their role still allows. Refused keys are counted per address, as wrong passwords are: past twenty, a wrong key is answered 429 for a while - a right one never is, so one misbehaving client cannot lock out the rest.

Stability

/api/v1 is versioned. Within it an answer may gain fields but never loses or renames one, and endpoints are not removed. A change that would break a client becomes /api/v2, beside v1.

If Homestead is behind Cloudflare Access, requests through Cloudflare need Access's own credentials too; reach the API on your network, or give the client a Cloudflare service token.

Clone this wiki locally