akiflow.task

Task API — create, update, delete, and list Akiflow tasks.

  1"""Task API — create, update, delete, and list Akiflow tasks."""
  2
  3from __future__ import annotations
  4
  5import uuid
  6from datetime import datetime, timezone
  7from typing import TYPE_CHECKING, Any
  8
  9if TYPE_CHECKING:
 10    from .client import Akiflow
 11
 12
 13class Task:
 14    """Operations on Akiflow tasks, available as `client.task`.
 15
 16    All mutations go through `PATCH /v5/tasks` (Akiflow uses upsert semantics).
 17    Deletion is a soft-delete via `trashed_at`.
 18
 19    Example:
 20        ```python
 21        from akiflow import Akiflow
 22
 23        client = Akiflow(refresh_token="def50200...")
 24
 25        # Create
 26        task = client.task.create("Buy groceries")
 27
 28        # Update
 29        client.task.update(task["id"], title="Buy organic groceries")
 30
 31        # Mark done
 32        client.task.done(task["id"])
 33
 34        # Delete
 35        client.task.delete(task["id"])
 36
 37        # List all tasks
 38        result = client.task.list()
 39        for t in result["data"]:
 40            print(t["title"], t["done"])
 41        ```
 42    """
 43
 44    def __init__(self, client: Akiflow):
 45        self._client = client
 46
 47    def list(self, *, sync_token: str | None = None, limit: int = 2500) -> dict:
 48        """Fetch tasks, with optional incremental sync.
 49
 50        Args:
 51            sync_token: Cursor from a previous `list()` response. Pass this
 52                to get only tasks changed since the last call.
 53            limit: Max tasks per page (default 2500).
 54
 55        Returns:
 56            Dict with `data` (list of task dicts), `sync_token` (cursor for
 57            next call), and `has_next_page`.
 58
 59        Example:
 60            ```python
 61            # Full sync
 62            result = client.task.list()
 63            tasks = result["data"]
 64            cursor = result["sync_token"]
 65
 66            # Incremental sync (only changes since last call)
 67            result = client.task.list(sync_token=cursor)
 68            ```
 69        """
 70        params: dict[str, Any] = {"limit": str(limit)}
 71        if sync_token:
 72            params["sync_token"] = sync_token
 73        return self._client._get("/v5/tasks", params=params)
 74
 75    def create(
 76        self,
 77        title: str,
 78        *,
 79        description: str | None = None,
 80        date: str | None = None,
 81        datetime_: str | None = None,
 82        datetime_tz: str | None = None,
 83        duration: int | None = None,
 84        due_date: str | None = None,
 85        priority: int | None = None,
 86        tags_ids: list[str] | None = None,
 87        label: str | None = None,
 88        list_id: str | None = None,
 89        section_id: str | None = None,
 90        links: list[str] | None = None,
 91        **extra: Any,
 92    ) -> dict:
 93        """Create a new task.
 94
 95        By default, tasks land in the **inbox** (`status=1`). To schedule a
 96        task on a specific date/time, pass `date` and `datetime_`.
 97
 98        Args:
 99            title: Task title.
100            description: HTML description body.
101            date: Planned date (`"2026-03-27"`).
102            datetime_: Planned datetime in UTC (`"2026-03-27T09:00:00.000Z"`).
103            datetime_tz: Timezone for display (default `"Europe/Zurich"`).
104            duration: Duration in seconds (e.g. `1800` for 30 min).
105            due_date: Hard due date (`"2026-03-28"`).
106            priority: Priority level.
107            tags_ids: List of tag UUIDs.
108            label: Label/project name **or** UUID. Resolved automatically
109                via `client.label.resolve_id()`. Takes precedence over `list_id`.
110            list_id: Project/list UUID (use `label` for name-based lookup).
111            section_id: Section within a project.
112            links: List of URL strings.
113            **extra: Additional fields passed directly to the API.
114
115        Returns:
116            The created task dict as returned by the API.
117
118        Example:
119            ```python
120            # Inbox task
121            task = client.task.create("Buy groceries")
122
123            # Task assigned to a label by name
124            task = client.task.create("Review PR", label="Work")
125
126            # Scheduled task with duration
127            task = client.task.create(
128                "Team standup",
129                date="2026-03-27",
130                datetime_="2026-03-27T09:00:00.000Z",
131                duration=1800,
132            )
133            ```
134        """
135        if label is not None:
136            list_id = self._client.label.resolve_id(label)
137
138        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
139        task_id = str(uuid.uuid4())
140        sorting = int(datetime.now(timezone.utc).timestamp() * 1000)
141
142        task: dict[str, Any] = {
143            "id": task_id,
144            "title": title,
145            "description": description,
146            "status": 1,
147            "done": False,
148            "done_at": None,
149            "date": date,
150            "datetime": datetime_,
151            "datetime_tz": datetime_tz or "Europe/Zurich",
152            "original_date": None,
153            "original_datetime": None,
154            "duration": duration,
155            "due_date": due_date,
156            "priority": priority,
157            "sorting": sorting,
158            "sorting_label": sorting,
159            "tags_ids": tags_ids,
160            "links": links or [],
161            "listId": list_id,
162            "section_id": section_id,
163            "calendar_id": None,
164            "time_slot_id": None,
165            "recurring_id": None,
166            "recurrence": None,
167            "recurrence_version": None,
168            "plan_unit": None,
169            "plan_period": None,
170            "origin": None,
171            "connector_id": None,
172            "origin_id": None,
173            "origin_account_id": None,
174            "doc": None,
175            "content": None,
176            "data": {},
177            "search_text": "",
178            "trashed_at": None,
179            "deleted_at": None,
180            "global_created_at": now,
181            "global_updated_at": now,
182            "global_list_id_updated_at": now if list_id else None,
183            "global_tags_ids_updated_at": None,
184            **extra,
185        }
186
187        resp = self._client._patch("/v5/tasks", json=[task])
188        # Return the single created task
189        if resp.get("data"):
190            return resp["data"][0]
191        return resp
192
193    def update(self, task_id: str, **fields: Any) -> dict:
194        """Update a task by ID.
195
196        Only pass the fields you want to change. The `global_updated_at`
197        timestamp is set automatically.
198
199        Args:
200            task_id: UUID of the task to update.
201            **fields: Any task fields to update. Use `label` for name-based
202                project lookup, `list_id` for UUID-based, and `datetime_`
203                for the datetime field.
204
205        Returns:
206            The updated task dict.
207
208        Example:
209            ```python
210            # Rename
211            client.task.update(task_id, title="New title")
212
213            # Assign to a label by name
214            client.task.update(task_id, label="Work")
215
216            # Reschedule
217            client.task.update(
218                task_id,
219                date="2026-04-01",
220                datetime_="2026-04-01T14:00:00.000Z",
221            )
222            ```
223        """
224        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
225        payload: dict[str, Any] = {
226            "id": task_id,
227            "global_updated_at": now,
228        }
229
230        # Resolve label name/UUID -> listId
231        if "label" in fields:
232            label = fields.pop("label")
233            if label is not None:
234                fields["list_id"] = self._client.label.resolve_id(label)
235            else:
236                fields["list_id"] = None
237
238        # Map python-friendly names to API names
239        if "list_id" in fields:
240            payload["listId"] = fields.pop("list_id")
241            payload["global_list_id_updated_at"] = now
242        if "datetime_" in fields:
243            payload["datetime"] = fields.pop("datetime_")
244
245        payload.update(fields)
246
247        resp = self._client._patch("/v5/tasks", json=[payload])
248        if resp.get("data"):
249            return resp["data"][0]
250        return resp
251
252    def delete(self, task_id: str) -> dict:
253        """Soft-delete a task.
254
255        Sets `trashed_at` to the current time. The task can still be
256        recovered in Akiflow's trash.
257
258        Args:
259            task_id: UUID of the task to delete.
260
261        Returns:
262            The updated task dict with `trashed_at` set.
263
264        Example:
265            ```python
266            client.task.delete("59442bbd-a57d-464f-9fa2-2cb9678379ee")
267            ```
268        """
269        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
270        payload = {
271            "id": task_id,
272            "status": 10,
273            "trashed_at": now,
274            "global_updated_at": now,
275        }
276        resp = self._client._patch("/v5/tasks", json=[payload])
277        if resp.get("data"):
278            return resp["data"][0]
279        return resp
280
281    def done(self, task_id: str, *, date: str | None = None) -> dict:
282        """Mark a task as done.
283
284        Args:
285            task_id: UUID of the task to complete.
286            date: Date in ``YYYY-MM-DD`` format. Defaults to today.
287                A date is required for the task to appear in Akiflow's
288                done list.
289
290        Returns:
291            The updated task dict with `done=True`.
292
293        Example:
294            ```python
295            client.task.done("59442bbd-a57d-464f-9fa2-2cb9678379ee")
296            client.task.done(task_id, date="2026-03-20")
297            ```
298        """
299        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
300        if date is None:
301            date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
302        return self.update(task_id, done=True, done_at=now, date=date, status=2)
class Task:
 14class Task:
 15    """Operations on Akiflow tasks, available as `client.task`.
 16
 17    All mutations go through `PATCH /v5/tasks` (Akiflow uses upsert semantics).
 18    Deletion is a soft-delete via `trashed_at`.
 19
 20    Example:
 21        ```python
 22        from akiflow import Akiflow
 23
 24        client = Akiflow(refresh_token="def50200...")
 25
 26        # Create
 27        task = client.task.create("Buy groceries")
 28
 29        # Update
 30        client.task.update(task["id"], title="Buy organic groceries")
 31
 32        # Mark done
 33        client.task.done(task["id"])
 34
 35        # Delete
 36        client.task.delete(task["id"])
 37
 38        # List all tasks
 39        result = client.task.list()
 40        for t in result["data"]:
 41            print(t["title"], t["done"])
 42        ```
 43    """
 44
 45    def __init__(self, client: Akiflow):
 46        self._client = client
 47
 48    def list(self, *, sync_token: str | None = None, limit: int = 2500) -> dict:
 49        """Fetch tasks, with optional incremental sync.
 50
 51        Args:
 52            sync_token: Cursor from a previous `list()` response. Pass this
 53                to get only tasks changed since the last call.
 54            limit: Max tasks per page (default 2500).
 55
 56        Returns:
 57            Dict with `data` (list of task dicts), `sync_token` (cursor for
 58            next call), and `has_next_page`.
 59
 60        Example:
 61            ```python
 62            # Full sync
 63            result = client.task.list()
 64            tasks = result["data"]
 65            cursor = result["sync_token"]
 66
 67            # Incremental sync (only changes since last call)
 68            result = client.task.list(sync_token=cursor)
 69            ```
 70        """
 71        params: dict[str, Any] = {"limit": str(limit)}
 72        if sync_token:
 73            params["sync_token"] = sync_token
 74        return self._client._get("/v5/tasks", params=params)
 75
 76    def create(
 77        self,
 78        title: str,
 79        *,
 80        description: str | None = None,
 81        date: str | None = None,
 82        datetime_: str | None = None,
 83        datetime_tz: str | None = None,
 84        duration: int | None = None,
 85        due_date: str | None = None,
 86        priority: int | None = None,
 87        tags_ids: list[str] | None = None,
 88        label: str | None = None,
 89        list_id: str | None = None,
 90        section_id: str | None = None,
 91        links: list[str] | None = None,
 92        **extra: Any,
 93    ) -> dict:
 94        """Create a new task.
 95
 96        By default, tasks land in the **inbox** (`status=1`). To schedule a
 97        task on a specific date/time, pass `date` and `datetime_`.
 98
 99        Args:
100            title: Task title.
101            description: HTML description body.
102            date: Planned date (`"2026-03-27"`).
103            datetime_: Planned datetime in UTC (`"2026-03-27T09:00:00.000Z"`).
104            datetime_tz: Timezone for display (default `"Europe/Zurich"`).
105            duration: Duration in seconds (e.g. `1800` for 30 min).
106            due_date: Hard due date (`"2026-03-28"`).
107            priority: Priority level.
108            tags_ids: List of tag UUIDs.
109            label: Label/project name **or** UUID. Resolved automatically
110                via `client.label.resolve_id()`. Takes precedence over `list_id`.
111            list_id: Project/list UUID (use `label` for name-based lookup).
112            section_id: Section within a project.
113            links: List of URL strings.
114            **extra: Additional fields passed directly to the API.
115
116        Returns:
117            The created task dict as returned by the API.
118
119        Example:
120            ```python
121            # Inbox task
122            task = client.task.create("Buy groceries")
123
124            # Task assigned to a label by name
125            task = client.task.create("Review PR", label="Work")
126
127            # Scheduled task with duration
128            task = client.task.create(
129                "Team standup",
130                date="2026-03-27",
131                datetime_="2026-03-27T09:00:00.000Z",
132                duration=1800,
133            )
134            ```
135        """
136        if label is not None:
137            list_id = self._client.label.resolve_id(label)
138
139        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
140        task_id = str(uuid.uuid4())
141        sorting = int(datetime.now(timezone.utc).timestamp() * 1000)
142
143        task: dict[str, Any] = {
144            "id": task_id,
145            "title": title,
146            "description": description,
147            "status": 1,
148            "done": False,
149            "done_at": None,
150            "date": date,
151            "datetime": datetime_,
152            "datetime_tz": datetime_tz or "Europe/Zurich",
153            "original_date": None,
154            "original_datetime": None,
155            "duration": duration,
156            "due_date": due_date,
157            "priority": priority,
158            "sorting": sorting,
159            "sorting_label": sorting,
160            "tags_ids": tags_ids,
161            "links": links or [],
162            "listId": list_id,
163            "section_id": section_id,
164            "calendar_id": None,
165            "time_slot_id": None,
166            "recurring_id": None,
167            "recurrence": None,
168            "recurrence_version": None,
169            "plan_unit": None,
170            "plan_period": None,
171            "origin": None,
172            "connector_id": None,
173            "origin_id": None,
174            "origin_account_id": None,
175            "doc": None,
176            "content": None,
177            "data": {},
178            "search_text": "",
179            "trashed_at": None,
180            "deleted_at": None,
181            "global_created_at": now,
182            "global_updated_at": now,
183            "global_list_id_updated_at": now if list_id else None,
184            "global_tags_ids_updated_at": None,
185            **extra,
186        }
187
188        resp = self._client._patch("/v5/tasks", json=[task])
189        # Return the single created task
190        if resp.get("data"):
191            return resp["data"][0]
192        return resp
193
194    def update(self, task_id: str, **fields: Any) -> dict:
195        """Update a task by ID.
196
197        Only pass the fields you want to change. The `global_updated_at`
198        timestamp is set automatically.
199
200        Args:
201            task_id: UUID of the task to update.
202            **fields: Any task fields to update. Use `label` for name-based
203                project lookup, `list_id` for UUID-based, and `datetime_`
204                for the datetime field.
205
206        Returns:
207            The updated task dict.
208
209        Example:
210            ```python
211            # Rename
212            client.task.update(task_id, title="New title")
213
214            # Assign to a label by name
215            client.task.update(task_id, label="Work")
216
217            # Reschedule
218            client.task.update(
219                task_id,
220                date="2026-04-01",
221                datetime_="2026-04-01T14:00:00.000Z",
222            )
223            ```
224        """
225        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
226        payload: dict[str, Any] = {
227            "id": task_id,
228            "global_updated_at": now,
229        }
230
231        # Resolve label name/UUID -> listId
232        if "label" in fields:
233            label = fields.pop("label")
234            if label is not None:
235                fields["list_id"] = self._client.label.resolve_id(label)
236            else:
237                fields["list_id"] = None
238
239        # Map python-friendly names to API names
240        if "list_id" in fields:
241            payload["listId"] = fields.pop("list_id")
242            payload["global_list_id_updated_at"] = now
243        if "datetime_" in fields:
244            payload["datetime"] = fields.pop("datetime_")
245
246        payload.update(fields)
247
248        resp = self._client._patch("/v5/tasks", json=[payload])
249        if resp.get("data"):
250            return resp["data"][0]
251        return resp
252
253    def delete(self, task_id: str) -> dict:
254        """Soft-delete a task.
255
256        Sets `trashed_at` to the current time. The task can still be
257        recovered in Akiflow's trash.
258
259        Args:
260            task_id: UUID of the task to delete.
261
262        Returns:
263            The updated task dict with `trashed_at` set.
264
265        Example:
266            ```python
267            client.task.delete("59442bbd-a57d-464f-9fa2-2cb9678379ee")
268            ```
269        """
270        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
271        payload = {
272            "id": task_id,
273            "status": 10,
274            "trashed_at": now,
275            "global_updated_at": now,
276        }
277        resp = self._client._patch("/v5/tasks", json=[payload])
278        if resp.get("data"):
279            return resp["data"][0]
280        return resp
281
282    def done(self, task_id: str, *, date: str | None = None) -> dict:
283        """Mark a task as done.
284
285        Args:
286            task_id: UUID of the task to complete.
287            date: Date in ``YYYY-MM-DD`` format. Defaults to today.
288                A date is required for the task to appear in Akiflow's
289                done list.
290
291        Returns:
292            The updated task dict with `done=True`.
293
294        Example:
295            ```python
296            client.task.done("59442bbd-a57d-464f-9fa2-2cb9678379ee")
297            client.task.done(task_id, date="2026-03-20")
298            ```
299        """
300        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
301        if date is None:
302            date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
303        return self.update(task_id, done=True, done_at=now, date=date, status=2)

