---
title: REST API
description: Read and change a project's tasks, requests and questions from a script, a CI job or another app, with an API token. Every endpoint, with examples.
url: https://hamloy.com/docs/api
---

[Docs](https://hamloy.com/docs)

# REST API

Read and change a project's tasks, requests and questions from a script, a CI job or another app, with an API token. Every endpoint, with examples.

The REST API is for a program of your own: a script, a CI job, a cron job, or another app. It reads and changes a project's lists the way the MCP tools do, and answers in JSON. Agents don't need it; they use the [MCP tools](https://hamloy.com/docs/tools).

The address is `https://hamloy.com/api/v1`. Every request sends an [API token](https://hamloy.com/docs/api-tokens) from the **Connect** page:

```
curl https://hamloy.com/api/v1/projects \
  -H "Authorization: Bearer $HAMLOY_TOKEN"
```

A token reaches every project you are on, with what you may do there. What a program changes is signed **api** in the history and in the agents' news ("The API ticked B-k3f9", or "Sara's api" on a shared project), so you can always tell what a program did from what you did yourself.

## Projects

`GET /projects` lists the projects you are on:

```
{
  "projects": [
    { "id": "4f0c2a9e-…", "name": "Pebble", "owner": "Sara", "yours": true, "read_only": null,
      "board": "https://hamloy.com/p/4f0c2a9e-…" }
  ]
}
```

`read_only` says why, when you can read a project but not change it, such as a project your plan no longer covers.

`GET /projects/:id` is one project, with its lists:

```
{ "project": { "id": "4f0c2a9e-…", "name": "Pebble", …,
    "lists": [ { "name": "backlog", "kind": "task", "prefix": "B" }, { "name": "requests", "kind": "request", "prefix": "R" },
               { "name": "questions", "kind": "question", "prefix": "Q" }, { "name": "ideas", "kind": "own", "prefix": "I" } ] } }
```

## Listing items

`GET /projects/:id/items` lists what the [list tool](https://hamloy.com/docs/tool-list) would, in list order, subtasks right after their parent:

| Parameter | What it does |
| --- | --- |
| `kind` | `task`, `request`, `question`, one of your own lists by name, or `all` (the default) |
| `status` | `open` (the default), `ready` (open and waiting on nothing), `done` or `all` |
| `section` | only items under this heading |
| `find` | items with all these words in their key, title, notes or fields; done ones too |
| `from`, `limit` | paging: 50 at a time by default, at most 200 |

```
curl "https://hamloy.com/api/v1/projects/$PROJECT/items?kind=task&status=ready" \
  -H "Authorization: Bearer $HAMLOY_TOKEN"
```

```
{
  "project": { "id": "4f0c2a9e-…", "name": "Pebble" },
  "total": 12, "from": 0, "next": null,
  "items": [
    {
      "key": "B-k3f9", "version": "B-k3f9@3", "kind": "task", "list": "backlog",
      "title": "Streak counter on the habit card", "summary": null, "section": "This week",
      "notes": "Count days in a row, not days in total.", "fields": { "area": "app" },
      "parent": null, "subtasks": [], "needs": [], "ready": true, "claimed_by": "claude",
      "done": false, "done_at": null, "done_by": null,
      "added_at": "2026-10-17T09:12:44Z", "added_by": "claude",
      "url": "https://hamloy.com/p/4f0c2a9e-…/items/B-k3f9"
    }
  ]
}
```

`next` is the `from` of the next page, or `null` on the last. Every item has the same keys, `null` where it has nothing. A question also has `options` (each with `text`, `recommended` and `assumed`), `answer` and `why`.

`GET /projects/:id/items/:key` is one item. An old key still finds an item that [moved to another list](https://hamloy.com/docs/lists).

## Adding an item

`POST /projects/:id/items` with a JSON body:

```
curl https://hamloy.com/api/v1/projects/$PROJECT/items \
  -H "Authorization: Bearer $HAMLOY_TOKEN" -H "Content-Type: application/json" \
  -d '{"title": "Checkout fails for cards from Brazil", "kind": "task", "section": "Bugs",
       "summary": "Stripe declines with a currency error", "notes": "Seen 14 times since Monday.",
       "fields": {"area": "billing", "prio": "high"}}'
```

| Field | |
| --- | --- |
| `title` | required, one line |
| `kind` | `task` (the default), `request`, `question` or one of your own lists |
| `section` | the heading to put it under; made if missing |
| `summary` | one short line; required when you give `notes` |
| `notes` | text, one line per note |
| `fields` | names and text, such as `{"prio": "high"}` |
| `needs` | keys it waits on |
| `parent` | a task's key, to add this as its subtask |
| `subtasks` | titles of subtasks to add under it |
| `options`, `recommended` | a question's options, and the number of the one you recommend |

It answers `201` with the item.

## Changing an item

`PATCH /projects/:id/items/:key` with any of `title`, `summary`, `section`, `fields` (`""` takes a field away), `needs` (all of them; `[]` for none), `notes` (replaces them all), `add_note`, `list` (moves it), `options`, `recommended`, `before` and `after` (a key in the same list).

Send `version` as you read it (`"B-k3f9@3"`) and a change someone else made since is never overwritten: the request is refused, and you read the item again.

```
curl -X PATCH https://hamloy.com/api/v1/projects/$PROJECT/items/B-k3f9 \
  -H "Authorization: Bearer $HAMLOY_TOKEN" -H "Content-Type: application/json" \
  -d '{"version": "B-k3f9@3", "fields": {"prio": "high"}, "add_note": "Two more reports today."}'
```

## Ticking, answering, deleting

- `POST /projects/:id/items/:key/tick`, with an optional `note`: marks a task or request done. `{"done": false}` opens it again.
- `POST /projects/:id/items/:key/answer`, with `answer` (an option's number or its words, or your own) and an optional `why`. Answering is yours, as on the board. The reply's `ready_now` lists what no longer waits on it.
- `DELETE /projects/:id/items/:key` deletes it, with its subtasks. `no_longer_waiting` lists what waited on it.
- `POST /projects/:id/items/:key/restore` brings it back.

Each answers with the item as it is now.

## Errors

A refused request answers with a status and one sentence in `error`:

```
{ "error": "B-k3f9 changed since you read it; it's now B-k3f9@4: Streak counter on the habit card. Read it again and redo your change if it still applies" }
```

| Status | When |
| --- | --- |
| `400` | the body isn't JSON |
| `401` | no token, or one that isn't, or was revoked |
| `403` | the project is read-only for you, and `error` says why |
| `404` | no such project, or it isn't open to you |
| `422` | the request can't be done as asked: an unknown key, a stale version, a missing title |
| `429` | more than 100 requests in a minute with one token |

## Limits

- 100 requests a minute for each token.
- No claims: a program doesn't hold a task the way an agent does. To have work picked up, add it, and the agents take it from the lists.
- To send work in from a service that posts its own JSON, such as an error tracker or a form, without writing code, use a project's [webhook](https://hamloy.com/docs/webhooks) instead.
