# workworkwork agent API

workworkwork is a project-management tool that renders issues as a DAG,
flowing into outcome nodes. This is the HTTP API for programmatic (agent)
access: list projects, read issues together with what feeds them and what
they feed, and create or update issues and edges.

This is a plain REST API. No MCP server, no client to install — a bearer
token is all you need.

## Getting a token

In the app: **Org settings → Agent access → Create token**. Org owners and
admins only. The raw token is shown exactly once at creation time; only its
hash is stored, so if you lose it, revoke it and make a new one.

A token is scoped to one org (every project in it) and carries one of two
roles, chosen at creation:

- **Read + write** (`member`) — everything below.
- **Read only** (`viewer`) — GET endpoints only; writes get `403`.

Every write made through a token is attributed to a dedicated bot identity
named after the token (e.g. "Alex's agent"), not to the human who created
it — it shows up that way in the activity feed and in `created_by` on the
rows it touches.

## Base URL

Same host as the rest of the API: `https://api.workworkwork.alknemeyer.xyz`
in production (`http://localhost:8091` against a local dev server).

## Auth

```
Authorization: Bearer wwak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

A missing, malformed or revoked token gets `401`.

## Concepts

- **Issues are addressed by display key** (`WWW-42`), not by internal id —
  the same key you see in the UI. `WWW` is the project's prefix, returned
  by `GET /projects`.
- **Tickets don't have a priority field.** Why a ticket matters is whatever
  outcome it flows into — that's what `fed_by` / `feeds_into` on
  `GET /projects/:slug/issues/:key` are for: the issues immediately
  upstream and downstream of this one, each with the `justification` text
  explaining that specific edge. Outcomes are issues with `is_outcome: true`
  (see `GET /projects/:slug/outcomes`).
- **Edges are directed**: `source` feeds `target`. `justification` is a free
  text field answering "why does source matter to target".

## Endpoints

### `GET /api/agents/v1/org`

The token's own org — there's no way to list orgs beyond the one a token
is scoped to.

```json
{ "slug": "acme", "name": "Acme Inc" }
```

### `GET /api/agents/v1/projects`

List the projects in the token's org.

```sh
curl -H "Authorization: Bearer $TOKEN" \
  https://api.workworkwork.alknemeyer.xyz/api/agents/v1/projects
```

```json
[{ "slug": "core", "name": "Core product", "prefix": "WWW" }]
```

### `GET /api/agents/v1/projects/:slug/issues`

List issues in a project. Query params (all optional): `status` (one of
`backlog`, `selected for development`, `in progress`, `review`, `done`,
`abandoned`),
`is_outcome` (`true`/`false`), `label`, `limit` (default 100, max 200),
`offset`.

```json
[
	{
		"key": "WWW-12",
		"title": "Add SSO support",
		"status": "in progress",
		"is_outcome": false,
		"description": "...",
		"labels": ["auth"],
		"reporter": "Alice",
		"responsible": "Bob",
		"created": "2026-01-05 10:12:00.000Z"
	}
]
```

### `GET /api/agents/v1/projects/:slug/outcomes`

Same shape, filtered to `is_outcome: true`.

### `GET /api/agents/v1/projects/:slug/issues/:key`

A single issue plus its direct graph neighborhood.

```json
{
	"key": "WWW-12",
	"title": "Add SSO support",
	"status": "in progress",
	"is_outcome": false,
	"description": "...",
	"labels": ["auth"],
	"reporter": "Alice",
	"responsible": "Bob",
	"created": "2026-01-05 10:12:00.000Z",
	"fed_by": [
		{
			"key": "WWW-7",
			"title": "Design auth flows",
			"status": "done",
			"is_outcome": false,
			"justification": "SSO needs the flows defined first"
		}
	],
	"feeds_into": [
		{
			"key": "WWW-40",
			"title": "Enterprise launch",
			"status": "backlog",
			"is_outcome": true,
			"justification": "Enterprise customers require SSO"
		}
	]
}
```

### `POST /api/agents/v1/projects/:slug/issues`

Create an issue. Body:

```json
{
	"title": "string, required",
	"description": "string, optional",
	"status": "optional, defaults to backlog",
	"is_outcome": "boolean, optional, defaults to false",
	"labels": ["optional", "string array"],
	"reporter": "string, optional",
	"responsible": "string, optional"
}
```

Returns the created issue in the same shape as the list endpoint. Requires
a `member`-role token.

### `PATCH /api/agents/v1/projects/:slug/issues/:key`

Partial update — send only the fields you want to change, from the same
set as create (plus `title`). Requires a `member`-role token.

### `POST /api/agents/v1/projects/:slug/edges`

Create an edge. Body: `{ "source": "WWW-7", "target": "WWW-12", "justification": "..." }`,
both endpoints keys in the same project. Returns
`{ "id": "...", "source": "WWW-7", "target": "WWW-12", "justification": "..." }` —
`id` is what `PATCH /edges/:id` takes, since edges have no display key of
their own. Requires a `member`-role token.

### `PATCH /api/agents/v1/edges/:id`

Update an edge's justification: `{ "justification": "..." }`. Requires a
`member`-role token.

## Errors

Non-2xx responses are `{ "message": "..." }`. `401` = bad/revoked token,
`403` = read-only token attempting a write (or wrong org), `404` = project,
issue, or edge not found, `400` = validation error.

## Not yet supported

Deleting issues/edges, and rate limiting — none is enforced today, so keep
write volume reasonable.
