Skip to content

Tasks API

The TasksAPI client exposes the task-detail read endpoint added in v0.4.0 and, since 0.10.0, the three write endpoints: create, edit and delete (the first write group opened by ADR 0001).

Resource grouping

The endpoint is grouped under client.tasks (D-v0.4-1), even though a task belongs to a post. This mirrors the M16 decision to give attachments their own resource group rather than folding into PostsAPI.

Usage

from pynteracta.client import InteractaClient

with InteractaClient(base_url="https://tenant.example.com", credentials=...) as client:
    task = client.tasks.get(7001)
    print(task.id, task.title, task.state)
    print(task.description_plain_text)

    # Sub-tasks and reminders (typed via …1 siblings)
    for st in task.sub_tasks:
        print(st.id, st.description, st.state)
    for r in task.reminders:
        print(r.id, r.value, r.range)

    # Capabilities
    if task.capabilities and task.capabilities.can_modify:
        print("task is editable")

    # Rich-text delta and survey data — reachable only via .raw (D-v0.4-4)
    print(task.raw.descriptionDelta)
    print(task.raw.surveyData)

    # Optimistic-concurrency token, needed by edit()
    print(task.occ_token)

Writing tasks

create(), edit() and delete() mirror the read API: explicit snake_case keyword arguments for every field of the request DTO, a *_raw() variant for callers holding a pre-built DTO, and a facade over the server response. Only the fields you pass are sent; the request body is built in camelCase from the non-None arguments.

from datetime import datetime
from zoneinfo import ZoneInfo

from pynteracta.client import InteractaClient
from pynteracta.exceptions import ConcurrencyError

with InteractaClient(base_url="https://tenant.example.com", credentials=...) as client:
    created = client.tasks.create(
        21269,
        title="Prepare the quarterly report",
        priority=2,
        assignee_user_id=1042,
        watcher_user_ids=[1042, 1099],
        expiration=datetime(2026, 12, 31, 18, 0, tzinfo=ZoneInfo("Europe/Rome")),
        client_uid="report-2026-q4",
    )
    print(created.task_id, created.next_occ_token, created.task.title)

    # Edit: read first, pass the concurrency token explicitly, and send back every field that
    # must survive (the server clears what is missing: see "Omitted fields" below)
    task = client.tasks.get(created.task_id)
    try:
        edited = client.tasks.edit(
            task.id,
            task.occ_token,
            title="Prepare the Q4 report",
            priority=task.priority,
            expiration=datetime(2026, 12, 31, 18, 0, tzinfo=ZoneInfo("Europe/Rome")),
            assignee_user_id=1042,
        )
    except ConcurrencyError:
        # the task changed after we read it: re-read and decide — the library never retries
        raise
    print(edited.next_occ_token)

    post_id = client.tasks.delete(created.task_id)

TaskWriteResult

create() and edit() return a TaskWriteResult: task_id, next_occ_token (the token to pass to the next edit()), task (the task data after the write, typed TaskDetailDTO1), capabilities, and .raw (the generated CreateTaskResponseDTO / EditTaskResponseDTO). delete() returns the id of the post that contained the task.

Concurrency: occ_token

edit() takes the occ_token read with get() (Task.occ_token). If the task changed in the meantime the server answers 409, mapped to ConcurrencyError. The library does not re-read and retry: that decision belongs to the caller (ADR 0001, non-negotiable rule "no automatic retries towards Interacta"). The same holds for timeouts and network errors on writes: one request per call, the outcome stays unknown until you check.

Expiration

expiration must be a timezone-aware datetime; a naive one raises ValueError before any request. With an IANA zone (ZoneInfo) the local time and the zone name are sent ({"datetime": "2026-12-31T18:00:00", "timezone": "Europe/Rome"}); with a fixed offset the value is converted to UTC and sent with "timezone": "UTC".

Omitted fields in edit(): the server replaces the task

Fields you do not pass are not sent, and the server treats the request as a replacement, not a patch (verified against a tenant, spec 02): title, description, expiration, priority, the assignee and sub_tasks that are missing from the request are cleared. Watchers and attachments are the exception: they are changed only through the add_* / remove_* arguments.

attachments (create) and add_attachments (edit) accept files just uploaded with client.attachments.upload() (an UploadedAttachment) as well as references the server already knows (InputTaskAttachmentDTO). See Uploading files.

Pass every field you want to keep

Read the task first and send back what must survive. The CLI tasks edit does this for you; the library does not, so that a call stays one explicit request. Sub-tasks sent back with their id are re-created by the server with new ids.

