or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

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

```