akiflow.label API documentation

# akiflow.label

Label API — create, update, delete, and list Akiflow labels (projects).

View Source

      1"""Label API — create, update, delete, and list Akiflow labels (projects)."""
      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 Label:
     14    """Operations on Akiflow labels/projects, available as `client.label`.
     15
     16    Labels are what Akiflow calls "projects" in the UI. Tasks reference
     17    labels via their `listId` field. Labels can be organized into folders
     18    using `parent_id` and `type="folder"`.
     19
     20    Example:
     21        ```python
     22        from akiflow import Akiflow
     23
     24        client = Akiflow(refresh_token="def50200...")
     25
     26        # Create a label
     27        label = client.label.create("My Project", color="palette-green")
     28
     29        # List all labels
     30        result = client.label.list()
     31        for lb in result["data"]:
     32            print(lb["title"], lb["id"])
     33
     34        # Find label ID by name
     35        label_id = client.label.get_id("My Project")
     36
     37        # Update
     38        client.label.update(label["id"], title="Renamed Project")
     39
     40        # Delete
     41        client.label.delete(label["id"])
     42        ```
     43    """
     44
     45    # Cache of name -> id, populated on first lookup
     46    _name_cache: dict[str, str] | None = None
     47
     48    def __init__(self, client: Akiflow):
     49        self._client = client
     50        self._name_cache = None
     51
     52    def list(self, *, sync_token: str | None = None, limit: int = 2500) -> dict:
     53        """Fetch labels, with optional incremental sync.
     54
     55        Args:
     56            sync_token: Cursor from a previous `list()` response.
     57            limit: Max labels per page (default 2500).
     58
     59        Returns:
     60            Dict with `data` (list of label dicts), `sync_token`, and
     61            `has_next_page`.
     62        """
     63        params: dict[str, Any] = {"limit": str(limit)}
     64        if sync_token:
     65            params["sync_token"] = sync_token
     66        return self._client._get("/v5/labels", params=params)
     67
     68    def create(
     69        self,
     70        title: str,
     71        *,
     72        color: str | None = None,
     73        icon: str | None = None,
     74        parent_id: str | None = None,
     75        type: str | None = None,
     76        **extra: Any,
     77    ) -> dict:
     78        """Create a new label.
     79
     80        Args:
     81            title: Label name.
     82            color: Color palette name (e.g. `"palette-green"`, `"palette-pink"`).
     83            icon: Emoji icon.
     84            parent_id: UUID of a folder label to nest under.
     85            type: Set to `"folder"` to create a folder, or `None` for a label.
     86            **extra: Additional fields passed directly to the API.
     87
     88        Returns:
     89            The created label dict.
     90
     91        Example:
     92            ```python
     93            label = client.label.create("Work", color="palette-cobalt")
     94            folder = client.label.create("Area", type="folder")
     95            nested = client.label.create("Sub-project", parent_id=folder["id"])
     96            ```
     97        """
     98        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
     99        label_id = str(uuid.uuid4())
    100        sorting = int(datetime.now(timezone.utc).timestamp() * 1000)
    101
    102        label: dict[str, Any] = {
    103            "id": label_id,
    104            "title": title,
    105            "color": color,
    106            "icon": icon,
    107            "sorting": sorting,
    108            "parent_id": parent_id,
    109            "type": type,
    110            "data": {},
    111            "global_created_at": now,
    112            "global_updated_at": now,
    113            "deleted_at": None,
    114            **extra,
    115        }
    116
    117        resp = self._client._patch("/v5/labels", json=[label])
    118        created = resp["data"][0] if resp.get("data") else resp
    119        # Invalidate name cache
    120        self._name_cache = None
    121        return created
    122
    123    def update(self, label_id: str, **fields: Any) -> dict:
    124        """Update a label by ID.
    125
    126        Args:
    127            label_id: UUID of the label to update.
    128            **fields: Any label fields to update (e.g. `title`, `color`, `icon`).
    129
    130        Returns:
    131            The updated label dict.
    132
    133        Example:
    134            ```python
    135            client.label.update(label_id, title="New Name", color="palette-red")
    136            ```
    137        """
    138        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    139        payload: dict[str, Any] = {
    140            "id": label_id,
    141            "global_updated_at": now,
    142        }
    143        payload.update(fields)
    144
    145        resp = self._client._patch("/v5/labels", json=[payload])
    146        self._name_cache = None
    147        if resp.get("data"):
    148            return resp["data"][0]
    149        return resp
    150
    151    def delete(self, label_id: str) -> dict:
    152        """Soft-delete a label.
    153
    154        Args:
    155            label_id: UUID of the label to delete.
    156
    157        Returns:
    158            The updated label dict with `deleted_at` set.
    159
    160        Example:
    161            ```python
    162            client.label.delete("d7f7c026-bd8a-4c3a-8c16-d9677ee959e9")
    163            ```
    164        """
    165        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    166        payload: dict[str, Any] = {
    167            "id": label_id,
    168            "title": None,
    169            "color": None,
    170            "sorting": None,
    171            "icon": None,
    172            "parent_id": None,
    173            "type": None,
    174            "data": {},
    175            "deleted_at": now,
    176            "global_updated_at": now,
    177        }
    178        resp = self._client._patch("/v5/labels", json=[payload])
    179        self._name_cache = None
    180        if resp.get("data"):
    181            return resp["data"][0]
    182        return resp
    183
    184    def _build_name_cache(self) -> dict[str, str]:
    185        """Fetch all labels and build a lowercase name -> id mapping."""
    186        result = self.list()
    187        cache: dict[str, str] = {}
    188        for lb in result.get("data", []):
    189            if lb.get("title") and not lb.get("deleted_at"):
    190                cache[lb["title"].lower()] = lb["id"]
    191        return cache
    192
    193    def get_id(self, name: str) -> str | None:
    194        """Resolve a label name to its UUID.
    195
    196        Case-insensitive. Returns `None` if no label matches.
    197
    198        Args:
    199            name: Label name to look up.
    200
    201        Returns:
    202            Label UUID, or `None` if not found.
    203
    204        Example:
    205            ```python
    206            label_id = client.label.get_id("Work")
    207            ```
    208        """
    209        if self._name_cache is None:
    210            self._name_cache = self._build_name_cache()
    211        return self._name_cache.get(name.lower())
    212
    213    def resolve_id(self, label: str) -> str:
    214        """Resolve a label name or UUID to a UUID.
    215
    216        If `label` looks like a UUID, returns it as-is. Otherwise, looks
    217        it up by name (case-insensitive).
    218
    219        Args:
    220            label: Label UUID or name.
    221
    222        Returns:
    223            Label UUID.
    224
    225        Raises:
    226            ValueError: If the name doesn't match any label.
    227
    228        Example:
    229            ```python
    230            # Both return the same UUID:
    231            client.label.resolve_id("d7f7c026-bd8a-4c3a-8c16-d9677ee959e9")
    232            client.label.resolve_id("Work")
    233            ```
    234        """
    235        try:
    236            uuid.UUID(label)
    237            return label
    238        except ValueError:
    239            pass
    240
    241        label_id = self.get_id(label)
    242        if label_id is None:
    243            raise ValueError(f"No label found with name: {label!r}")
    244        return label_id