The same tenant also enforces fields the Swagger does not mark as required, on both create() and edit(): an assignee (assignee_user_id or assignee_group_id; without one the server answers 400 on the field assignee, raised as ValidationError) and an expiration (without one the server answers 500, raised as ServerError). Every sub-task needs a non-zero state. When the mapped message is not enough, the server's detail is in exc.response_body.

The server returns expiration as {"zonedDatetime": "2026-12-31T18:00:00+01:00", "localDatetime": "2026-12-31T18:00:00", "timezone": "Europe/Rome"}.

state vs currentWorkflowState

Task.state is an integer lifecycle code (open/closed etc.) — the recommended field to display and filter on. Task.current_workflow_state is the post's workflow-state DTO (PostWorkflowDefinitionStateDTO) and represents the post-level workflow, not the task lifecycle. Both are exposed on the facade; the CLI default table shows state (Q-v0.4-3).

Description and survey data

Per D-v0.4-4, the facade surfaces description_plain_text (curated scalar). The Quill rich-text JSON (descriptionDelta) and survey payloads (surveyData, surveyDataCommentsInfo) are left on .raw — surveyData is an untyped object → object map with no DTO, making .raw the only faithful surface.

API Reference

pynteracta.api.tasks.TasksAPI

Bases: ResourceClient

Client for the tasks endpoints: one read, plus create, edit and delete (spec 02).

get(task_id)

GET /communication/tasks/data/task-detail-by-id/{taskId}.

Parameters:

Name Type Description Default
task_id int

The task ID to fetch.

required

Returns:

Name Type Description
A Task

class:~pynteracta.models.facade.tasks.Task facade.

create(post_id, *, title=None, description_delta=None, description_plain_text=None, expiration=None, priority=None, sub_tasks=None, assignee_user_id=None, assignee_group_id=None, attachments=None, watcher_user_ids=None, watcher_group_ids=None, client_uid=None)

POST /communication/tasks/manage/create-task/{postId}.

Il corpo contiene solo i campi passati, in camelCase. expiration deve avere un fuso orario (ValueError altrimenti, prima di qualunque chiamata). Una sola richiesta: un errore del server o di rete risale al chiamante, mai un nuovo tentativo.

Parameters:

Name Type Description Default
post_id int

Il post a cui il task appartiene.

required
title str | None

Titolo del task.

None
description_delta str | None

Descrizione in formato Quill delta (JSON).

None
description_plain_text str | None

Descrizione in testo semplice.

None
expiration datetime | None

Scadenza, datetime con fuso.

None
priority int | None

Priorità (intero del tenant).

None
sub_tasks WriteItems

Sub-task, nella forma di SubTaskDTO (dict o modello).

None
assignee_user_id int | None

Utente assegnatario.

None
assignee_group_id int | None

Gruppo assegnatario.

None
attachments WriteItems

Allegati: :class:~pynteracta.models.facade.attachments.UploadedAttachment da client.attachments.upload, o InputTaskAttachmentDTO già noti.

None
watcher_user_ids list[int] | None

Utenti osservatori.

None
watcher_group_ids list[int] | None

Gruppi osservatori.

None
client_uid str | None

Identificativo scelto dal chiamante.

None

Returns:

Name Type Description
Un TaskWriteResult

class:~pynteracta.models.facade.tasks.TaskWriteResult con task_id,

TaskWriteResult

next_occ_token, task e capabilities.

create_raw(post_id, req)

Come :meth:create, da un CreateTaskRequestDTO già costruito.

edit(task_id, occ_token, *, title=None, description_delta=None, description_plain_text=None, expiration=None, priority=None, sub_tasks=None, assignee_user_id=None, assignee_group_id=None, add_attachments=None, remove_attachment_ids=None, add_watcher_user_ids=None, remove_watcher_user_ids=None, add_watcher_group_ids=None, remove_watcher_group_ids=None)

PUT /communication/tasks/manage/edit-task/{taskId}/{occToken}.

occ_token è il token di concorrenza letto con :meth:get (Task.occ_token): se il task è cambiato nel frattempo il server risponde 409 e viene sollevata :class:~pynteracta.exceptions.ConcurrencyError; la libreria non rilegge e non riprova. Il corpo contiene solo i campi passati; l'effetto sui campi omessi è deciso dal server.

Parameters:

Name Type Description Default
task_id int

Il task da modificare.

required
occ_token int

Token di concorrenza ottimistica del task letto.

required
title str | None

Titolo.

None
description_delta str | None

Descrizione in formato Quill delta (JSON).