Operations on Akiflow tasks, available as client.task.

All mutations go through PATCH /v5/tasks (Akiflow uses upsert semantics). Deletion is a soft-delete via trashed_at.

Example:

from akiflow import Akiflow

client = Akiflow(refresh_token="def50200...")

# Create
task = client.task.create("Buy groceries")

# Update
client.task.update(task["id"], title="Buy organic groceries")

# Mark done
client.task.done(task["id"])

# Delete
client.task.delete(task["id"])

# List all tasks
result = client.task.list()
for t in result["data"]:
    print(t["title"], t["done"])
Task(client: akiflow.Akiflow)
45    def __init__(self, client: Akiflow):
46        self._client = client
def list(self, *, sync_token: str | None = None, limit: int = 2500) -> dict:
48    def list(self, *, sync_token: str | None = None, limit: int = 2500) -> dict:
49        """Fetch tasks, with optional incremental sync.
50
51        Args:
52            sync_token: Cursor from a previous `list()` response. Pass this
53                to get only tasks changed since the last call.
54            limit: Max tasks per page (default 2500).
55
56        Returns:
57            Dict with `data` (list of task dicts), `sync_token` (cursor for
58            next call), and `has_next_page`.
59
60        Example:
61            ```python
62            # Full sync
63            result = client.task.list()
64            tasks = result["data"]
65            cursor = result["sync_token"]
66
67            # Incremental sync (only changes since last call)
68            result = client.task.list(sync_token=cursor)
69            ```
70        """
71        params: dict[str, Any] = {"limit": str(limit)}
72        if sync_token:
73            params["sync_token"] = sync_token
74        return self._client._get("/v5/tasks", params=params)