class Label: View Source

     14class Label:
     15    """Operations on Akiflow labels/projects, available as `client.label`.
     16
     17    Labels are what Akiflow calls "projects" in the UI. Tasks reference
     18    labels via their `listId` field. Labels can be organized into folders
     19    using `parent_id` and `type="folder"`.
     20
     21    Example:
     22        ```python
     23        from akiflow import Akiflow
     24
     25        client = Akiflow(refresh_token="def50200...")
     26
     27        # Create a label
     28        label = client.label.create("My Project", color="palette-green")
     29
     30        # List all labels
     31        result = client.label.list()
     32        for lb in result["data"]:
     33            print(lb["title"], lb["id"])
     34
     35        # Find label ID by name
     36        label_id = client.label.get_id("My Project")
     37
     38        # Update
     39        client.label.update(label["id"], title="Renamed Project")
     40
     41        # Delete
     42        client.label.delete(label["id"])
     43        ```
     44    """
     45
     46    # Cache of name -> id, populated on first lookup
     47    _name_cache: dict[str, str] | None = None
     48
     49    def __init__(self, client: Akiflow):
     50        self._client = client
     51        self._name_cache = None
     52
     53    def list(self, *, sync_token: str | None = None, limit: int = 2500) -> dict:
     54        """Fetch labels, with optional incremental sync.
     55
     56        Args:
     57            sync_token: Cursor from a previous `list()` response.
     58            limit: Max labels per page (default 2500).
     59
     60        Returns:
     61            Dict with `data` (list of label dicts), `sync_token`, and
     62            `has_next_page`.
     63        """
     64        params: dict[str, Any] = {"limit": str(limit)}
     65        if sync_token:
     66            params["sync_token"] = sync_token
     67        return self._client._get("/v5/labels", params=params)
     68
     69    def create(
     70        self,
     71        title: str,
     72        *,
     73        color: str | None = None,
     74        icon: str | None = None,
     75        parent_id: str | None = None,
     76        type: str | None = None,
     77        **extra: Any,
     78    ) -> dict:
     79        """Create a new label.
     80
     81        Args:
     82            title: Label name.
     83            color: Color palette name (e.g. `"palette-green"`, `"palette-pink"`).
     84            icon: Emoji icon.
     85            parent_id: UUID of a folder label to nest under.
     86            type: Set to `"folder"` to create a folder, or `None` for a label.
     87            **extra: Additional fields passed directly to the API.
     88
     89        Returns:
     90            The created label dict.
     91
     92        Example:
     93            ```python
     94            label = client.label.create("Work", color="palette-cobalt")
     95            folder = client.label.create("Area", type="folder")
     96            nested = client.label.create("Sub-project", parent_id=folder["id"])
     97            ```
     98        """
     99        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    100        label_id = str(uuid.uuid4())
    101        sorting = int(datetime.now(timezone.utc).timestamp() * 1000)
    102
    103        label: dict[str, Any] = {
    104            "id": label_id,
    105            "title": title,
    106            "color": color,
    107            "icon": icon,
    108            "sorting": sorting,
    109            "parent_id": parent_id,
    110            "type": type,
    111            "data": {},
    112            "global_created_at": now,
    113            "global_updated_at": now,
    114            "deleted_at": None,
    115            **extra,
    116        }
    117
    118        resp = self._client._patch("/v5/labels", json=[label])
    119        created = resp["data"][0] if resp.get("data") else resp
    120        # Invalidate name cache
    121        self._name_cache = None
    122        return created
    123
    124    def update(self, label_id: str, **fields: Any) -> dict:
    125        """Update a label by ID.
    126
    127        Args:
    128            label_id: UUID of the label to update.
    129            **fields: Any label fields to update (e.g. `title`, `color`, `icon`).
    130
    131        Returns:
    132            The updated label dict.
    133
    134        Example:
    135            ```python
    136            client.label.update(label_id, title="New Name", color="palette-red")
    137            ```
    138        """
    139        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    140        payload: dict[str, Any] = {
    141            "id": label_id,
    142            "global_updated_at": now,
    143        }
    144        payload.update(fields)
    145
    146        resp = self._client._patch("/v5/labels", json=[payload])
    147        self._name_cache = None
    148        if resp.get("data"):
    149            return resp["data"][0]
    150        return resp
    151
    152    def delete(self, label_id: str) -> dict:
    153        """Soft-delete a label.
    154
    155        Args:
    156            label_id: UUID of the label to delete.
    157
    158        Returns:
    159            The updated label dict with `deleted_at` set.
    160
    161        Example:
    162            ```python
    163            client.label.delete("d7f7c026-bd8a-4c3a-8c16-d9677ee959e9")
    164            ```
    165        """
    166        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    167        payload: dict[str, Any] = {
    168            "id": label_id,
    169            "title": None,
    170            "color": None,
    171            "sorting": None,
    172            "icon": None,
    173            "parent_id": None,
    174            "type": None,
    175            "data": {},
    176            "deleted_at": now,
    177            "global_updated_at": now,
    178        }
    179        resp = self._client._patch("/v5/labels", json=[payload])
    180        self._name_cache = None
    181        if resp.get("data"):
    182            return resp["data"][0]
    183        return resp
    184
    185    def _build_name_cache(self) -> dict[str, str]:
    186        """Fetch all labels and build a lowercase name -> id mapping."""
    187        result = self.list()
    188        cache: dict[str, str] = {}
    189        for lb in result.get("data", []):
    190            if lb.get("title") and not lb.get("deleted_at"):
    191                cache[lb["title"].lower()] = lb["id"]
    192        return cache
    193
    194    def get_id(self, name: str) -> str | None:
    195        """Resolve a label name to its UUID.
    196
    197        Case-insensitive. Returns `None` if no label matches.
    198
    199        Args:
    200            name: Label name to look up.
    201
    202        Returns:
    203            Label UUID, or `None` if not found.
    204
    205        Example:
    206            ```python
    207            label_id = client.label.get_id("Work")
    208            ```
    209        """
    210        if self._name_cache is None:
    211            self._name_cache = self._build_name_cache()
    212        return self._name_cache.get(name.lower())
    213
    214    def resolve_id(self, label: str) -> str:
    215        """Resolve a label name or UUID to a UUID.
    216
    217        If `label` looks like a UUID, returns it as-is. Otherwise, looks
    218        it up by name (case-insensitive).
    219
    220        Args:
    221            label: Label UUID or name.
    222
    223        Returns:
    224            Label UUID.
    225
    226        Raises:
    227            ValueError: If the name doesn't match any label.
    228
    229        Example:
    230            ```python
    231            # Both return the same UUID:
    232            client.label.resolve_id("d7f7c026-bd8a-4c3a-8c16-d9677ee959e9")
    233            client.label.resolve_id("Work")
    234            ```
    235        """
    236        try:
    237            uuid.UUID(label)
    238            return label
    239        except ValueError:
    240            pass
    241
    242        label_id = self.get_id(label)
    243        if label_id is None:
    244            raise ValueError(f"No label found with name: {label!r}")
    245        return label_id

