Skip to content

Tools Security

Ryan James edited this page Aug 17, 2026 · 3 revisions

Security tools

This page is generated from the code by scripts/gen_wiki_tools.py — do not edit by hand.

22 tools · category token security · enable with DATAVERSE_TOOLS=security (unset enables every category)

Tool-Index · Home

Every tool returns JSON. Errors return {"error": true, "message": "..."}. Write tools require DATAVERSE_ALLOW_WRITE=true; delete tools require DATAVERSE_ALLOW_DELETE=true.


dataverse_add_team_members

Write · idempotent · category security

Add one or more system users to a Dataverse team.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
team_id str yes GUID of the team to add members to. (min_len=36)
user_ids list[str] yes List of system user GUIDs to add as team members. At least one user_id must be provided. (min_len=1)

Returns {"team_id": ..., "results": ..., "total": ..., "succeeded": ..., "failed": ...}.


dataverse_assign_security_role

Write · idempotent · category security

Assign a security role to a user or team via the Web API $ref association.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
role_id str yes GUID of the security role to assign. (min_len=36)
user_id str | None no None GUID of the system user to assign the role to. Provide exactly one of user_id or team_id.
team_id str | None no None GUID of the team to assign the role to. Provide exactly one of user_id or team_id.

dataverse_audit_user_access

Read · idempotent · category security

Return a composite access report for a Dataverse system user.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
user_id str | None no None GUID of the system user to audit. Provide either user_id or user_domain_name, not both. (min_len=36)
user_domain_name str | None no None Domain name of the user to audit (e.g., 'user@contoso.com'). Provide either user_id or user_domain_name, not both. (min_len=1)
target_entity_set_name str | None no None OData collection name of a specific record to check access against (e.g., 'accounts'). Requires target_record_id. (pattern=^[a-zA-Z_][a-zA-Z0-9_]*$)
target_record_id str | None no None GUID of the specific record to check access against. Requires target_entity_set_name. (min_len=36)
include_privileges bool no True When True, includes the user's effective privilege list. Set False to skip this call and speed up the report.

dataverse_get_attribute_change_history

Read · idempotent · category security

Retrieve the audit trail for ONE COLUMN of ONE RECORD — who changed this field.

Note: MIND THE SINGULAR/PLURAL SPLIT — this tool takes the SAME TABLE TWICE, under two different names, and they are NOT interchangeable.