None
description_plain_text str | None

Descrizione in testo semplice.

None
expiration datetime | None

Scadenza, datetime con fuso.

None
priority int | None

Priorità.

None
sub_tasks WriteItems

Sub-task, nella forma di SubTaskDTO.

None
assignee_user_id int | None

Utente assegnatario.

None
assignee_group_id int | None

Gruppo assegnatario.

None
add_attachments WriteItems

Allegati da aggiungere: UploadedAttachment o InputTaskAttachmentDTO.

None
remove_attachment_ids list[int] | None

Allegati da togliere.

None
add_watcher_user_ids list[int] | None

Utenti osservatori da aggiungere.

None
remove_watcher_user_ids list[int] | None

Utenti osservatori da togliere.

None
add_watcher_group_ids list[int] | None

Gruppi osservatori da aggiungere.

None
remove_watcher_group_ids list[int] | None

Gruppi osservatori da togliere.

None

Returns:

Name Type Description
Un TaskWriteResult

class:~pynteracta.models.facade.tasks.TaskWriteResult; next_occ_token è il

TaskWriteResult

token per la modifica successiva.

edit_raw(task_id, occ_token, req)

Come :meth:edit, da un EditTaskRequestDTO già costruito.

delete(task_id)

DELETE /communication/tasks/manage/delete-task/{taskId}.

Returns:

Type Description
int | None

L'id del post che conteneva il task (postId della risposta), se il server lo

int | None

restituisce.

Facade Reference

pynteracta.models.facade.tasks.Task

Narrow facade over :class:~generated.GetTaskDetailResponseDTO.

Nested TaskCapabilitiesDTO, SubTaskDTO, and TaskReminderDTO are generated as RootModel[Any] stubs; this facade re-validates them against their typed …1 siblings.

descriptionDelta (Quill rich-text JSON), surveyData, and surveyDataCommentsInfo are left on .raw per D-v0.4-4.

Attributes:

Name Type Description
raw

The underlying generated DTO; access additional fields via this escape hatch.

Source code in src/pynteracta/models/facade/tasks.py
class Task:
    """Narrow facade over :class:`~generated.GetTaskDetailResponseDTO`.

    Nested ``TaskCapabilitiesDTO``, ``SubTaskDTO``, and ``TaskReminderDTO`` are generated as
    ``RootModel[Any]`` stubs; this facade re-validates them against their typed ``…1`` siblings.

    ``descriptionDelta`` (Quill rich-text JSON), ``surveyData``, and ``surveyDataCommentsInfo``
    are left on ``.raw`` per D-v0.4-4.

    Attributes:
        raw: The underlying generated DTO; access additional fields via this escape hatch.
    """

    def __init__(self, raw: generated.GetTaskDetailResponseDTO) -> None:
        self.raw = raw
        self._capabilities: TaskCapabilities | None = None
        if raw.capabilities is not None:
            root = _resolve_root(raw.capabilities)
            if isinstance(root, dict):
                self._capabilities = TaskCapabilities(
                    generated.TaskCapabilitiesDTO1.model_validate(root)
                )

    @property
    def id(self) -> int | None:
        return self.raw.id

    @property
    def post_id(self) -> int | None:
        return self.raw.postId

    @property
    def title(self) -> str | None:
        return self.raw.title

    @property
    def description_plain_text(self) -> str | None:
        return self.raw.descriptionPlainText

    @property
    def expiration(self) -> generated.ZonedDatetimeDTO | None:
        return self.raw.expiration

    @property
    def priority(self) -> int | None:
        return self.raw.priority

    @property
    def state(self) -> int | None:
        """Task lifecycle state (integer code). Use ``.raw.currentWorkflowState`` for the post
        workflow state DTO."""
        return self.raw.state

    @property
    def current_workflow_state(self) -> generated.PostWorkflowDefinitionStateDTO | None:
        return self.raw.currentWorkflowState

    @property
    def attachments_count(self) -> int | None:
        return self.raw.attachmentsCount

    @property
    def occ_token(self) -> int | None:
        """Token di concorrenza ottimistica, da passare a ``TasksAPI.edit``."""
        return self.raw.occToken

    @property
    def creation_timestamp(self) -> int | None:
        return self.raw.creationTimestamp

    @property
    def creator_user(self) -> generated.UserDTO | None:
        return self.raw.creatorUser

    @property
    def assignee_user(self) -> generated.UserDTO | None:
        return self.raw.assigneeUser

    @property
    def assignee_group(self) -> generated.GroupDTO | None:
        return self.raw.assigneeGroup

    @property
    def watcher_users(self) -> list[generated.UserDTO] | None:
        return self.raw.watcherUsers

    @property
    def watcher_groups(self) -> list[generated.GroupDTO] | None:
        return self.raw.watcherGroups

    @property
    def capabilities(self) -> TaskCapabilities | None:
        """Typed capabilities facade."""
        return self._capabilities

    @property
    def sub_tasks(self) -> list[SubTask]:
        """Re-validate each opaque ``SubTaskDTO`` stub into :class:`SubTask`."""
        if not self.raw.subTasks:
            return []
        result = []
        for item in self.raw.subTasks:
            root = _resolve_root(item)
            if isinstance(root, dict):
                result.append(SubTask(generated.SubTaskDTO1.model_validate(root)))
        return result

    @property
    def reminders(self) -> list[TaskReminder]:
        """Re-validate each opaque ``TaskReminderDTO`` stub into :class:`TaskReminder`."""
        if not self.raw.reminders:
            return []
        result = []
        for item in self.raw.reminders:
            root = _resolve_root(item)
            if isinstance(root, dict):
                result.append(TaskReminder(generated.TaskReminderDTO1.model_validate(root)))
        return result

    @classmethod
    def from_dict(cls, data: dict) -> Task:  # type: ignore[type-arg]
        """Parse from a raw API response dict.

        Args:
            data: Parsed JSON response body.

        Returns:
            A new :class:`Task`.
        """
        raw = generated.GetTaskDetailResponseDTO.model_validate(data)
        return cls(raw)