Operations on Akiflow labels/projects, available as `client.label`.

Labels are what Akiflow calls "projects" in the UI. Tasks reference labels via their `listId` field. Labels can be organized into folders using `parent_id` and `type="folder"`.

Example:

    from akiflow import Akiflow

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

    # Create a label
    label = client.label.create("My Project", color="palette-green")

    # List all labels
    result = client.label.list()
    for lb in result["data"]:
        print(lb["title"], lb["id"])

    # Find label ID by name
    label_id = client.label.get_id("My Project")

    # Update
    client.label.update(label["id"], title="Renamed Project")

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

Label(client: akiflow.Akiflow) View Source

    49    def __init__(self, client: Akiflow):
    50        self._client = client
    51        self._name_cache = None

def list(self, *, sync_token: str | None = None, limit: int = 2500) -> dict: View Source

    53    def list(self, *, sync_token: str | None = None, limit: int = 2500) -> dict:
    54        """Fetch labels, with optional incremental sync.
    55
    56        Args:
    57            sync_token: Cursor from a previous `list()` response.
    58            limit: Max labels per page (default 2500).
    59
    60        Returns:
    61            Dict with `data` (list of label dicts), `sync_token`, and
    62            `has_next_page`.
    63        """
    64        params: dict[str, Any] = {"limit": str(limit)}
    65        if sync_token:
    66            params["sync_token"] = sync_token
    67        return self._client._get("/v5/labels", params=params)