Fetch tasks, with optional incremental sync.

Args: sync_token: Cursor from a previous list() response. Pass this to get only tasks changed since the last call. limit: Max tasks per page (default 2500).

Returns: Dict with data (list of task dicts), sync_token (cursor for next call), and has_next_page.

Example:

# Full sync
result = client.task.list()
tasks = result["data"]
cursor = result["sync_token"]

# Incremental sync (only changes since last call)
result = client.task.list(sync_token=cursor)
def create( self, title: str, *, description: str | None = None, date: str | None = None, datetime_: str | None = None, datetime_tz: str | None = None, duration: int | None = None, due_date: str | None = None, priority: int | None = None, tags_ids: 'list[str] | None' = None, label: str | None = None, list_id: str | None = None, section_id: str | None = None, links: 'list[str] | None' = None, **extra: Any) -> dict:
 76    def create(
 77        self,
 78        title: str,
 79        *,
 80        description: str | None = None,
 81        date: str | None = None,
 82        datetime_: str | None = None,
 83        datetime_tz: str | None = None,
 84        duration: int | None = None,
 85        due_date: str | None = None,
 86        priority: int | None = None,
 87        tags_ids: list[str] | None = None,
 88        label: str | None = None,
 89        list_id: str | None = None,
 90        section_id: str | None = None,
 91        links: list[str] | None = None,
 92        **extra: Any,
 93    ) -> dict:
 94        """Create a new task.
 95
 96        By default, tasks land in the **inbox** (`status=1`). To schedule a
 97        task on a specific date/time, pass `date` and `datetime_`.
 98
 99        Args:
100            title: Task title.
101            description: HTML description body.
102            date: Planned date (`"2026-03-27"`).
103            datetime_: Planned datetime in UTC (`"2026-03-27T09:00:00.000Z"`).
104            datetime_tz: Timezone for display (default `"Europe/Zurich"`).
105            duration: Duration in seconds (e.g. `1800` for 30 min).
106            due_date: Hard due date (`"2026-03-28"`).
107            priority: Priority level.
108            tags_ids: List of tag UUIDs.
109            label: Label/project name **or** UUID. Resolved automatically
110                via `client.label.resolve_id()`. Takes precedence over `list_id`.
111            list_id: Project/list UUID (use `label` for name-based lookup).
112            section_id: Section within a project.
113            links: List of URL strings.
114            **extra: Additional fields passed directly to the API.
115
116        Returns:
117            The created task dict as returned by the API.
118
119        Example:
120            ```python
121            # Inbox task
122            task = client.task.create("Buy groceries")
123
124            # Task assigned to a label by name
125            task = client.task.create("Review PR", label="Work")
126
127            # Scheduled task with duration
128            task = client.task.create(
129                "Team standup",
130                date="2026-03-27",
131                datetime_="2026-03-27T09:00:00.000Z",
132                duration=1800,
133            )
134            ```
135        """
136        if label is not None:
137            list_id = self._client.label.resolve_id(label)
138
139        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
140        task_id = str(uuid.uuid4())
141        sorting = int(datetime.now(timezone.utc).timestamp() * 1000)
142
143        task: dict[str, Any] = {
144            "id": task_id,
145            "title": title,
146            "description": description,
147            "status": 1,
148            "done": False,
149            "done_at": None,
150            "date": date,
151            "datetime": datetime_,
152            "datetime_tz": datetime_tz or "Europe/Zurich",
153            "original_date": None,
154            "original_datetime": None,
155            "duration": duration,
156            "due_date": due_date,
157            "priority": priority,
158            "sorting": sorting,
159            "sorting_label": sorting,
160            "tags_ids": tags_ids,
161            "links": links or [],
162            "listId": list_id,
163            "section_id": section_id,
164            "calendar_id": None,
165            "time_slot_id": None,
166            "recurring_id": None,
167            "recurrence": None,
168            "recurrence_version": None,
169            "plan_unit": None,
170            "plan_period": None,
171            "origin": None,
172            "connector_id": None,
173            "origin_id": None,
174            "origin_account_id": None,
175            "doc": None,
176            "content": None,
177            "data": {},
178            "search_text": "",
179            "trashed_at": None,
180            "deleted_at": None,
181            "global_created_at": now,
182            "global_updated_at": now,
183            "global_list_id_updated_at": now if list_id else None,
184            "global_tags_ids_updated_at": None,
185            **extra,
186        }
187
188        resp = self._client._patch("/v5/tasks", json=[task])
189        # Return the single created task
190        if resp.get("data"):
191            return resp["data"][0]
192        return resp

