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)
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"])
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)
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,
)
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",
)
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")
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")