Harthelm Docs
Guides

Call the API

Authentication, errors, idempotency, ETags, operations, pagination and events.

Last updated 2026-10-19

Everything the console and hart do goes through the REST API, so you can do it too. This guide walks through the conventions with curl; every operation is in the REST API reference.

Terminal
export HART_API=https://console.example.com/api/v1
export HART_TOKEN=hart_…

Authenticate

Create an API key under Account settings → API keys, or use the one hart login made. Send it as a bearer token:

Terminal
curl -s -H "Authorization: Bearer $HART_TOKEN" "$HART_API/me"

Harthelm-Authorization: Bearer … works the same. A key acts as you, with your roles; a request it may not make answers 403. Requests with a key need no CSRF header. A few operations, such as GET /version, need no key at all.

Every response carries Harthelm-Version with the server's version.

Read lists

Lists are paged by cursor: limit (1 to 200, default 50) and cursor in, items and next_cursor out.

Terminal
curl -s -H "Authorization: Bearer $HART_TOKEN" "$HART_API/workspaces?team=7&limit=20"

When next_cursor is present, pass it as cursor for the next page; when it is missing, you have everything.

Create things once

Creates accept an Idempotency-Key header. Repeating a request with the same key within 24 hours returns the first result instead of creating a second thing, so a retry after a timeout is safe:

Terminal
curl -s -X POST "$HART_API/workspaces" \
  -H "Authorization: Bearer $HART_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-shop-$(date +%Y%m%d)" \
  -d '{"team_id": 7, "name": "shop", "plan": "ide-s", "base_image": "ubuntu"}'

GET /plans and GET /base-images list the plan and base image ids your installation offers; ubuntu stands for one of them.

Follow long-running actions

Actions that outlive the request, such as creating a workspace or a Sync, answer 202 with an operation. Its URL is in the Location header, and the body has it too:

202 Accepted
{
  "workspace": { "id": 42, "state": "creating" },
  "operation": { "id": "b6a7c1d2-…", "kind": "workspace.create", "state": "running", "detail": "Configuring storage" }
}

Poll it until state is succeeded or failed:

Terminal
curl -s -H "Authorization: Bearer $HART_TOKEN" "$HART_API/operations/b6a7c1d2-8e4f-4a3b-9c10-5d2e7f8a9b01"

A failed operation carries an error in the same problem format as below. Operations stay readable for 7 days.

Change things safely

Resources you can change return an ETag. A change sends it back as If-Match, so it never overwrites a change someone else made in between:

Terminal
etag=$(curl -s -o /dev/null -D - -H "Authorization: Bearer $HART_TOKEN" "$HART_API/workspaces/42" \
  | awk 'tolower($1)=="etag:" {print $2}' | tr -d '\r')
curl -s -X PATCH "$HART_API/workspaces/42" \
  -H "Authorization: Bearer $HART_TOKEN" \
  -H "Content-Type: application/json" \
  -H "If-Match: $etag" \
  -d '{"idle_timeout_minutes": 30}'

A stale ETag answers 412; read the resource again and repeat the change. A missing If-Match answers 428.

Read errors

Errors are RFC 9457 problem documents, application/problem+json:

409 Conflict
{
  "type": "urn:harthelm:problem:plan-changed",
  "title": "Conflict.",
  "status": 409,
  "detail": "The file changed since the plan was made. Plan again."
}

Branch on type: it is about:blank or a URN urn:harthelm:problem:<name>, and each operation lists the ones it returns. Validation problems add errors, one per field, with a JSON pointer, a message and, for harthelm.yml, the line. The problem types page lists them all.

Listen to events

GET /events is a stream of server-sent events about everything you may see; ?team=7 narrows it to one team:

Terminal
curl -sN -H "Authorization: Bearer $HART_TOKEN" "$HART_API/events?team=7"

Each event has an id, an event type such as workspace.updated or operation.updated, and JSON data. A comment line : ping comes every 25 seconds. There is no replay: the first event is always ready, and after every ready, also after a reconnect, read again what you show.

The API on your installation

Every installation serves its own description at /api/v1/openapi.yaml and a browsable reference at /api/docs. Use the description to generate a client for your language.

Next steps

On this page