Microsoft Dataverse with Python and Zato Services
Overview
Microsoft Dataverse is a cloud-based data storage and management platform, often used with PowerApps and Dynamics 365.
Integrating Dataverse with Python via Zato enables automation, API orchestration, and seamless CRUD (Create, Read, Update, Delete) operations on any Dataverse object.
Below, you'll find practical code examples for working with Dataverse from Python, including detailed comments and explanations. The focus is on the "accounts" entity, but the same approach applies to any object in Dataverse.
Connecting to Dataverse and Retrieving Accounts
The main service class configures the Dataverse client and retrieves all accounts. Both the handle
and get_accounts
methods are shown together for clarity.
# -*- coding: utf-8 -*-# Zatofromzato.common.typing_importany_fromzato.server.serviceimportDataverseClient,ServiceclassMyService(Service):defhandle(self):# Set up Dataverse credentials - in a real service,# this would go to your configuration file.tenant_id='221de69a-602d-4a0b-a0a4-1ff2a3943e9f'client_id='17aaa657-557c-4b18-95c3-71d742fbc6a3'client_secret='MjsrO1zc0.WEV5unJCS5vLa1'org_url='https://org123456.api.crm4.dynamics.com'# Build the Dataverse client using the credentialsclient=DataverseClient(tenant_id=tenant_id,client_id=client_id,client_secret=client_secret,org_url=org_url)# Retrieve all accounts using a helper methodaccounts=self.get_accounts(client)# Process the accounts as needed (custom logic goes here)passdefget_accounts(self,client:'DataverseClient')->'any_':# Specify the API path for the accounts entitypath='accounts'# Call the Dataverse API to retrieve all accountsresponse=client.get(path)# Log the response for debugging/auditingself.logger.info(f'Dataverse response (get accounts): {response}')# Return the API response to the callerreturnresponse
{'@odata.context':'https://org1234567.crm4.dynamics.com/api/data/v9.0/$metadata#accounts','value':[{'@odata.etag':'W/"11122233"','territorycode':1,'accountid':'d92e6f18-36fb-4fa8-b7c2-ecc7cc28f50c','name':'Zato Test Account 1','_owninguser_value':'ea4dd84c-dee6-405d-b638-c37b57f00938'}]}
Let's check more examples - you'll note they all follow the same pattern as the first one.
Retrieving an Account by ID
defget_account_by_id(self,client:'DataverseClient',account_id:'str')->'any_':# Construct the API path using the account's GUIDpath=f'accounts({account_id})'# Call the Dataverse API to fetch the accountresponse=client.get(path)# Log the response for traceabilityself.logger.info(f'Dataverse response (get account by ID): {response}')# Return the fetched accountreturnresponse
Retrieving an Account by Name
defget_account_by_name(self,client:'DataverseClient',account_name:'str')->'any_':# Construct the API path with a filter for the account namepath=f"accounts?$filter=name eq '{account_name}'"# Call the Dataverse API with the filterresponse=client.get(path)# Log the response for auditingself.logger.info(f'Dataverse response (get account by name): {response}')# Return the filtered account(s)returnresponse
Creating a New Account
defcreate_account(self,client:'DataverseClient')->'any_':# Specify the API path for account creationpath='accounts'# Prepare the data for the new accountaccount_data={'name':'New Test Account','telephone1':'+1-555-123-4567','emailaddress1':'hello@example.com','address1_city':'Prague','address1_country':'Czech Republic',}# Call the Dataverse API to create the accountresponse=client.post(path,account_data)# Log the response for traceabilityself.logger.info(f'Dataverse response (create account): {response}')# Return the API responsereturnresponse
Updating an Existing Account
defupdate_account(self,client:'DataverseClient',account_id:'str')->'any_':# Prepare the data to updateupdate_data={'name':'Updated Account Name','telephone1':'+1-555-987-6543','emailaddress1':'hello2@example.com',}# Call the Dataverse API to update the account by IDresponse=client.patch(f'accounts({account_id})',update_data)# Log the response for auditingself.logger.info(f'Dataverse response (update account): {response}')# Return the updated account responsereturnresponse
Deleting an Account
defdelete_account(self,client:'DataverseClient',account_id:'str')->'any_':# Call the Dataverse API to delete the accountresponse=client.delete(f'accounts({account_id})')# Log the response for traceabilityself.logger.info(f'Dataverse response (delete account): {response}')# Return the API responsereturnresponse
API Path vs. PowerApps UI Table Names
A detail to note when working with Dataverse APIs is that the names you see in the PowerApps or Dynamics UI are not always the same as the paths expected by the API. For example:
- In PowerApps, you may see a table called Account.
- In the API, you must use the path accounts (lowercase, plural) when making requests.
This pattern applies to all Dataverse objects: always check the API documentation or inspect the metadata to determine the correct entity path.
Working with Other Dataverse Objects
While the examples above focus on the "accounts" entity, the same approach applies to any object in Dataverse: contacts, leads, opportunities, custom tables, and more. Simply adjust the API path and payload as needed.
Full CRUD Support
With Zato and Python, you get full CRUD (Create, Read, Update, Delete) capability for any Dataverse entity. The methods shown above can be adapted for any object, allowing you to automate, integrate, and orchestrate data flows across your organization.
Summary
This article has shown how to connect to Microsoft Dataverse from Python using Zato, perform CRUD operations, and understand the mapping between UI and API paths. These techniques enable robust integration and automation scenarios with any Dataverse data.
More resources
➤ Microsoft 365 APIs and Python Tutorial
➤ Python API integration tutorials
➤ What is an integration platform?
➤ Python Integration platform as a Service (iPaaS)
➤ What is an Enterprise Service Bus (ESB)? What is SOA?
➤ Open-source iPaaS in Python