Skip to content

Admin Manage API

The AdminManageAPI client exposes four admin-only read-form endpoints, added in v0.6.0, and the workspace write edit_workspace().

Resource grouping

All these endpoints are grouped under client.admin_manage (D-v0.6-1), separate from the posts, users, or catalogs resource clients. The other admin writes live on their resource: the user credentials on client.users.edit_credentials() (see Users), catalogs and entries on client.catalogs (see catalogs). client.users.get_credentials_for_edit(), client.catalogs.get_for_edit() and client.catalogs.get_entry_for_edit() are aliases of the read forms below.

Endpoint scope

Each read form returns the full editable state of an entity plus an occToken (optimistic concurrency token) for the write operations. Every facade exposes it as occ_token; CatalogForEdit also exposes parent_catalog_id.

Method Path Facade
workspace_for_edit GET admin/manage/workspaces/{workspaceId}/edit WorkspaceForEdit
catalog_for_edit GET admin/manage/catalogs/{catalogId}/edit CatalogForEdit
catalog_entry_for_edit GET admin/manage/catalogs/{catalogId}/entries/{entryId}/edit CatalogEntryForEdit
user_credentials_for_edit GET admin/manage/users/{userId}/credentials/edit UserCredentialsForEdit
edit_workspace PUT admin/manage/workspaces/{workspaceId}/{occToken} WorkspaceForEdit

Usage

from pynteracta.client import InteractaClient

with InteractaClient(base_url="https://tenant.example.com", credentials=...) as client:
    # Workspace edit form
    ws = client.admin_manage.workspace_for_edit(88)
    print(ws.id, ws.name, ws.member_users_count, ws.occ_token)

    # Catalog edit form
    catalog = client.admin_manage.catalog_for_edit(5)
    print(catalog.id, catalog.name, catalog.parent_catalog_id, catalog.occ_token)

    # Catalog entry edit form
    entry = client.admin_manage.catalog_entry_for_edit(5, 100)
    print(entry.id, entry.label, entry.external_id, entry.parents_count)

    # User credentials edit form
    creds = client.admin_manage.user_credentials_for_edit(1042)
    print(creds.has_google_credentials, creds.has_custom_credentials, creds.custom_username)
    print(creds.occ_token)  # the token for client.users.edit_credentials()

Editing a workspace

with InteractaClient(base_url="https://tenant.example.com", credentials=...) as client:
    ws = client.admin_manage.workspace_for_edit(88)
    cover = client.attachments.upload("cover.png")
    client.admin_manage.edit_workspace(
        88,
        ws.occ_token,
        name=ws.name,  # required on every edit
        translations=ws.translations,
        description=ws.description,
        add_user_ids=[1042],
        add_user_role_associations=[{"userId": 1042, "roleId": 4}],
        new_cover_image_content_ref=cover.content_ref,
    )
    ws = client.admin_manage.workspace_for_edit(88)  # fresh counts

edit_workspace(workspace_id, occ_token, ...) sends the occToken in the path and only the fields given in the body; it also takes remove_user_ids, add_group_ids, remove_group_ids, add_group_role_associations ({"groupId", "roleId"}), remove_role_association_ids and new_logo_content_ref. The cover and logo contentRef come from client.attachments.upload(). edit_workspace_raw(workspace_id, occ_token, req) takes an EditWorkspaceRequestDTO.

What the test tenant does (verified):

  • the name is required on every edit: without it the server answers 400 with REQUIRED_FIELD on name, also when only members change. Pass the name, translations and description as read; admin-manage edit-workspace in the CLI does it for you. Whether omitted translations and description are cleared was not tested, to protect the shared test workspace;
  • the response is the workspace form: its occ_token is the token for the next edit, but its user and group counts are 0. Read the form again for the real counts (the CLI does);
  • one request, never retried: a 409 raises ConcurrencyError.

Field curation (Q-v0.6-1)