Create a new task.

By default, tasks land in the inbox (status=1). To schedule a task on a specific date/time, pass date and datetime_.

Args: title: Task title. description: HTML description body. date: Planned date ("2026-03-27"). datetime_: Planned datetime in UTC ("2026-03-27T09:00:00.000Z"). datetime_tz: Timezone for display (default "Europe/Zurich"). duration: Duration in seconds (e.g. 1800 for 30 min). due_date: Hard due date ("2026-03-28"). priority: Priority level. tags_ids: List of tag UUIDs. label: Label/project name or UUID. Resolved automatically via client.label.resolve_id(). Takes precedence over list_id. list_id: Project/list UUID (use label for name-based lookup). section_id: Section within a project. links: List of URL strings. **extra: Additional fields passed directly to the API.

Returns: The created task dict as returned by the API.

Example:

# Inbox task
task = client.task.create("Buy groceries")

# Task assigned to a label by name
task = client.task.create("Review PR", label="Work")

# Scheduled task with duration
task = client.task.create(
    "Team standup",
    date="2026-03-27",
    datetime_="2026-03-27T09:00:00.000Z",
    duration=1800,
)
def update(self, task_id: str, **fields: Any) -> dict:
194    def update(self, task_id: str, **fields: Any) -> dict:
195        """Update a task by ID.
196
197        Only pass the fields you want to change. The `global_updated_at`
198        timestamp is set automatically.
199
200        Args:
201            task_id: UUID of the task to update.
202            **fields: Any task fields to update. Use `label` for name-based
203                project lookup, `list_id` for UUID-based, and `datetime_`
204                for the datetime field.
205
206        Returns:
207            The updated task dict.
208
209        Example:
210            ```python
211            # Rename
212            client.task.update(task_id, title="New title")
213
214            # Assign to a label by name
215            client.task.update(task_id, label="Work")
216
217            # Reschedule
218            client.task.update(
219                task_id,
220                date="2026-04-01",
221                datetime_="2026-04-01T14:00:00.000Z",
222            )
223            ```
224        """
225        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
226        payload: dict[str, Any] = {
227            "id": task_id,
228            "global_updated_at": now,
229        }
230
231        # Resolve label name/UUID -> listId
232        if "label" in fields:
233            label = fields.pop("label")
234            if label is not None:
235                fields["list_id"] = self._client.label.resolve_id(label)
236            else:
237                fields["list_id"] = None
238
239        # Map python-friendly names to API names
240        if "list_id" in fields:
241            payload["listId"] = fields.pop("list_id")
242            payload["global_list_id_updated_at"] = now
243        if "datetime_" in fields:
244            payload["datetime"] = fields.pop("datetime_")
245
246        payload.update(fields)
247
248        resp = self._client._patch("/v5/tasks", json=[payload])
249        if resp.get("data"):
250            return resp["data"][0]
251        return resp