Fetch labels, with optional incremental sync.

Args: sync_token: Cursor from a previous `list()` response. limit: Max labels per page (default 2500).

Returns: Dict with `data` (list of label dicts), `sync_token`, and `has_next_page`.

def create( self, title: str, *, color: str | None = None, icon: str | None = None, parent_id: str | None = None, type: str | None = None, **extra: Any) -> dict: View Source

     69    def create(
     70        self,
     71        title: str,
     72        *,
     73        color: str | None = None,
     74        icon: str | None = None,
     75        parent_id: str | None = None,
     76        type: str | None = None,
     77        **extra: Any,
     78    ) -> dict:
     79        """Create a new label.
     80
     81        Args:
     82            title: Label name.
     83            color: Color palette name (e.g. `"palette-green"`, `"palette-pink"`).
     84            icon: Emoji icon.
     85            parent_id: UUID of a folder label to nest under.
     86            type: Set to `"folder"` to create a folder, or `None` for a label.
     87            **extra: Additional fields passed directly to the API.
     88
     89        Returns:
     90            The created label dict.
     91
     92        Example:
     93            ```python
     94            label = client.label.create("Work", color="palette-cobalt")
     95            folder = client.label.create("Area", type="folder")
     96            nested = client.label.create("Sub-project", parent_id=folder["id"])
     97            ```
     98        """
     99        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    100        label_id = str(uuid.uuid4())
    101        sorting = int(datetime.now(timezone.utc).timestamp() * 1000)
    102
    103        label: dict[str, Any] = {
    104            "id": label_id,
    105            "title": title,
    106            "color": color,
    107            "icon": icon,
    108            "sorting": sorting,
    109            "parent_id": parent_id,
    110            "type": type,
    111            "data": {},
    112            "global_created_at": now,
    113            "global_updated_at": now,
    114            "deleted_at": None,
    115            **extra,
    116        }
    117
    118        resp = self._client._patch("/v5/labels", json=[label])
    119        created = resp["data"][0] if resp.get("data") else resp
    120        # Invalidate name cache
    121        self._name_cache = None
    122        return created

Create a new label.

Args: title: Label name. color: Color palette name (e.g. `"palette-green"`, `"palette-pink"`). icon: Emoji icon. parent_id: UUID of a folder label to nest under. type: Set to `"folder"` to create a folder, or `None` for a label. **extra: Additional fields passed directly to the API.

Returns: The created label dict.

Example:

    label = client.label.create("Work", color="palette-cobalt")
    folder = client.label.create("Area", type="folder")
    nested = client.label.create("Sub-project", parent_id=folder["id"])