capabilities property

Typed capabilities facade.

occ_token property

Token di concorrenza ottimistica, da passare a TasksAPI.edit.

reminders property

Re-validate each opaque TaskReminderDTO stub into :class:TaskReminder.

state property

Task lifecycle state (integer code). Use .raw.currentWorkflowState for the post workflow state DTO.

sub_tasks property

Re-validate each opaque SubTaskDTO stub into :class:SubTask.

from_dict(data) classmethod

Parse from a raw API response dict.

Parameters:

Name Type Description Default
data dict

Parsed JSON response body.

required

Returns:

Type Description
Task

A new :class:Task.

Source code in src/pynteracta/models/facade/tasks.py
@classmethod
def from_dict(cls, data: dict) -> Task:  # type: ignore[type-arg]
    """Parse from a raw API response dict.

    Args:
        data: Parsed JSON response body.

    Returns:
        A new :class:`Task`.
    """
    raw = generated.GetTaskDetailResponseDTO.model_validate(data)
    return cls(raw)

pynteracta.models.facade.tasks.TaskWriteResult

Façade sulla risposta di create ed edit di un task.

taskData e capabilities sono stub RootModel nei modelli generati: la façade li rivalida nelle varianti tipizzate TaskDetailDTO1 e TaskCapabilitiesDTO1.

Attributes:

Name Type Description
raw

Il DTO generato sottostante (CreateTaskResponseDTO o EditTaskResponseDTO).

Source code in src/pynteracta/models/facade/tasks.py
class TaskWriteResult:
    """Façade sulla risposta di ``create`` ed ``edit`` di un task.

    ``taskData`` e ``capabilities`` sono stub ``RootModel`` nei modelli generati: la façade li
    rivalida nelle varianti tipizzate ``TaskDetailDTO1`` e ``TaskCapabilitiesDTO1``.

    Attributes:
        raw: Il DTO generato sottostante (``CreateTaskResponseDTO`` o ``EditTaskResponseDTO``).
    """

    def __init__(
        self, raw: generated.CreateTaskResponseDTO | generated.EditTaskResponseDTO
    ) -> None:
        self.raw = raw
        self._task: generated.TaskDetailDTO1 | None = None
        self._capabilities: TaskCapabilities | None = None
        if raw.taskData is not None:
            root = _resolve_root(raw.taskData)
            if isinstance(root, dict):
                self._task = generated.TaskDetailDTO1.model_validate(root)
        if raw.capabilities is not None:
            root = _resolve_root(raw.capabilities)
            if isinstance(root, dict):
                self._capabilities = TaskCapabilities(
                    generated.TaskCapabilitiesDTO1.model_validate(root)
                )

    @property
    def task_id(self) -> int | None:
        """Id del task: dalla risposta di creazione, altrimenti dai dati del task."""
        task_id = getattr(self.raw, "taskId", None)
        if task_id is not None:
            return int(task_id)
        return self._task.id if self._task is not None else None

    @property
    def next_occ_token(self) -> int | None:
        """Token di concorrenza da usare per la modifica successiva."""
        return self.raw.nextOccToken

    @property
    def task(self) -> generated.TaskDetailDTO1 | None:
        """Dati del task dopo la scrittura, tipizzati."""
        return self._task

    @property
    def capabilities(self) -> TaskCapabilities | None:
        """Capabilities sul task dopo la scrittura."""
        return self._capabilities

    @classmethod
    def from_create(cls, data: dict) -> TaskWriteResult:  # type: ignore[type-arg]
        """Parse the response of ``create-task``."""
        return cls(generated.CreateTaskResponseDTO.model_validate(data))

    @classmethod
    def from_edit(cls, data: dict) -> TaskWriteResult:  # type: ignore[type-arg]
        """Parse the response of ``edit-task``."""
        return cls(generated.EditTaskResponseDTO.model_validate(data))