Update a task by ID.

Only pass the fields you want to change. The global_updated_at timestamp is set automatically.

Args: task_id: UUID of the task to update. **fields: Any task fields to update. Use label for name-based project lookup, list_id for UUID-based, and datetime_ for the datetime field.

Returns: The updated task dict.

Example:

# Rename
client.task.update(task_id, title="New title")

# Assign to a label by name
client.task.update(task_id, label="Work")

# Reschedule
client.task.update(
    task_id,
    date="2026-04-01",
    datetime_="2026-04-01T14:00:00.000Z",
)
def delete(self, task_id: str) -> dict:
253    def delete(self, task_id: str) -> dict:
254        """Soft-delete a task.
255
256        Sets `trashed_at` to the current time. The task can still be
257        recovered in Akiflow's trash.
258
259        Args:
260            task_id: UUID of the task to delete.
261
262        Returns:
263            The updated task dict with `trashed_at` set.
264
265        Example:
266            ```python
267            client.task.delete("59442bbd-a57d-464f-9fa2-2cb9678379ee")
268            ```
269        """
270        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
271        payload = {
272            "id": task_id,
273            "status": 10,
274            "trashed_at": now,
275            "global_updated_at": now,
276        }
277        resp = self._client._patch("/v5/tasks", json=[payload])
278        if resp.get("data"):
279            return resp["data"][0]
280        return resp