Narrow facade properties surface the directly-typed scalar fields of each response DTO, plus light convenience accessors that dig one level into the nested editable-content blocks (which the code generator emits as RootModel[Any] stubs) for the human-facing name/label/credential flags. Complex nested structures (the full contentData, communityAssociations, parents, per-provider credential configs, the paginated member lists of the workspace) stay on .raw. Languages whose value is null in a name, label, translation or description map are dropped from the facades: the generated models accept strings only.

API Reference

pynteracta.api.admin_manage.AdminManageAPI

Bases: ResourceClient

Client for the admin/manage edit (read-form) endpoints.

These are admin-only read helpers, propaedeutic to the write surface. Each returns the entity's editable state and an occToken, esposto come occ_token da tutte le façade. :meth:edit_workspace è l'unica scrittura di questo client (spec 06): una sola richiesta, mai ripetuta; un 409 è ConcurrencyError.

workspace_for_edit(workspace_id)

GET /admin/manage/workspaces/{workspaceId}/edit.

Parameters:

Name Type Description Default
workspace_id int

The workspace ID to fetch.

required

catalog_for_edit(catalog_id)

GET /admin/manage/catalogs/{catalogId}/edit.

Stessa richiesta di :meth:~pynteracta.api.catalogs.CatalogsAPI.get_for_edit; la scrittura è :meth:~pynteracta.api.catalogs.CatalogsAPI.edit (spec 06).

Parameters:

Name Type Description Default
catalog_id int

The catalog ID to fetch.

required

catalog_entry_for_edit(catalog_id, entry_id)

GET /admin/manage/catalogs/{catalogId}/entries/{entryId}/edit.

Stessa richiesta di :meth:~pynteracta.api.catalogs.CatalogsAPI.get_entry_for_edit; la scrittura è :meth:~pynteracta.api.catalogs.CatalogsAPI.edit_entry (spec 06).

Parameters:

Name Type Description Default
catalog_id int

The catalog ID owning the entry.

required
entry_id int

The catalog entry ID to fetch.

required

user_credentials_for_edit(user_id)

GET /admin/manage/users/{userId}/credentials/edit.

Stessa richiesta di :meth:~pynteracta.api.users.UsersAPI.get_credentials_for_edit; la scrittura è :meth:~pynteracta.api.users.UsersAPI.edit_credentials (spec 05).

Parameters:

Name Type Description Default
user_id int

The user ID whose credentials to fetch.

required

edit_workspace(workspace_id, occ_token, *, name=None, translations=None, description=None, new_cover_image_content_ref=None, new_logo_content_ref=None, add_user_ids=None, remove_user_ids=None, add_group_ids=None, remove_group_ids=None, add_user_role_associations=None, add_group_role_associations=None, remove_role_association_ids=None)

PUT /admin/manage/workspaces/{workspaceId}/{occToken}: modifica un workspace.

L'occToken sta nel path, non nel corpo; il corpo contiene solo i campi passati. La risposta è il form del workspace: il suo occ_token vale per la modifica successiva. I campi omessi non vengono inviati (semantica del server in docs/api/admin_manage.md).

Parameters:

Name Type Description Default
workspace_id int

Il workspace da modificare.

required
occ_token int

Token letto da WorkspaceForEdit.occ_token.

required
name str | None

Nome del workspace.

None
translations dict[str, str] | None

Traduzioni del nome per lingua.

None
description dict[str, str] | None

Descrizione per lingua.

None
new_cover_image_content_ref str | None

contentRef della copertina, da client.attachments.upload (spec 04).

None
new_logo_content_ref str | None

contentRef del logo.

None
add_user_ids list[int] | None

Utenti da aggiungere come membri.

None
remove_user_ids list[int] | None

Utenti da togliere.

None
add_group_ids list[int] | None

Gruppi da aggiungere come membri.

None
remove_group_ids list[int] | None

Gruppi da togliere.

None
add_user_role_associations RoleAssociations

Ruoli da dare a utenti ({"userId", "roleId"}).

None
add_group_role_associations RoleAssociations

Ruoli da dare a gruppi ({"groupId", "roleId"}).

None
remove_role_association_ids list[int] | None

Associazioni di ruolo da togliere.

None

