# TodoBud API guide

Production base URL: `https://todobud.com/api/v1/`

For agents using the CLI, start with the [CLI & agent guide](https://todobud.com/cli/). Discovery instructions are at [llms.txt](https://todobud.com/llms.txt).

Create a managed key under **Profile → API Keys**. Never put the key in a URL, log, activity message, or note.

Interactive OpenAPI UI lives at `/api/docs/` (staff only). This page is the public, machine-readable guide.

## Authentication

Send the key as:

```http
Authorization: Api-Key tdb_<prefix>_<secret>
```

Or:

```http
X-API-Key: tdb_<prefix>_<secret>
```

Scopes:

| Scope | Allows |
| --- | --- |
| `todos:read` | `GET`, `HEAD`, `OPTIONS` |
| `todos:write` | `POST`, `PUT`, `PATCH`, `DELETE` |

`GET /api/v1/workspaces/current/` accepts either scope for target resolution.
Workspace listing, retrieval, and content reads still require `todos:read`.

Session auth also works. API-key requests are rate-limited to about 120/minute per key and 300/minute per user.

Expected errors: `400` invalid input, `401` bad/missing key, `403` missing scope or write not enabled, `404` another user's resource, `429` rate limit.

### TodoBud CLI authorization

The public TodoBud CLI uses OAuth 2.0 authorization code with PKCE and a
loopback redirect, implemented by [django-oauth-toolkit](https://django-oauth-toolkit.readthedocs.io/).
Headless clients use the OAuth device authorization grant. This is OAuth 2.0,
not OpenID Connect: the CLI receives access and refresh tokens, not an ID token.

```http
Authorization: Bearer <access_token>
```

Access tokens last 15 minutes. Refresh tokens rotate, expire after 30 days
without use, and have a 90-day absolute lifetime. Users can revoke one or every
CLI session under **Profile → Authorized CLIs**. Password reset and account
deactivation revoke all CLI sessions.

Workspace-scoped requests identify the workspace on the request, not from
session "active workspace." Send `X-Workspace: <id-or-slug>` or
`?workspace=` to select scope explicitly. Reads and writes without either use
your personal workspace. Invalid, empty, inactive, or inaccessible explicit
identifiers return `404`; conflicting header/query identifiers return `400`.
Team access is disabled until release, including for team members and staff.
Write bodies must not supply `workspace` /
`workspace_id`; the server assigns it.

OAuth endpoints live under `/o/`:

| Endpoint | Purpose |
| --- | --- |
| `GET/POST authorize/` | Browser authorization with PKCE S256 |
| `POST device-authorization/` | Start device authorization |
| `GET/POST device/` | Approve a device code in the browser |
| `POST token/` | Exchange or refresh a token |
| `POST revoke_token/` | Revoke an access or refresh token |

## Todos — `/api/v1/todos/`

Full CRUD. List responses are paginated (`page_size` default 50, max 100). Follow `next` until null.

### Fields

| Field | Type | Writable | Notes |
| --- | --- | --- | --- |
| `id` | int | no | |
| `title` | string | yes | Required; cannot be blank |
| `body` | string | yes | Markdown notes; default `""` |
| `status` | string | yes | `T`, `IP`, `D`, or `C` (default `T`) |
| `status_label` | string | no | Human-readable status |
| `priority` | string | yes | `P0`, `P1`, `P2`, or `P3` (default `P2`) |
| `creation_source` | string | no | Set to `api` when created via API |
| `project` | int \| null | yes | Project id in the selected workspace |
| `workspace` | int | no | Request workspace; assigned by the server |
| `due_at` | date \| null | yes | `YYYY-MM-DD` |
| `created_at` | datetime | no | |
| `updated_at` | datetime | no | |
| `status_last_updated_at` | datetime \| null | no | Set when status changes |

Status values: `T` Todo, `IP` In Progress, `D` Done, `C` Cancelled.

Priority values: `P0` Urgent, `P1` High, `P2` Normal, `P3` Low.

### List filters

| Query param | Description |
| --- | --- |
| `status` | `T`, `IP`, `D`, or `C` |
| `priority` | `P0`–`P3` |
| `project` | Project id |
| `due_before` | Inclusive `YYYY-MM-DD` |
| `due_after` | Inclusive `YYYY-MM-DD` |
| `updated_after` | ISO-8601 datetime |
| `ordering` | `created_at`, `updated_at`, `due_at`, `priority` (prefix `-` for desc). Default `-updated_at` |
| `page` / `page_size` | Pagination |

### Examples

Provide your managed key through `TODOBUD_API_KEY`. These examples use your personal workspace. Replace todo ID `123` and sample PR URLs with real values.

```bash
curl -H "Authorization: Api-Key $TODOBUD_API_KEY" \
  'https://todobud.com/api/v1/todos/?status=T&page_size=100'

curl -X POST -H "Authorization: Api-Key $TODOBUD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Example","priority":"P2"}' \
  https://todobud.com/api/v1/todos/

curl -X PATCH -H "Authorization: Api-Key $TODOBUD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"D"}' \
  https://todobud.com/api/v1/todos/123/
```

Creating or updating a todo records activity automatically (`created`, `status`, or `updated`).

## Activities — `/api/v1/activities/`

`GET` and `POST` only (no update/delete). List all of your todos' activities, or filter with `?todo=<id>`.

### Fields

| Field | Type | Writable | Notes |
| --- | --- | --- | --- |
| `id` | int | no | |
| `todo` | int | yes | Required; must be your todo |
| `kind` | string | yes | See kinds below |
| `source` | string | no | Always `api` for API-created rows |
| `message` | string | yes | Free text |
| `url` | string | yes | Required when `kind` is `link` |
| `note` | int \| null | yes | Required when `kind` is `note` |
| `note_title` | string | no | |
| `metadata` | object | yes | Arbitrary JSON; default `{}` |
| `actor_name` | string | no | Username of the actor |
| `created_at` | datetime | no | |

Kinds you typically create:

| `kind` | Meaning | Extra fields |
| --- | --- | --- |
| `comment` | Free-text comment from anyone | `message` |
| `link` | Linked code / PR | `url` required |
| `note` | Linked note | `note` required |

System-recorded kinds (usually read-only): `created`, `updated`, `status`.

### Examples

```bash
curl -X POST -H "Authorization: Api-Key $TODOBUD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"todo":123,"kind":"comment","message":"Implemented and tested."}' \
  https://todobud.com/api/v1/activities/

curl -X POST -H "Authorization: Api-Key $TODOBUD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"todo":123,"kind":"link","url":"https://github.com/org/repo/pull/42"}' \
  https://todobud.com/api/v1/activities/

curl -H "Authorization: Api-Key $TODOBUD_API_KEY" \
  'https://todobud.com/api/v1/activities/?todo=123'
```

## Projects — `/api/v1/projects/`

Full CRUD. Scoped to the selected workspace; personal by default.

### Fields

| Field | Type | Writable | Notes |
| --- | --- | --- | --- |
| `id` | int | no | |
| `title` | string | yes | Required; cannot be blank |
| `body` | string | yes | Notes; default `""` |
| `status` | string | yes | `T`, `IP`, `D`, or `C` (default `T`) |
| `due_at` | date \| null | yes | `YYYY-MM-DD` |
| `workspace` | int | no | Request workspace; assigned by the server |
| `creation_source` | string | no | `api` when created via API |
| `created_at` | datetime | no | |
| `updated_at` | datetime | no | |

### Example

```bash
curl -X POST -H "Authorization: Api-Key $TODOBUD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Launch","status":"IP"}' \
  https://todobud.com/api/v1/projects/
```

## Notes — `/api/v1/notes/`

Full CRUD. Markdown `body`; response includes read-only `rendered_body` (HTML).

### Fields

| Field | Type | Writable | Notes |
| --- | --- | --- | --- |
| `id` | int | no | |
| `title` | string | yes | Required |
| `body` | string | yes | Markdown source |
| `rendered_body` | string | no | HTML |
| `todo` | int \| null | yes | Attach to at most one of todo / project |
| `project` | int \| null | yes | |
| `workspace` | int | no | Request workspace; assigned by the server |
| `created_at` | datetime | no | |
| `updated_at` | datetime | no | |

### Example

```bash
curl -X POST -H "Authorization: Api-Key $TODOBUD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Roadmap","body":"# Roadmap\n\nMarkdown source","todo":123}' \
  https://todobud.com/api/v1/notes/
```

## Typical agent flow

1. List open todos: `GET /api/v1/todos/?status=T` or `status=IP`.
2. Optionally create or pick a project: `POST /api/v1/projects/`.
3. Create or update a todo: `POST` / `PATCH /api/v1/todos/`.
4. Post progress: `POST /api/v1/activities/` with `kind=comment`. (`agent_update` is still accepted as a deprecated alias.)
5. Link a PR: `POST /api/v1/activities/` with `kind=link` and `url`.
6. Mark done: `PATCH /api/v1/todos/<id>/` with `{"status":"D"}`.