Note: ON A 404, READ THE ERROR CODE — DO NOT ASSUME WHICH FAILURE IT IS.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
entity_set_name str yes OData collection name of the record's table — the PLURAL entity set name ('accounts', 'contacts', 'new_projects'), NOT the singular logical name this tool ALSO takes as table_logical_name. Use dataverse_get_entity_sets to confirm it; the plural is irregular often enough that guessing it costs a 404. (min_len=1, max_len=64, pattern=^[a-zA-Z_][a-zA-Z0-9_]*$)
record_id str yes GUID of the record whose column history to retrieve (the row's primary key, e.g. an accountid). It becomes the key predicate inside the target EntityReference, so it must be a well-formed GUID.
table_logical_name str yes SINGULAR lowercase logical name of the record's table ('account', not 'accounts'). It is NOT sent to the function — it is used only to probe whether auditing is enabled on the table and the column when the result comes back empty, which is what turns an empty answer into a usable one. (min_len=1, max_len=50, pattern=^[a-zA-Z_][a-zA-Z0-9_]*$)
column_logical_name str yes Lowercase logical name of the column whose changes to retrieve ('name', 'telephone1', 'new_status'). (min_len=1, max_len=50, pattern=^[a-zA-Z_][a-zA-Z0-9_]*$)
top int no 50 Maximum number of audit detail entries to return. The function is called without PagingInfo, so the list is trimmed here; has_more reports whether anything was cut. (ge=1, le=5000)

dataverse_get_audit_details

Read · idempotent · category security

Retrieve full details from a single audit record.

Note: Requires auditing enabled on the org. If auditing is disabled, Dataverse returns an HTTP error — check the error message for guidance.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
audit_id str yes GUID of the audit record whose full details to retrieve (e.g., 'a1b2c3d4-1234-5678-abcd-ef0123456789'). Obtain audit record GUIDs from dataverse_list_audit or dataverse_retrieve_record_change_history. (min_len=36)

Returns {"audit_id": ..., "audit_detail": ...}.


dataverse_get_role_privileges

Read · idempotent · category security

Answer "what can this security role actually DO?" — list a role's privileges.

Note: A well-formed but nonexistent role id returns an ERROR, not an empty list: Dataverse answers HTTP 404 [0x80040217] "Entity 'role' With Id = ... Does Not Exist", surfaced through the standard {"error": true, "message": ...} envelope.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
role_id str yes GUID of the security role whose privileges to list. Use dataverse_list_security_roles to find a role id by name, or dataverse_get_security_role if you already have one and want the role record itself.
top int no 50 See note below.
  • top — Maximum number of privilege entries to return. RetrieveRolePrivilegesRole has no server-side paging — it returns every privilege in one response, and a System Administrator role was measured live at 4,132 privileges in a ~1 MB payload — so the list is trimmed here. total_count always reports the full number Dataverse returned and has_more says whether anything was trimmed, so the magnitude is never hidden. depth_summary is computed over ALL entries, not just the returned page. Raise this to see more. (ge=1, le=1000)

dataverse_get_security_role

Read · idempotent · category security

Retrieve a single Dataverse security role by its GUID.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
role_id str yes GUID of the security role to retrieve. (min_len=36)
select list[str] | None no None Columns to return. Defaults to roleid, name, _businessunitid_value, ismanaged, modifiedon.

Returns {"record": ...}.


dataverse_get_team

Read · idempotent · category security

Retrieve a single Dataverse team by its GUID.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
team_id str yes GUID of the team to retrieve. (min_len=36)
select list[str] | None no None Columns to return. Defaults to teamid, name, teamtype, _businessunitid_value, isdefault, modifiedon.

Returns {"record": ...}.


dataverse_get_team_privileges

Read · idempotent · category security

Answer "what can this TEAM actually DO?" — list a team's privileges.

Note: AN EMPTY LIST IS A REAL ANSWER, NOT A FAILURE. count: 0 with normalized: true means the team has NO DIRECTLY-ASSIGNED SECURITY ROLES — a common and entirely normal state, and the usual one: most teams get their access from their members' own roles rather than from a role assigned to the team itself.

Note: A well-formed but nonexistent team id returns an ERROR, not an empty list — VERIFIED LIVE: Dataverse answers HTTP 404 [0x80040217] "Does Not Exist", as the role function does, and it is surfaced through the standard {"error": true, "message": ...} envelope.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
team_id str yes GUID of the team whose privileges to list. Use dataverse_list_teams to find a team id by name, or dataverse_get_team if you already have one and want the team record itself.
top int no 50 See note below.
  • top — Maximum number of privilege entries to return. RetrieveTeamPrivileges has no server-side paging — it returns every privilege in one response, and its sibling RetrieveRolePrivilegesRole was measured live at 4,132 privileges in a ~1 MB payload for a broad role — so the list is trimmed here. total_count always reports the full number Dataverse returned and has_more says whether anything was trimmed, so the magnitude is never hidden. depth_summary is computed over ALL entries, not just the returned page. Raise this to see more. (ge=1, le=1000)

dataverse_get_user

Read · idempotent · category security

Retrieve a single Dataverse system user by their GUID.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
user_id str yes GUID of the system user to retrieve. Use dataverse_whoami to get the current caller's UserId. (min_len=36)
select list[str] | None no None Columns to return. Defaults to systemuserid, fullname, domainname, internalemailaddress, isdisabled, _businessunitid_value.

Returns {"record": ...}.


dataverse_list_audit

Read · idempotent · category security

Query the audit table with optional OData filters.

Note: Requires auditing enabled on the org. If auditing is disabled, Dataverse may return an empty result set or an HTTP error.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
filter str | None no None OData $filter expression to narrow results. Use lowercase logical names. Examples: "operation eq 2" (Updates only), "objecttypecode eq 'account'", "createdon gt 2024-01-01T00:00:00Z", "_userid_value eq '<guid>'"
select list[str] | None no None Columns to return. Defaults to auditid, createdon, operation, action, objecttypecode, _userid_value, _objectid_value, transactionid.
orderby list[str] | None no None Sort order. Each entry is 'column_name asc' or 'column_name desc'. Example: ['createdon desc']
top int no 50 Maximum number of audit records to return. (ge=1, le=5000)

Returns {"records": ..., "count": ..., "has_more": ...}.


dataverse_list_business_units

Read · idempotent · category security

List business units in the Dataverse environment.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
filter str | None no None OData $filter expression to narrow results (e.g., "isdisabled eq false").
select list[str] | None no None Columns to return. Defaults to businessunitid, name, _parentbusinessunitid_value, isdisabled, modifiedon.
top int no 50 Maximum number of business units to return. (ge=1, le=5000)

Returns {"records": ..., "count": ..., "has_more": ...}.


dataverse_list_privileges

Read · idempotent · category security

List the privileges DEFINED in the environment — the catalogue of what CAN be granted.

Note: ACCESS RIGHTS ARE DECODED BY A HAND-ROLLED MAP, AND THAT IS THE POINT. The accessright column is an integer with NO option set behind it anywhere in Dataverse: the PicklistAttributeMetadata cast 404s, GlobalOptionSetDefinitions for it 404s, and annotation-included FormattedValues return only the integer with thousands separators.

Note: total_count COMES FROM AN AGGREGATION, NOT @odata.count. On this collection @odata.count CAPS AT 5,000 and lies — ?$count=true reports 5,000 where the true catalogue is ~7,346 — so the count is taken with $apply=aggregate($count as c), which bypasses the cap.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
table_logical_name str | None no None See note below.
name_startswith str | None no None Optional prefix of the privilege NAME to filter on, case-insensitively ('prvRead', 'prvCreate', 'prvAppendTo'). Privilege names follow the prv{Verb}{Table} form, so a prefix is the natural way to ask 'every read privilege'. Combine it with table_logical_name to narrow one table's privileges further. (min_len=1, max_len=100)
access_right Literal['None', 'ReadAccess', 'WriteAccess', 'AppendAccess', 'AppendToAccess', 'CreateAccess', 'DeleteAccess', 'ShareAccess', 'AssignAccess'] | None no None Optional access right to filter on, given by NAME. The name is translated to its integer here — no caller text is ever interpolated into the query. Note that 'None' is a REAL access right (the value 0, carried by non-CRUD privileges such as prvActOnBehalfOfAnotherUser), not a way of saying 'no filter' — omit the field entirely for that.
top int no 50 Maximum number of privileges to return. The environment-wide catalogue runs to roughly 7,300 rows, so the list is trimmed. total_count reports the TRUE total (obtained by aggregation, because @odata.count caps at 5,000 on this collection and under-reports) and has_more says whether anything was trimmed, so the magnitude is never hidden. (ge=1, le=5000)
  • table_logical_name — Optional SINGULAR logical name of a table ('account', not 'accounts') to scope the list to that table's privileges. The value is LOWERCASED for you — Dataverse logical names are always lowercase and it rejects 'Account' outright — so only the singular/plural distinction is yours to get right. Scoping goes through a join table, NOT by matching privilege names: matching on the name is wrong in general (endswith(name,'Role') returns 25 privileges spanning role, connectionrole, relationshiprole and mspp_webrole), it just happens to look right on the tables people test with. Omit it to list the whole environment-wide privilege catalogue. (min_len=1, max_len=50, pattern=^[a-zA-Z_][a-zA-Z0-9_]*$)

dataverse_list_security_roles

Read · idempotent · category security

List security roles in the Dataverse environment.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
filter str | None no None OData $filter expression to narrow results. Use lowercase logical names (e.g., "ismanaged eq false", "_businessunitid_value eq '<guid>'")
select list[str] | None no None Columns to return. Defaults to roleid, name, _businessunitid_value, ismanaged, modifiedon.
top int no 50 Maximum number of roles to return. (ge=1, le=5000)

Returns {"records": ..., "count": ..., "has_more": ...}.


dataverse_list_shared_principals

Read · idempotent · category security

Answer "WHO has this record because it was SHARED with them?".

Note: A WRONG ENTITY SET NAME LOOKS EXACTLY LIKE A MISSING RECORD. VERIFIED LIVE: a nonexistent record id and a VALID id paired with the WRONG entity set both return HTTP 404 [0x80040217] "Entity '<Type>' With Id = <guid> Does Not Exist" from BOTH functions — the same status, the same error code, indistinguishable text.

Note: So when this tool errors with "Does Not Exist", CHECK THE ENTITY SET NAME FIRST (plural — 'accounts', not 'account'; see the asymmetry note above) before concluding the record is gone.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
entity_set_name str yes OData collection name of the record's table — the PLURAL entity set name ('accounts', 'contacts', 'new_projects'), NOT the singular logical name dataverse_retrieve_access_origin takes. Use dataverse_get_entity_sets to confirm it ('account' -> 'accounts'); the plural is irregular often enough that guessing it costs a 404. (min_len=1, max_len=64, pattern=^[a-zA-Z_][a-zA-Z0-9_]*$)
record_id str yes GUID of the record whose shares to list (the row's primary key, e.g. an accountid). It becomes the key predicate inside the target EntityReference, so it must be a well-formed GUID.
top int no 50 Maximum number of entries to return from EACH of the two functions. Neither has server-side paging, and a heavily shared record can carry many principals, so each list is trimmed here. total_count and has_more are reported per list and always describe the full set, so the true magnitude is never hidden. (ge=1, le=1000)

dataverse_list_teams

Read · idempotent · category security

List teams in the Dataverse environment.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
filter str | None no None OData $filter expression to narrow results (e.g., "teamtype eq 0" for owner teams, "isdefault eq false").
select list[str] | None no None Columns to return. Defaults to teamid, name, teamtype, _businessunitid_value, isdefault, modifiedon.
top int no 50 Maximum number of teams to return. (ge=1, le=5000)

Returns {"records": ..., "count": ..., "has_more": ...}.


dataverse_list_users

Read · idempotent · category security

List system users (systemusers) in the Dataverse environment.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
filter str | None no None OData $filter expression to narrow results (e.g., "isdisabled eq false", "domainname eq 'user@contoso.com'").
select list[str] | None no None Columns to return. Defaults to systemuserid, fullname, domainname, internalemailaddress, isdisabled, _businessunitid_value.
top int no 50 Maximum number of users to return. (ge=1, le=5000)

Returns {"records": ..., "count": ..., "has_more": ...}.


dataverse_remove_security_role

Delete · idempotent · category security

Remove a security role from a user or team via the Web API $ref disassociation.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
role_id str yes GUID of the security role to remove. (min_len=36)
user_id str | None no None GUID of the system user to remove the role from. Provide exactly one of user_id or team_id.
team_id str | None no None GUID of the team to remove the role from. Provide exactly one of user_id or team_id.

dataverse_remove_team_members

Delete · idempotent · category security

Remove one or more system users from a Dataverse team.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
team_id str yes GUID of the team to remove members from. (min_len=36)
user_ids list[str] yes List of system user GUIDs to remove from the team. At least one user_id must be provided. (min_len=1)

Returns {"team_id": ..., "results": ..., "total": ..., "succeeded": ..., "failed": ...}.


dataverse_retrieve_access_origin

Read · idempotent · category security

Answer "WHY does this principal have access to this record?".

Note: HTTP 200 DOES NOT MEAN "HAS ACCESS" — READ THE STRING. Three materially different outcomes all come back as a successful call with normalized true, and they are distinguishable ONLY by the English prose inside the string.

Note: The text is passed through verbatim and deliberately NOT classified into a boolean: pattern-matching platform prose is fragile and locale-dependent, and a wrong security verdict is worse than none.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
object_id str yes GUID of the record whose access is being explained (the row's primary key, e.g. an accountid or an incidentid).
logical_name str yes Lowercase logical name of the table that record belongs to (e.g. 'account', 'incident', 'new_project') — the singular logical name, NOT the plural entity set name. Use dataverse_list_tables to confirm it. (min_len=1, max_len=50, pattern=^[a-zA-Z_][a-zA-Z0-9_]*$)
principal_id str yes GUID of the principal whose access is being explained. Must be a systemuser or a team — no other principal type is accepted. Use dataverse_list_users or dataverse_list_teams to find one, or dataverse_whoami for the current caller's own UserId.

dataverse_retrieve_record_change_history

Read · idempotent · category security

Retrieve the full audit change history for a specific record.

Note: AN HTTP 200 IS NOT PROOF OF ANYTHING, AND ON A 404 READ THE ERROR CODE.

Note: NOT EVERY ENTRY IS A RESULT. Dataverse MAY add org-level audit-CONFIGURATION rows (records of auditing itself being switched on or off) to a response.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
entity_set_name str yes OData collection name of the record's table (e.g., 'accounts', 'contacts'). Use dataverse_get_entity_sets to discover the correct name. (min_len=1, pattern=^[a-zA-Z_][a-zA-Z0-9_]*$)
record_id str yes GUID of the record whose change history to retrieve (e.g., 'a1b2c3d4-1234-5678-abcd-ef0123456789'). (min_len=36)
top int no 50 Maximum number of audit detail entries to return. (ge=1, le=5000)

dataverse_set_user_state

Write · idempotent · category security

Enable or disable a Dataverse system user.

Note: The caller must hold the System Administrator role, and a user cannot disable their own account. In online environments user lifecycle is also governed by Microsoft Entra ID.

Param Type Req Default Notes
dataverse_url str yes Required Dataverse organization URL for this request (e.g., 'https://yourorg.crm.dynamics.com').
user_id str yes GUID of the system user to enable or disable. Use dataverse_whoami to get the current caller's UserId. (min_len=36)
disabled bool yes True to disable the user (statecode=1, statuscode=2); False to enable the user (statecode=0, statuscode=1).

Returns {"updated": ..., "user_id": ..., "disabled": ...}.


Clone this wiki locally