edit_workspace_raw(workspace_id, occ_token, req)

PUT /admin/manage/workspaces/{workspaceId}/{occToken} da un DTO già costruito.

Facade Reference

pynteracta.models.facade.admin_manage.WorkspaceForEdit

Narrow facade over :class:~generated.GetWorkspaceForEditResponseDTO.

The editable payload (name, description, members, …) lives in contentData, which the generator emits as a RootModel[Any] stub; :attr:name and :attr:description re-validate it into the typed sibling for convenience. :attr:occ_token è il token da passare ad admin_manage.edit_workspace; la stessa façade avvolge la risposta di quella scrittura, il cui occ_token vale per la modifica successiva (spec 06).

Attributes:

Name Type Description
raw

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

Source code in src/pynteracta/models/facade/admin_manage.py
class WorkspaceForEdit:
    """Narrow facade over :class:`~generated.GetWorkspaceForEditResponseDTO`.

    The editable payload (``name``, ``description``, members, …) lives in ``contentData``, which
    the generator emits as a ``RootModel[Any]`` stub; :attr:`name` and :attr:`description`
    re-validate it into the typed sibling for convenience. :attr:`occ_token` è il token da passare
    ad ``admin_manage.edit_workspace``; la stessa façade avvolge la risposta di quella scrittura,
    il cui ``occ_token`` vale per la modifica successiva (spec 06).

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

    def __init__(self, raw: generated.GetWorkspaceForEditResponseDTO) -> None:
        self.raw = raw
        self._content = self._typed_content(raw)

    @staticmethod
    def _typed_content(
        raw: generated.GetWorkspaceForEditResponseDTO,
    ) -> generated.WorkspaceEditableContentDataDTO1 | None:
        if raw.contentData is None:
            return None
        root = _resolve_root(raw.contentData)
        if isinstance(root, dict):
            return generated.WorkspaceEditableContentDataDTO1.model_validate(
                _clean_maps(root, "translations", "description")
            )
        return None

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

    @property
    def name(self) -> str | None:
        """Workspace name (from the editable ``contentData`` block)."""
        return self._content.name if self._content is not None else None

    @property
    def translations(self) -> dict[str, str] | None:
        """Traduzioni del nome per lingua (da ``contentData``)."""
        return self._content.translations if self._content is not None else None

    @property
    def description(self) -> dict[str, str] | None:
        """Internationalized description map (from ``contentData``)."""
        return self._content.description if self._content is not None else None

    @property
    def occ_token(self) -> int | None:
        """Token di concorrenza per ``admin_manage.edit_workspace`` (spec 06, RF-023e)."""
        return self.raw.occToken

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

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

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

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

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

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

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

description property

Internationalized description map (from contentData).

name property

Workspace name (from the editable contentData block).

occ_token property

Token di concorrenza per admin_manage.edit_workspace (spec 06, RF-023e).

translations property

Traduzioni del nome per lingua (da contentData).

from_dict(data) classmethod

Parse from a raw API response dict.

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

pynteracta.models.facade.admin_manage.CatalogForEdit

Narrow facade over :class:~generated.GetCatalogForEditResponseDTO.

The editable payload (name map, parentCatalog, deleted) lives in contentData, a RootModel[Any] stub re-validated into its typed sibling for :attr:name and :attr:deleted. communityAssociations (the communities using this catalog) is summarized by :attr:community_associations_count; the full list stays on .raw. :attr:occ_token è il token per catalogs.edit; :attr:parent_catalog_id l'id del catalogo parent (spec 06).

Attributes:

Name Type Description
raw

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

Source code in src/pynteracta/models/facade/admin_manage.py
class CatalogForEdit:
    """Narrow facade over :class:`~generated.GetCatalogForEditResponseDTO`.

    The editable payload (``name`` map, ``parentCatalog``, ``deleted``) lives in ``contentData``,
    a ``RootModel[Any]`` stub re-validated into its typed sibling for :attr:`name` and
    :attr:`deleted`. ``communityAssociations`` (the communities using this catalog) is summarized
    by :attr:`community_associations_count`; the full list stays on ``.raw``. :attr:`occ_token` è
    il token per ``catalogs.edit``; :attr:`parent_catalog_id` l'id del catalogo parent (spec 06).

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

    def __init__(self, raw: generated.GetCatalogForEditResponseDTO) -> None:
        self.raw = raw
        self._content = self._typed_content(raw)

    @staticmethod
    def _typed_content(
        raw: generated.GetCatalogForEditResponseDTO,
    ) -> generated.CatalogEditableContentDTOModel | None:
        if raw.contentData is None:
            return None
        root = _resolve_root(raw.contentData)
        if isinstance(root, dict):
            return generated.CatalogEditableContentDTOModel.model_validate(
                _clean_maps(root, "name")
            )
        return None

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

    @property
    def name(self) -> dict[str, str] | None:
        """Internationalized catalog name map (from ``contentData``)."""
        return self._content.name if self._content is not None else None

    @property
    def deleted(self) -> bool | None:
        """Whether the catalog is logically deleted (from ``contentData``)."""
        return self._content.deleted if self._content is not None else None

    @property
    def parent_catalog_id(self) -> int | None:
        """Id del catalogo parent, da ``contentData.parentCatalog``; ``None`` senza parent."""
        if self._content is None or self._content.parentCatalog is None:
            return None
        root = _resolve_root(self._content.parentCatalog)
        if not isinstance(root, dict):
            return None
        return generated.CatalogDTOModel.model_validate(root).id

    @property
    def occ_token(self) -> int | None:
        """Token di concorrenza per ``catalogs.edit`` (spec 06, RF-023e)."""
        return self.raw.occToken

    @property
    def community_associations_count(self) -> int | None:
        """Number of communities that use this catalog."""
        assocs = self.raw.communityAssociations
        return len(assocs) if assocs is not None else None

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