def update(self, label_id: str, **fields: Any) -> dict: View Source

    124    def update(self, label_id: str, **fields: Any) -> dict:
    125        """Update a label by ID.
    126
    127        Args:
    128            label_id: UUID of the label to update.
    129            **fields: Any label fields to update (e.g. `title`, `color`, `icon`).
    130
    131        Returns:
    132            The updated label dict.
    133
    134        Example:
    135            ```python
    136            client.label.update(label_id, title="New Name", color="palette-red")
    137            ```
    138        """
    139        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    140        payload: dict[str, Any] = {
    141            "id": label_id,
    142            "global_updated_at": now,
    143        }
    144        payload.update(fields)
    145
    146        resp = self._client._patch("/v5/labels", json=[payload])
    147        self._name_cache = None
    148        if resp.get("data"):
    149            return resp["data"][0]
    150        return resp

Update a label by ID.

Args: label_id: UUID of the label to update. **fields: Any label fields to update (e.g. `title`, `color`, `icon`).

Returns: The updated label dict.

Example:

    client.label.update(label_id, title="New Name", color="palette-red")

def delete(self, label_id: str) -> dict: View Source

    152    def delete(self, label_id: str) -> dict:
    153        """Soft-delete a label.
    154
    155        Args:
    156            label_id: UUID of the label to delete.
    157
    158        Returns:
    159            The updated label dict with `deleted_at` set.
    160
    161        Example:
    162            ```python
    163            client.label.delete("d7f7c026-bd8a-4c3a-8c16-d9677ee959e9")
    164            ```
    165        """
    166        now = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    167        payload: dict[str, Any] = {
    168            "id": label_id,
    169            "title": None,
    170            "color": None,
    171            "sorting": None,
    172            "icon": None,
    173            "parent_id": None,
    174            "type": None,
    175            "data": {},
    176            "deleted_at": now,
    177            "global_updated_at": now,
    178        }
    179        resp = self._client._patch("/v5/labels", json=[payload])
    180        self._name_cache = None
    181        if resp.get("data"):
    182            return resp["data"][0]
    183        return resp

Soft-delete a label.

Args: label_id: UUID of the label to delete.

Returns: The updated label dict with `deleted_at` set.

Example:

    client.label.delete("d7f7c026-bd8a-4c3a-8c16-d9677ee959e9")

def get_id(self, name: str) -> str | None: View Source

    194    def get_id(self, name: str) -> str | None:
    195        """Resolve a label name to its UUID.
    196
    197        Case-insensitive. Returns `None` if no label matches.
    198
    199        Args:
    200            name: Label name to look up.
    201
    202        Returns:
    203            Label UUID, or `None` if not found.
    204
    205        Example:
    206            ```python
    207            label_id = client.label.get_id("Work")
    208            ```
    209        """
    210        if self._name_cache is None:
    211            self._name_cache = self._build_name_cache()
    212        return self._name_cache.get(name.lower())

Resolve a label name to its UUID.

Case-insensitive. Returns `None` if no label matches.

Args: name: Label name to look up.

Returns: Label UUID, or `None` if not found.

Example:

    label_id = client.label.get_id("Work")

def resolve_id(self, label: str) -> str: View Source

    214    def resolve_id(self, label: str) -> str:
    215        """Resolve a label name or UUID to a UUID.
    216
    217        If `label` looks like a UUID, returns it as-is. Otherwise, looks
    218        it up by name (case-insensitive).
    219
    220        Args:
    221            label: Label UUID or name.
    222
    223        Returns:
    224            Label UUID.
    225
    226        Raises:
    227            ValueError: If the name doesn't match any label.
    228
    229        Example:
    230            ```python
    231            # Both return the same UUID:
    232            client.label.resolve_id("d7f7c026-bd8a-4c3a-8c16-d9677ee959e9")
    233            client.label.resolve_id("Work")
    234            ```
    235        """
    236        try:
    237            uuid.UUID(label)
    238            return label
    239        except ValueError:
    240            pass
    241
    242        label_id = self.get_id(label)
    243        if label_id is None:
    244            raise ValueError(f"No label found with name: {label!r}")
    245        return label_id

Resolve a label name or UUID to a UUID.

If `label` looks like a UUID, returns it as-is. Otherwise, looks it up by name (case-insensitive).

Args: label: Label UUID or name.

Returns: Label UUID.

Raises: ValueError: If the name doesn't match any label.

Example:

    # Both return the same UUID:
    client.label.resolve_id("d7f7c026-bd8a-4c3a-8c16-d9677ee959e9")
    client.label.resolve_id("Work")