capabilities property

Capabilities sul task dopo la scrittura.

next_occ_token property

Token di concorrenza da usare per la modifica successiva.

task property

Dati del task dopo la scrittura, tipizzati.

task_id property

Id del task: dalla risposta di creazione, altrimenti dai dati del task.

from_create(data) classmethod

Parse the response of create-task.

Source code in src/pynteracta/models/facade/tasks.py
@classmethod
def from_create(cls, data: dict) -> TaskWriteResult:  # type: ignore[type-arg]
    """Parse the response of ``create-task``."""
    return cls(generated.CreateTaskResponseDTO.model_validate(data))

from_edit(data) classmethod

Parse the response of edit-task.

Source code in src/pynteracta/models/facade/tasks.py
@classmethod
def from_edit(cls, data: dict) -> TaskWriteResult:  # type: ignore[type-arg]
    """Parse the response of ``edit-task``."""
    return cls(generated.EditTaskResponseDTO.model_validate(data))

pynteracta.models.facade.tasks.TaskCapabilities

Thin facade over :class:~generated.TaskCapabilitiesDTO1.

Attributes:

Name Type Description
raw

The underlying typed DTO.

Source code in src/pynteracta/models/facade/tasks.py
class TaskCapabilities:
    """Thin facade over :class:`~generated.TaskCapabilitiesDTO1`.

    Attributes:
        raw: The underlying typed DTO.
    """

    def __init__(self, raw: generated.TaskCapabilitiesDTO1) -> None:
        self.raw = raw

    @property
    def can_view_detail(self) -> bool | None:
        return self.raw.canViewDetail

    @property
    def can_modify(self) -> bool | None:
        return self.raw.canModify

    @property
    def can_update_state(self) -> bool | None:
        return self.raw.canUpdateState

    @property
    def can_copy(self) -> bool | None:
        return self.raw.canCopy

    @property
    def can_delete(self) -> bool | None:
        return self.raw.canDelete

    @property
    def can_add_comment(self) -> bool | None:
        return self.raw.canAddComment

    @property
    def can_edit_attachments(self) -> bool | None:
        return self.raw.canEditAttachments

    @property
    def can_edit_reminders(self) -> bool | None:
        return self.raw.canEditReminders

pynteracta.models.facade.tasks.SubTask

Thin facade over :class:~generated.SubTaskDTO1.

Attributes:

Name Type Description
raw

The underlying typed DTO.

Source code in src/pynteracta/models/facade/tasks.py
class SubTask:
    """Thin facade over :class:`~generated.SubTaskDTO1`.

    Attributes:
        raw: The underlying typed DTO.
    """

    def __init__(self, raw: generated.SubTaskDTO1) -> None:
        self.raw = raw

    @property
    def id(self) -> int | None:
        return self.raw.id

    @property
    def description(self) -> str | None:
        return self.raw.description

    @property
    def state(self) -> int | None:
        return self.raw.state

pynteracta.models.facade.tasks.TaskReminder

Thin facade over :class:~generated.TaskReminderDTO1.

Attributes:

Name Type Description
raw

The underlying typed DTO.

Source code in src/pynteracta/models/facade/tasks.py
class TaskReminder:
    """Thin facade over :class:`~generated.TaskReminderDTO1`.

    Attributes:
        raw: The underlying typed DTO.
    """

    def __init__(self, raw: generated.TaskReminderDTO1) -> None:
        self.raw = raw

    @property
    def id(self) -> int | None:
        return self.raw.id

    @property
    def value(self) -> int | None:
        return self.raw.value

    @property
    def range(self) -> int | None:
        return self.raw.range

    @property
    def types(self) -> list[int] | None:
        return self.raw.types