community_associations_count property

Number of communities that use this catalog.

deleted property

Whether the catalog is logically deleted (from contentData).

name property

Internationalized catalog name map (from contentData).

occ_token property

Token di concorrenza per catalogs.edit (spec 06, RF-023e).

parent_catalog_id property

Id del catalogo parent, da contentData.parentCatalog; None senza parent.

from_dict(data) classmethod

Parse from a raw API response dict.

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

pynteracta.models.facade.admin_manage.CatalogEntryForEdit

Narrow facade over :class:~generated.GetCatalogEntryForEditResponseDTO.

All surfaced fields are directly typed on the response DTO. label is an internationalized map; parents (the hierarchical parent chain) is summarized by :attr:parents_count, with the full list available on .raw. :attr:occ_token è il token per catalogs.edit_entry (spec 06).

Attributes:

Name Type Description
raw

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

Source code in src/pynteracta/models/facade/admin_manage.py
class CatalogEntryForEdit:
    """Narrow facade over :class:`~generated.GetCatalogEntryForEditResponseDTO`.

    All surfaced fields are directly typed on the response DTO. ``label`` is an internationalized
    map; ``parents`` (the hierarchical parent chain) is summarized by :attr:`parents_count`, with
    the full list available on ``.raw``. :attr:`occ_token` è il token per ``catalogs.edit_entry``
    (spec 06).

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

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

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

    @property
    def label(self) -> dict[str, str] | None:
        """Internationalized entry label map."""
        return self.raw.label

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

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

    @property
    def occ_token(self) -> int | None:
        """Token di concorrenza per ``catalogs.edit_entry`` (spec 06, RF-023e)."""
        return self.raw.occToken

    @property
    def parents_count(self) -> int | None:
        """Number of parent entries (hierarchical dependency chain)."""
        parents = self.raw.parents
        return len(parents) if parents is not None else None

    @classmethod
    def from_dict(cls, data: dict) -> CatalogEntryForEdit:  # type: ignore[type-arg]
        """Parse from a raw API response dict; le lingue ``null`` di ``label`` sono scartate."""
        return cls(
            generated.GetCatalogEntryForEditResponseDTO.model_validate(_clean_maps(data, "label"))
        )

label property

Internationalized entry label map.

occ_token property

Token di concorrenza per catalogs.edit_entry (spec 06, RF-023e).

parents_count property

Number of parent entries (hierarchical dependency chain).

from_dict(data) classmethod

Parse from a raw API response dict; le lingue null di label sono scartate.

Source code in src/pynteracta/models/facade/admin_manage.py
@classmethod
def from_dict(cls, data: dict) -> CatalogEntryForEdit:  # type: ignore[type-arg]
    """Parse from a raw API response dict; le lingue ``null`` di ``label`` sono scartate."""
    return cls(
        generated.GetCatalogEntryForEditResponseDTO.model_validate(_clean_maps(data, "label"))
    )

pynteracta.models.facade.admin_manage.UserCredentialsForEdit

Narrow facade over :class:~generated.GetUserCredentialsForEditResponseDTO.

The response has no entity id (the user id is the path parameter). The credentials payload lives in userCredentialsConfiguration, a RootModel[Any] stub re-validated into its typed sibling. Narrow properties expose which credential kinds are configured plus the custom username/active flags; the full per-provider configuration stays on .raw. :attr:occ_token è il token di concorrenza da passare a users.edit_credentials (spec 05).

Attributes:

Name Type Description
raw

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

Source code in src/pynteracta/models/facade/admin_manage.py
class UserCredentialsForEdit:
    """Narrow facade over :class:`~generated.GetUserCredentialsForEditResponseDTO`.

    The response has no entity id (the user id is the path parameter). The credentials payload
    lives in ``userCredentialsConfiguration``, a ``RootModel[Any]`` stub re-validated into its
    typed sibling. Narrow properties expose which credential kinds are configured plus the custom
    username/active flags; the full per-provider configuration stays on ``.raw``.
    :attr:`occ_token` è il token di concorrenza da passare a ``users.edit_credentials`` (spec 05).

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

    def __init__(self, raw: generated.GetUserCredentialsForEditResponseDTO) -> None:
        self.raw = raw
        self._config = self._typed_config(raw)

    @staticmethod
    def _typed_config(
        raw: generated.GetUserCredentialsForEditResponseDTO,
    ) -> generated.UserCredentialsConfigurationDTO1 | None:
        if raw.userCredentialsConfiguration is None:
            return None
        root = _resolve_root(raw.userCredentialsConfiguration)
        if isinstance(root, dict):
            return generated.UserCredentialsConfigurationDTO1.model_validate(root)
        return None

    @staticmethod
    def _typed_custom(
        config: generated.UserCredentialsConfigurationDTO1 | None,
    ) -> generated.CustomUserCredentialsConfigurationDTOModel | None:
        if config is None or config.custom is None:
            return None
        root = _resolve_root(config.custom)
        if isinstance(root, dict):
            return generated.CustomUserCredentialsConfigurationDTOModel.model_validate(root)
        return None

    @property
    def has_google_credentials(self) -> bool:
        return self._config is not None and self._config.google is not None

    @property
    def has_microsoft_credentials(self) -> bool:
        return self._config is not None and self._config.microsoft is not None

    @property
    def has_custom_credentials(self) -> bool:
        return self._config is not None and self._config.custom is not None

    @property
    def custom_username(self) -> str | None:
        """Username of the custom (username/password) credentials, if configured."""
        custom = self._typed_custom(self._config)
        return custom.username if custom is not None else None

    @property
    def custom_active(self) -> bool | None:
        """Whether the custom credentials are active, if configured."""
        custom = self._typed_custom(self._config)
        return custom.active if custom is not None else None

    @property
    def occ_token(self) -> int | None:
        """Token di concorrenza da passare a ``users.edit_credentials`` (spec 05, RF-023a)."""
        return self.raw.occToken

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

custom_active property

Whether the custom credentials are active, if configured.

custom_username property

Username of the custom (username/password) credentials, if configured.

occ_token property

Token di concorrenza da passare a users.edit_credentials (spec 05, RF-023a).

from_dict(data) classmethod

Parse from a raw API response dict.

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