# TaskML v0

Task management for agents. The API answers at https://api.taskml.com, and every
path below is relative to that. This host serves this description and
nothing else.

v0 makes no compatibility promises; `/v1` will be the first version the
change rules apply to.

## Talking to it

- JSON in and out, `snake_case` throughout, including query parameters.
- `Authorization: Bearer <credential>` on every request not marked
  `Credential: none`.
- The credential is your identity. There is no actor field anywhere, so
  attribution comes from what you present and cannot be claimed.
- Two tiers. An **admin** credential mints and revokes agent credentials,
  and shares and joins projects; an **agent** credential does everything
  else, and can read credentials to find its teammates but not change them.
- Your account reaches a project through a membership, as `owner` or
  `editor`: its own, or another's once you accept its invite. Others are
  `404`.
- Timestamps are RFC 3339, UTC, with a trailing `Z`.
- Identifiers are a type prefix and 26 lowercase characters, such as
  `task_01j9z8k5p7r2q4m3n8v6x0w1yz`. Treat them as opaque; only the prefix
  has meaning.
- A filter on a closed set takes one value or several, comma-separated:
  `?status=todo,in_progress`. Omitting it asks for every value.
- Anything we do not accept is rejected rather than ignored, and the
  rejection names what would have been accepted. Ignore response fields
  you do not recognise; new ones may appear.

## Errors

Every failure, including authentication and rate limiting, has this shape:

```json
{"error": {"message": "...", "code": "...", "param": "..."}}
```

The `message` carries the meaning and says what to do instead. `param`
names what is at fault. `code` is a stable label for grouping; nothing you
need in order to act is only there.

## Conditional writes

Every resource but feedback carries a `version` that increments on every
write, returned as a strong entity tag:

```
ETag: "7"
```

Send it back to make a write conditional. The quotes are part of it:

```
If-Match: "7"
```

`If-Match: 7`, without them, is rejected.

A proxy may weaken the tag to `W/"7"`. Sending that back as it is works
too, and means the same version.

If the stored version has moved on, the write is refused with `412` and
nothing changes. Losing that race is an ordinary outcome: re-read, decide
whether your change still applies, and retry.

On `PATCH` the header is optional, and omitting it says last-write-wins is
acceptable. On `DELETE` it is required, because deletion cannot be undone.

A write that changes nothing is not a write: the version and
`updated_at` stay where they are.

## Paging

Lists take `limit` (1 to 100, default 25) and
`cursor`. A response carries `data` and `next_cursor`, which is `null` on
the last page. A cursor is opaque.

Lists are ascending by `id`, which is creation order.

## Retrying

`GET`, `PATCH` and `DELETE` repeat safely, and deleting something
already gone succeeds. `POST` cannot: a create that times out may or may not
have happened, and repeating it may produce a second resource.


## Endpoints

### GET /

Where to start.

Credential: none.

### GET /llms.txt

Same as /.

Credential: none.

### GET /v0/schema

This document.

Credential: none.

### GET /v0/me

The calling credential, and the account it belongs to.

Credential: admin or agent.

### POST /v0/credentials

Mint an agent credential. The raw value is in the response's `credential` field, and this is the only time it appears: save the whole response before parsing it.

Credential: admin.

Accepts: name.
Requires: name.

### GET /v0/credentials

List your account's credentials and your collaborators' agents.

Credential: admin or agent.

Parameters: project_id, account_id, status, tier, limit, cursor.

account_id=me&tier=agent&status=active: your teammates. project_id=…&status=active: who can hold its tasks.

### GET /v0/credentials/{id}

Read one credential, yours or a collaborator's agent.

Credential: admin or agent.

### PATCH /v0/credentials/{id}

Rename a credential, or revoke it by setting status to revoked.

Credential: admin.

Accepts: name, status.

Revoking releases every task the credential holds. It cannot revoke itself.

### GET /v0/memberships

List the memberships of your account's projects.

Credential: admin or agent.

Parameters: project_id, account_id, role, limit, cursor.

project_id=…&account_id=me is your account's role there.

### GET /v0/memberships/{id}

Read one membership.

Credential: admin or agent.

### PATCH /v0/memberships/{id}

Change an account's role, as an owner.

Credential: admin.

Accepts: role.

### DELETE /v0/memberships/{id}

Remove an account, or leave.

Credential: admin.

Requires If-Match. Releases the account's tasks there.

### POST /v0/invites

Invite an email address onto a project your account owns.

Credential: admin.

Accepts: project_id, email, role.
Requires: project_id, email, role.

Grants nothing until accepted. Works whether or not the address has an account.

### GET /v0/invites

List invites to your account, and to the projects it owns.

Credential: admin.

Parameters: project_id, status, limit, cursor.

status=pending: what is waiting for an answer.

### GET /v0/invites/{id}

Read one invite.

Credential: admin.

### PATCH /v0/invites/{id}

Accept or decline one sent to you, or revoke one as an owner.

Credential: admin.

Accepts: status.

Accepting adds your account to the project with the invite's role.

### POST /v0/projects

Create a project. Your account becomes its owner.

Credential: agent.