Soft-delete a task.

Sets trashed_at to the current time. The task can still be recovered in Akiflow's trash.

Args: task_id: UUID of the task to delete.

Returns: The updated task dict with trashed_at set.

Example:

client.task.delete("59442bbd-a57d-464f-9fa2-2cb9678379ee")
def done(self, task_id: str, *, date: str | None = None) -> dict:
282    def done(self, task_id: str, *, date: str | None = None) -> dict:
283        """Mark a task as done.
284
285        Args:
286            task_id: UUID of the task to complete.
287            date: Date in ``YYYY-MM-DD`` format. Defaults to today.
288                A date is required for the task to appear in Akiflow's
289                done list.
290
291        Returns:
292            The updated task dict with `done=True`.
293
294        Example:
295            ```python
296            client.task.done("59442bbd-a57d-464f-9fa2-2cb9678379ee")
297            client.task.done(task_id, date="2026-03-20")
298            ```
299        """
300        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
301        if date is None:
302            date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
303        return self.update(task_id, done=True, done_at=now, date=date, status=2)

Mark a task as done.

Args: task_id: UUID of the task to complete. date: Date in YYYY-MM-DD format. Defaults to today. A date is required for the task to appear in Akiflow's done list.

Returns: The updated task dict with done=True.

Example:

client.task.done("59442bbd-a57d-464f-9fa2-2cb9678379ee")
client.task.done(task_id, date="2026-03-20")