0
# Dynamics365CRM Python
1
2
A Python API wrapper for Microsoft Dynamics 365 CRM API v9.0 that provides OAuth2 authentication and CRUD operations for CRM entities including contacts, accounts, opportunities, leads, and campaigns.
3
4
## Package Information
5
6
- **Package Name**: dynamics365crm-python
7
- **Package Type**: Python API wrapper library
8
- **Language**: Python
9
- **Installation**: `pip install dynamics365crm-python`
10
11
## Core Imports
12
13
```python
14
from dynamics365crm.client import Client
15
```
16
17
## Basic Usage
18
19
```python
20
from dynamics365crm.client import Client
21
22
# Initialize with access token
23
client = Client(
24
domain="https://tenant_name.crmX.dynamics.com",
25
access_token="your_access_token"
26
)
27
28
# Or initialize for OAuth2 flow
29
client = Client(
30
domain="https://tenant_name.crmX.dynamics.com",
31
client_id="your_client_id",
32
client_secret="your_client_secret"
33
)
34
35
# Create a contact
36
contact_id = client.create_contact(
37
firstname="John",
38
lastname="Doe",
39
emailaddress1="john.doe@example.com"
40
)
41
42
# Get all contacts
43
contacts = client.get_contacts()
44
45
# Update a contact
46
client.update_contact(
47
id=contact_id,
48
middlename="Michael"
49
)
50
51
# Delete a contact
52
client.delete_contact(contact_id)
53
```
54
55
## Architecture
56
57
The library is built around a single `Client` class that provides:
58
59
- **Authentication**: OAuth2 flow support using Microsoft Authentication Library (MSAL)
60
- **HTTP Operations**: Generic request methods with OData query support
61
- **Entity Methods**: Specific CRUD operations for Dynamics 365 entities
62
- **Error Handling**: Comprehensive HTTP status code handling
63
64
## Capabilities
65
66
### Client Initialization
67
68
Initialize the Dynamics 365 CRM client with domain and authentication credentials.
69
70
```python { .api }
71
class Client:
72
api_path = "api/data/v9.0"
73
74
def __init__(self, domain: str, client_id: str = None, client_secret: str = None, access_token: str = None):
75
"""
76
Initialize the Dynamics 365 CRM client.
77
78
Class Attributes:
79
- api_path: API path for Dynamics 365 Web API v9.0
80
81
Parameters:
82
- domain: The Dynamics 365 tenant domain URL
83
- client_id: Azure AD application client ID (for OAuth2)
84
- client_secret: Azure AD application client secret (for OAuth2)
85
- access_token: Direct access token for API requests
86
"""
87
```
88
89
### Authentication Management
90
91
Manage OAuth2 authentication flow and access tokens for API requests.
92
93
```python { .api }
94
def set_access_token(self, token: str):
95
"""
96
Sets the access token for API requests.
97
98
Parameters:
99
- token: Access token string
100
101
Raises:
102
- AssertionError: If token is None
103
"""
104
105
def build_authorization_url(self, tenant_id: str, redirect_uri: str, state: str) -> str:
106
"""
107
Generate OAuth2 authorization URL for user consent.
108
109
Parameters:
110
- tenant_id: Azure AD tenant ID or "common"
111
- redirect_uri: Callback URL for authorization response
112
- state: Unique state identifier for security
113
114
Returns:
115
- str: Authorization URL for user redirection
116
"""
117
118
def exchange_code(self, tenant_id: str, redirect_uri: str, code: str) -> dict:
119
"""
120
Exchange authorization code for access token.
121
122
Parameters:
123
- tenant_id: Azure AD tenant ID or "common"
124
- redirect_uri: Must match the redirect_uri used in authorization
125
- code: Authorization code from callback
126
127
Returns:
128
- dict: Token response containing access_token, refresh_token, etc.
129
"""
130
131
def refresh_access_token(self, tenant_id: str, refresh_token: str) -> dict:
132
"""
133
Refresh access token using refresh token.
134
135
Parameters:
136
- tenant_id: Azure AD tenant ID or "common"
137
- refresh_token: Refresh token from previous authentication
138
139
Returns:
140
- dict: New token response or error dict with "error" key
141
"""
142
143
def build_msal_client(self, tenant_id: str):
144
"""
145
Create MSAL ConfidentialClientApplication instance for OAuth2 operations.
146
147
Parameters:
148
- tenant_id: Azure AD tenant ID
149
150
Returns:
151
- msal.ConfidentialClientApplication: MSAL client instance configured for tenant
152
"""
153
```
154
155
### Core HTTP Methods
156
157
Low-level HTTP request methods for direct API interaction.
158
159
```python { .api }
160
def make_request(self, method: str, endpoint: str, expand: str = None, filter: str = None, orderby: str = None, select: str = None, skip: str = None, top: str = None, data = None, json = None, **kwargs):
161
"""
162
Core method for making API requests with OData query parameters.
163
164
Parameters:
165
- method: HTTP method ("get", "post", "patch", "delete")
166
- endpoint: API endpoint path
167
- expand: OData expand parameter for related entities
168
- filter: OData filter parameter for querying
169
- orderby: OData orderby parameter for sorting
170
- select: OData select parameter for field selection
171
- skip: OData skip parameter for pagination
172
- top: OData top parameter for limiting results
173
- data: Raw request data
174
- json: JSON request data
175
- **kwargs: Additional request parameters
176
177
Returns:
178
- dict: Parsed API response
179
180
Raises:
181
- AssertionError: If domain or access_token is None
182
- Exception: For various HTTP error responses
183
"""
184
185
def parse_response(self, response):
186
"""
187
Parse HTTP response and handle error conditions.
188
189
Parameters:
190
- response: requests.Response object
191
192
Returns:
193
- dict: JSON response data
194
- str: Entity GUID for successful create operations
195
- bool: True for successful operations without response data
196
197
Raises:
198
- Exception: For HTTP error status codes (400, 401, 403, 404, 412, 413, 500, 501, 503)
199
"""
200
```
201
202
### Generic Data Operations
203
204
Generic CRUD operations for any Dynamics 365 entity type using OData query parameters.
205
206
```python { .api }
207
def get_data(self, type: str, expand: str = None, filter: str = None, orderby: str = None, select: str = None, skip: str = None, top: str = None, **kwargs) -> dict:
208
"""
209
Generic method to retrieve data for any entity type.
210
211
Parameters:
212
- type: Entity type name (e.g., "contacts", "accounts")
213
- expand: OData expand parameter for related entities
214
- filter: OData filter parameter for querying
215
- orderby: OData orderby parameter for sorting
216
- select: OData select parameter for field selection
217
- skip: OData skip parameter for pagination
218
- top: OData top parameter for limiting results
219
220
Returns:
221
- dict: API response with entity data
222
223
Raises:
224
- Exception: If type is None
225
"""
226
227
def create_data(self, type: str, **kwargs) -> str:
228
"""
229
Generic method to create data for any entity type.
230
231
Parameters:
232
- type: Entity type name
233
- **kwargs: Entity field values
234
235
Returns:
236
- str: Created entity GUID or True if successful
237
238
Raises:
239
- Exception: If type is None
240
"""
241
242
def update_data(self, type: str, id: str, **kwargs) -> bool:
243
"""
244
Generic method to update data for any entity type.
245
246
Parameters:
247
- type: Entity type name
248
- id: Entity GUID
249
- **kwargs: Updated field values
250
251
Returns:
252
- bool: True if successful
253
254
Raises:
255
- Exception: If type or id is None
256
"""
257
258
def delete_data(self, type: str, id: str) -> bool:
259
"""
260
Generic method to delete data for any entity type.
261
262
Parameters:
263
- type: Entity type name
264
- id: Entity GUID
265
266
Returns:
267
- bool: True if successful
268
269
Raises:
270
- Exception: If type or id is None
271
"""
272
```
273
274
### Contact Management
275
276
CRUD operations for contact entities in Dynamics 365.
277
278
```python { .api }
279
def get_contacts(self, expand: str = None, filter: str = None, orderby: str = None, select: str = None, skip: str = None, top: str = None, **kwargs) -> dict:
280
"""
281
Retrieve contacts with optional OData query parameters.
282
283
Parameters:
284
- expand: Related entities to include
285
- filter: Filter criteria
286
- orderby: Sort order
287
- select: Fields to return
288
- skip: Number of records to skip
289
- top: Maximum number of records
290
291
Returns:
292
- dict: API response with contact data
293
"""
294
295
def create_contact(self, **kwargs) -> str:
296
"""
297
Create a new contact.
298
299
Parameters:
300
- **kwargs: Contact field values (firstname, lastname, emailaddress1, etc.)
301
302
Returns:
303
- str: Created contact GUID or True if successful
304
"""
305
306
def update_contact(self, id: str, **kwargs) -> bool:
307
"""
308
Update an existing contact.
309
310
Parameters:
311
- id: Contact GUID
312
- **kwargs: Updated field values
313
314
Returns:
315
- bool: True if successful
316
317
Raises:
318
- Exception: If id is empty
319
"""
320
321
def delete_contact(self, id: str) -> bool:
322
"""
323
Delete a contact by ID.
324
325
Parameters:
326
- id: Contact GUID
327
328
Returns:
329
- bool: True if successful
330
331
Raises:
332
- Exception: If id is empty
333
"""
334
```
335
336
### Account Management
337
338
CRUD operations for account entities in Dynamics 365.
339
340
```python { .api }
341
def get_accounts(self, expand: str = None, filter: str = None, orderby: str = None, select: str = None, skip: str = None, top: str = None, **kwargs) -> dict:
342
"""
343
Retrieve accounts with optional OData query parameters.
344
345
Parameters:
346
- expand: Related entities to include
347
- filter: Filter criteria
348
- orderby: Sort order
349
- select: Fields to return
350
- skip: Number of records to skip
351
- top: Maximum number of records
352
353
Returns:
354
- dict: API response with account data
355
"""
356
357
def create_account(self, **kwargs) -> str:
358
"""
359
Create a new account.
360
361
Parameters:
362
- **kwargs: Account field values (name, websiteurl, etc.)
363
364
Returns:
365
- str: Created account GUID or True if successful
366
"""
367
368
def update_account(self, id: str, **kwargs) -> bool:
369
"""
370
Update an existing account.
371
372
Parameters:
373
- id: Account GUID
374
- **kwargs: Updated field values
375
376
Returns:
377
- bool: True if successful
378
379
Raises:
380
- Exception: If id is empty
381
"""
382
383
def delete_account(self, id: str) -> bool:
384
"""
385
Delete an account by ID.
386
387
Parameters:
388
- id: Account GUID
389
390
Returns:
391
- bool: True if successful
392
393
Raises:
394
- Exception: If id is empty
395
"""
396
```
397
398
### Opportunity Management
399
400
CRUD operations for opportunity entities in Dynamics 365.
401
402
```python { .api }
403
def get_opportunities(self, expand: str = None, filter: str = None, orderby: str = None, select: str = None, skip: str = None, top: str = None, **kwargs) -> dict:
404
"""
405
Retrieve opportunities with optional OData query parameters.
406
407
Parameters:
408
- expand: Related entities to include
409
- filter: Filter criteria
410
- orderby: Sort order
411
- select: Fields to return
412
- skip: Number of records to skip
413
- top: Maximum number of records
414
415
Returns:
416
- dict: API response with opportunity data
417
"""
418
419
def create_opportunity(self, **kwargs) -> str:
420
"""
421
Create a new opportunity.
422
423
Parameters:
424
- **kwargs: Opportunity field values (name, etc.)
425
426
Returns:
427
- str: Created opportunity GUID or True if successful
428
"""
429
430
def update_opportunity(self, id: str, **kwargs) -> bool:
431
"""
432
Update an existing opportunity.
433
434
Parameters:
435
- id: Opportunity GUID
436
- **kwargs: Updated field values
437
438
Returns:
439
- bool: True if successful
440
441
Raises:
442
- Exception: If id is empty
443
"""
444
445
def delete_opportunity(self, id: str) -> bool:
446
"""
447
Delete an opportunity by ID.
448
449
Parameters:
450
- id: Opportunity GUID
451
452
Returns:
453
- bool: True if successful
454
455
Raises:
456
- Exception: If id is empty
457
"""
458
```
459
460
### Lead Management
461
462
CRUD operations for lead entities in Dynamics 365.
463
464
```python { .api }
465
def get_leads(self, expand: str = None, filter: str = None, orderby: str = None, select: str = None, skip: str = None, top: str = None, **kwargs) -> dict:
466
"""
467
Retrieve leads with optional OData query parameters.
468
469
Parameters:
470
- expand: Related entities to include
471
- filter: Filter criteria
472
- orderby: Sort order
473
- select: Fields to return
474
- skip: Number of records to skip
475
- top: Maximum number of records
476
477
Returns:
478
- dict: API response with lead data
479
"""
480
481
def create_lead(self, **kwargs) -> str:
482
"""
483
Create a new lead.
484
485
Parameters:
486
- **kwargs: Lead field values (fullname, subject, mobilephone, websiteurl, etc.)
487
488
Returns:
489
- str: Created lead GUID or True if successful
490
"""
491
492
def update_lead(self, id: str, **kwargs) -> bool:
493
"""
494
Update an existing lead.
495
496
Parameters:
497
- id: Lead GUID
498
- **kwargs: Updated field values
499
500
Returns:
501
- bool: True if successful
502
503
Raises:
504
- Exception: If id is empty
505
"""
506
507
def delete_lead(self, id: str) -> bool:
508
"""
509
Delete a lead by ID.
510
511
Parameters:
512
- id: Lead GUID
513
514
Returns:
515
- bool: True if successful
516
517
Raises:
518
- Exception: If id is empty
519
"""
520
```
521
522
### Campaign Management
523
524
CRUD operations for campaign entities in Dynamics 365.
525
526
```python { .api }
527
def get_campaigns(self, expand: str = None, filter: str = None, orderby: str = None, select: str = None, skip: str = None, top: str = None, **kwargs) -> dict:
528
"""
529
Retrieve campaigns with optional OData query parameters.
530
531
Parameters:
532
- expand: Related entities to include
533
- filter: Filter criteria
534
- orderby: Sort order
535
- select: Fields to return
536
- skip: Number of records to skip
537
- top: Maximum number of records
538
539
Returns:
540
- dict: API response with campaign data
541
"""
542
543
def create_campaign(self, **kwargs) -> str:
544
"""
545
Create a new campaign.
546
547
Parameters:
548
- **kwargs: Campaign field values (name, description, etc.)
549
550
Returns:
551
- str: Created campaign GUID or True if successful
552
"""
553
554
def update_campaign(self, id: str, **kwargs) -> bool:
555
"""
556
Update an existing campaign.
557
558
Parameters:
559
- id: Campaign GUID
560
- **kwargs: Updated field values
561
562
Returns:
563
- bool: True if successful
564
565
Raises:
566
- Exception: If id is empty
567
"""
568
569
def delete_campaign(self, id: str) -> bool:
570
"""
571
Delete a campaign by ID.
572
573
Parameters:
574
- id: Campaign GUID
575
576
Returns:
577
- bool: True if successful
578
579
Raises:
580
- Exception: If id is empty
581
"""
582
```
583
584
## Error Handling
585
586
The client automatically handles various HTTP error responses:
587
588
- **400 Bad Request**: Invalid request body or parameters
589
- **401 Unauthorized**: Invalid or expired credentials
590
- **403 Forbidden**: Insufficient permissions
591
- **404 Not Found**: Resource not found
592
- **412 Precondition Failed**: Conditional request failed
593
- **413 Request Entity Too Large**: Request payload too large
594
- **500 Internal Server Error**: Server-side error
595
- **501 Not Implemented**: Requested operation not supported
596
- **503 Service Unavailable**: Service temporarily unavailable
597
598
All error conditions raise `Exception` with descriptive messages including the URL, status code, and raw error response.
599
600
## Usage Examples
601
602
### OAuth2 Authentication Flow
603
604
```python
605
from dynamics365crm.client import Client
606
607
# Initialize client for OAuth2
608
client = Client(
609
"https://tenant_name.crmX.dynamics.com",
610
client_id="your_client_id",
611
client_secret="your_client_secret"
612
)
613
614
# Step 1: Get authorization URL
615
auth_url = client.build_authorization_url(
616
tenant_id="your_tenant_id",
617
redirect_uri="https://yourapp.com/callback",
618
state="unique_state_string"
619
)
620
print(f"Visit: {auth_url}")
621
622
# Step 2: Exchange code for token (after user consent)
623
token_response = client.exchange_code(
624
tenant_id="your_tenant_id",
625
redirect_uri="https://yourapp.com/callback",
626
code="authorization_code_from_callback"
627
)
628
629
# Step 3: Set access token
630
client.set_access_token(token_response['access_token'])
631
632
# Step 4: Use the client for API calls
633
contacts = client.get_contacts()
634
```
635
636
### Working with Contacts
637
638
```python
639
# Create a contact with multiple fields
640
contact_id = client.create_contact(
641
firstname="Jane",
642
lastname="Smith",
643
middlename="Elizabeth",
644
emailaddress1="jane.smith@company.com",
645
mobilephone="555-0123",
646
jobtitle="Sales Manager"
647
)
648
649
# Get contacts with filtering and selection
650
filtered_contacts = client.get_contacts(
651
filter="contains(lastname,'Smith')",
652
select="fullname,emailaddress1,jobtitle",
653
orderby="lastname",
654
top="10"
655
)
656
657
# Update contact information
658
client.update_contact(
659
id=contact_id,
660
jobtitle="Senior Sales Manager",
661
emailaddress1="jane.smith@newcompany.com"
662
)
663
```
664
665
### Working with Related Entities
666
667
```python
668
# Get accounts with expanded contact information
669
accounts_with_contacts = client.get_accounts(
670
expand="primarycontactid($select=fullname,emailaddress1)",
671
select="name,websiteurl,telephone1"
672
)
673
674
# Create related entities
675
account_id = client.create_account(
676
name="Tech Solutions Inc",
677
websiteurl="https://techsolutions.com",
678
telephone1="555-0100"
679
)
680
681
opportunity_id = client.create_opportunity(
682
name="Software Implementation",
683
description="CRM software implementation project",
684
estimatedvalue=50000
685
)
686
```