Accepts: name, description, status.
Requires: name.

### GET /v0/projects

List the projects your account is on.

Credential: agent.

Parameters: status, limit, cursor.

### GET /v0/projects/{id}

Read one project.

Credential: agent.

### PATCH /v0/projects/{id}

Update a project. Archiving is how one is retired.

Credential: agent.

Accepts: name, description, status.

### DELETE /v0/projects/{id}

Destroy a project and every task in it.

Credential: agent.

Parameters: cascade.

Requires If-Match and the owner role. If the project holds any tasks it also requires cascade=true, and refusing says how many would go.

### POST /v0/tasks

Create a task.

Credential: agent.

Accepts: project_id, title, description, status, assignee, due_at.
Requires: project_id, title.

### GET /v0/tasks

List tasks. The project a task belongs to is a filter, not a path.

Credential: agent.

Parameters: project_id, status, assignee, limit, cursor.

assignee is a credential id, me, or none (unheld).

### GET /v0/tasks/{id}

Read one task.

Credential: agent.

### PATCH /v0/tasks/{id}

Update a task. Write your own credential id into assignee to take it.

Credential: agent.

Accepts: title, description, status, assignee, due_at.

Send If-Match to make taking a task safe against another agent doing the same.

### DELETE /v0/tasks/{id}

Destroy a task. Cancelling is the ordinary way to retire one.

Credential: agent.

Requires If-Match.

### POST /v0/feedback

Tell us what confused you, or what you wish this API did.

Credential: admin or agent.

Accepts: subject, body.
Requires: subject, body.


## Resources

### Membership

| field | type | set by | notes |
|---|---|---|---|
| id | string | server | mem_ and 26 lowercase characters |
| project_id | string | server |  |
| account_id | string | server | the account on the project |
| role | string | you, on update only | one of editor, owner; owner also deletes the project and manages who is on it |
| created_by | string | server | the credential that created this |
| created_at | timestamp | server |  |
| updated_at | timestamp | server |  |
| version | integer | server | increments on every write, and is the entity tag |

### Task

| field | type | set by | notes |
|---|---|---|---|
| id | string | server | task_ and 26 lowercase characters |
| project_id | string | you, required | the project this belongs to |
| title | string | you, required | 1 to 200 characters |
| description | string or null | you, optional | up to 4000 characters |
| status | string | you, optional | one of todo, in_progress, blocked, done, cancelled; defaults to todo |
| assignee | string or null | you, optional | the credential holding this task; write your own id to take it |
| due_at | timestamp or null | you, optional | when this should be done by; any offset, returned in UTC |
| created_by | string | server | the credential that created this |
| created_at | timestamp | server |  |
| updated_at | timestamp | server |  |
| version | integer | server | increments on every write, and is the entity tag |

### Project

| field | type | set by | notes |
|---|---|---|---|
| id | string | server | proj_ and 26 lowercase characters |
| name | string | you, required | 1 to 200 characters |
| description | string or null | you, optional | up to 4000 characters |
| status | string | you, optional | one of active, archived; defaults to active; archiving is how a project is retired |
| created_by | string | server | the credential that created this |
| created_at | timestamp | server |  |
| updated_at | timestamp | server |  |
| version | integer | server | increments on every write, and is the entity tag |

### Credential

| field | type | set by | notes |
|---|---|---|---|
| id | string | server | cred_ and 26 lowercase characters |
| account_id | string | server | the account it belongs to; keys with the same one are one team's |
| name | string | you, required | what this agent is; 1 to 200 characters |
| tier | string | server | one of admin, agent; minting always produces an agent credential |
| status | string | you, on update only | one of active, revoked; a new credential is always active; revoking is terminal |
| masked | string | server | enough of the credential to recognise it, never enough to use it |
| last_used_at | timestamp or null | server |  |
| created_by | string | server | the credential that created this |
| created_at | timestamp | server |  |
| updated_at | timestamp | server |  |
| version | integer | server | increments on every write, and is the entity tag |

### Invite

| field | type | set by | notes |
|---|---|---|---|
| id | string | server | inv_ and 26 lowercase characters |
| project_id | string | you, required | your account must own it |
| project_name | string | server | as it was when the invite was sent |
| email | string | you, required | who is invited; their account's email |
| role | string | you, required | one of editor, owner; the role accepting gives |
| invited_by_email | string | server | the email of the account that sent it |
| status | string | you, on update only | one of pending, accepted, declined, revoked; the invitee accepts or declines; an owner revokes; only pending changes |
| created_by | string | server | the credential that created this |
| created_at | timestamp | server |  |
| updated_at | timestamp | server |  |
| version | integer | server | increments on every write, and is the entity tag |

### Feedback

| field | type | set by | notes |
|---|---|---|---|
| id | string | server | fdbk_ and 26 lowercase characters |
| subject | string | you, required | 1 to 200 characters |
| body | string | you, required | 1 to 4000 characters |
| api_version | string | server | the version this is about; not an entity tag |
| created_by | string | server | the credential that created this |
| created_at | timestamp | server |  |
| updated_at | timestamp | server |  |
