WizardM365API.Client 15.3.0

dotnet add package WizardM365API.Client --version 15.3.0
                    
NuGet\Install-Package WizardM365API.Client -Version 15.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="WizardM365API.Client" Version="15.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WizardM365API.Client" Version="15.3.0" />
                    
Directory.Packages.props
<PackageReference Include="WizardM365API.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add WizardM365API.Client --version 15.3.0
                    
#r "nuget: WizardM365API.Client, 15.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package WizardM365API.Client@15.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=WizardM365API.Client&version=15.3.0
                    
Install as a Cake Addin
#tool nuget:?package=WizardM365API.Client&version=15.3.0
                    
Install as a Cake Tool

WizardM365API.Client

A comprehensive .NET client SDK for interacting with the Wizard M365 API. This package provides a type-safe, easy-to-use interface for all Microsoft 365 services, Azure DevOps, and security operations.

.NET License

Table of Contents

Features

Microsoft 365 Services

  • 🔷 Teams: Post, reply, edit, and retrieve Teams channel messages, get user activity, presence, and team memberships
  • 📝 OneNote: Create and manage notebooks, sections, and pages with full CRUD operations
  • 📁 SharePoint: Create folders, get site information, and manage SharePoint objects
  • 💬 Chat: Harvest chat messages and retrieve chat information across conversations
  • 📞 Communications: Access call records, online meetings, and meeting transcripts
  • 👤 Users: Manage user events, calendar operations, directory audits, and activity logs

Security & Compliance

  • 🛡️ Defender: Retrieve machines, alerts, vulnerabilities, software, recommendations, and security insights
  • 📋 Audits: Start and manage audit jobs, retrieve audit logs and compliance reports

Development & Operations

  • 🔧 Azure DevOps: Access pull requests, work items, commits, and comprehensive developer analytics
  • 🔐 Applications: Create and manage Azure AD app registrations programmatically

Core Features

  • ✅ Type-Safe API: Strongly-typed request and response models with IntelliSense support
  • 🔄 Automatic Retries: Built-in retry logic for transient failures
  • 🔒 OAuth2 Authentication: Automatic token management with client credentials flow
  • 💾 Persistence Layer: Optional automatic database persistence for API operations
  • 📊 Logging: Comprehensive structured logging for debugging and monitoring
  • ⚡ Async/Await: Fully asynchronous API with cancellation token support
  • 🏥 Health Checks: Monitor API health and status

Installation

dotnet add package WizardM365API.Client

Quick Start

1. Prerequisites

  • .NET 6.0 or higher
  • Azure AD tenant with appropriate permissions
  • Wizard M365 API subscription key

2. Configuration

Add the M365 API settings to your appsettings.json:

{
  "M365Api": {
    "BaseUrl": "https://your-m365-api.com",
    "SubscriptionKey": "your-subscription-key",
    "SystemId": "your-system-id",
    "DefaultUserEmail": "user@example.com",
    "TimeoutSeconds": 30,
    "RetryAttempts": 3,
    "ThrowOnApiError": false,
    "OAuth2": {
      "TenantId": "your-azure-ad-tenant-id",
      "ClientId": "your-azure-ad-application-client-id",
      "ClientSecret": "your-azure-ad-application-client-secret",
      "Scope": "https://graph.microsoft.com/.default",
      "TokenCacheDurationMinutes": 55
    },
    "Persistence": {
      "Enabled": false,
      "FailSilently": true,
      "PersistTeamsOperations": false,
      "PersistOneNoteOperations": false,
      "PersistSharePointOperations": false,
      "PersistChatOperations": false,
      "PersistCommunicationsOperations": false,
      "TimeoutSeconds": 30
    }
  }
}

⚠️ Security Note: Never commit sensitive credentials to source control. Use environment variables, Azure Key Vault, or user secrets for production deployments.

3. Dependency Injection Setup

Register the M365 API client in your Program.cs or Startup.cs:

using WizardM365API.Client.Extensions;

// Basic setup - reads configuration from appsettings.json
builder.Services.AddM365ApiClient(builder.Configuration);

// OR configure manually with code
builder.Services.AddM365ApiClient(options =>
{
    options.BaseUrl = "https://your-m365-api.com";
    options.SubscriptionKey = "your-subscription-key";
    options.SystemId = "your-system-id";
    options.DefaultUserEmail = "user@example.com";
    options.TimeoutSeconds = 60;
    options.RetryAttempts = 3;
});

// For applications requiring persistence (see Persistence Feature section)
builder.Services.AddM365ApiClientWithPersistence(builder.Configuration);

4. Using the Client

using WizardM365API.Client;
using WizardM365API.Client.Models.Teams;

public class TeamsService
{
    private readonly IM365ApiClient _m365Client;

    public TeamsService(IM365ApiClient m365Client)
    {
        _m365Client = m365Client;
    }

    public async Task PostMessageAsync()
    {
        var request = new PostChannelMessageRequest
        {
            TeamId = "team-id",
            ChannelId = "channel-id",
            Subject = "Hello from Client SDK",
            Message = "This message was sent using the M365 API Client SDK!",
            Importance = "Important", // Supported: Standard, Important
            UserEmail = "user@example.com"
        };

        var response = await _m365Client.Teams.PostChannelMessageAsync(request);
        
        if (response.IsSuccess)
        {
            Console.WriteLine($"Message posted successfully: {response.Data?.Id}");
        }
        else
        {
            Console.WriteLine($"Error: {response.ErrorMessage}");
        }
    }
}

Configuration

Configuration Options

The M365 API Client supports comprehensive configuration through the M365ApiClientOptions class:

Option Type Required Default Description
BaseUrl string Yes - Base URL of the Wizard M365 API endpoint
SubscriptionKey string Yes - Your API subscription key for authentication
SystemId string Yes - Unique identifier for your system/application
DefaultUserEmail string No null Default user email for operations requiring user context
TimeoutSeconds int No 30 HTTP request timeout in seconds
RetryAttempts int No 3 Number of retry attempts for failed requests
ThrowOnApiError bool No false Whether to throw exceptions on API errors

OAuth2 Configuration

Configure Azure AD authentication for Microsoft Graph API operations:

Option Type Required Default Description
OAuth2.TenantId string Yes - Your Azure AD tenant ID
OAuth2.ClientId string Yes - Azure AD application (client) ID
OAuth2.ClientSecret string Yes - Azure AD application client secret
OAuth2.Scope string No "" OAuth2 scope (e.g., "https://graph.microsoft.com/.default")
OAuth2.TokenCacheDurationMinutes int No 55 Duration to cache access tokens (in minutes)

Persistence Configuration

Enable automatic database persistence for API operations (optional):

Option Type Default Description
Persistence.Enabled bool false Master switch for persistence functionality
Persistence.FailSilently bool true Continue operation if persistence fails
Persistence.PersistTeamsOperations bool false Enable persistence for Teams operations
Persistence.PersistOneNoteOperations bool false Enable persistence for OneNote operations
Persistence.PersistSharePointOperations bool false Enable persistence for SharePoint operations
Persistence.PersistChatOperations bool false Enable persistence for Chat operations
Persistence.PersistCommunicationsOperations bool false Enable persistence for Communications operations
Persistence.TimeoutSeconds int 30 Timeout for persistence operations

Environment-Specific Configuration

For different environments (Development, Staging, Production), use environment-specific configuration files:

appsettings.Development.json

{
  "M365Api": {
    "BaseUrl": "https://dev-api.example.com",
    "SubscriptionKey": "dev-subscription-key",
    "SystemId": "dev-system",
    "TimeoutSeconds": 60,
    "ThrowOnApiError": true
  }
}

appsettings.Production.json

{
  "M365Api": {
    "BaseUrl": "https://api.example.com",
    "SubscriptionKey": "#{ProductionSubscriptionKey}#",
    "SystemId": "production-system",
    "TimeoutSeconds": 30,
    "RetryAttempts": 5,
    "ThrowOnApiError": false
  }
}

Using Azure Key Vault

For secure credential storage in production:

// Program.cs
builder.Configuration.AddAzureKeyVault(
    new Uri($"https://{keyVaultName}.vault.azure.net/"),
    new DefaultAzureCredential());

builder.Services.AddM365ApiClient(options =>
{
    options.BaseUrl = builder.Configuration["M365Api:BaseUrl"];
    options.SubscriptionKey = builder.Configuration["M365Api:SubscriptionKey"];
    options.OAuth2.ClientSecret = builder.Configuration["M365Api:OAuth2:ClientSecret"];
});

API Reference

Teams Client

// Importance values for Teams channel messages/replies/edits:
// "Standard" (default) or "Important"

// Post a message to a Teams channel
var postRequest = new PostChannelMessageRequest
{
    TeamId = "team-id",
    ChannelId = "channel-id",
    Subject = "Message Subject",
    Message = "Message content",
    Importance = "Important",
    EmailsToMention = new List<string> { "user@example.com" }
};
var response = await client.Teams.PostChannelMessageAsync(postRequest);

// Get a channel message using the generic M365 object endpoint
var getMessageRequest = new GetM365ObjectRequest
{
    ObjectTypeId = 1,           // Required: Object type for channel message
    ObjectId = "1259337",       // Required: Your object ID
    UserEmail = "mcjoseph.agbanlog@wizard-ai.com" // Required: User email
};
var channelMessageResponse = await client.Teams.GetChannelMessageObjectAsync(getMessageRequest);

// Reply to a message
var replyRequest = new ReplyChannelMessageRequest
{
    TeamId = "team-id",
    ChannelId = "channel-id",
    MessageId = "message-id",
    Message = "Reply content",
    Importance = "Important"
};
var replyResponse = await client.Teams.ReplyChannelMessageAsync(replyRequest);

// Edit a message
var editRequest = new EditChannelMessageRequest
{
    TeamId = "team-id",
    ChannelId = "channel-id",
    MessageId = "message-id",
    Message = "Updated content",
    Importance = "Important"
};
var editResponse = await client.Teams.EditChannelMessageAsync(editRequest);

// Get messages
var getRequest = new GetChannelMessagesRequest
{
    TeamId = "team-id",
    ChannelId = "channel-id",
    Top = 50
};
var messagesResponse = await client.Teams.GetChannelMessagesAsync(getRequest);

OneNote Client

// Create a notebook
var notebookRequest = new CreateNotebookRequest
{
    DisplayName = "My Notebook",
    SiteId = "optional-site-id"
};
var notebookResponse = await client.OneNote.CreateNotebookAsync(notebookRequest);

// Create a section
var sectionRequest = new CreateSectionRequest
{
    DisplayName = "My Section",
    NotebookId = "notebook-id"
};
var sectionResponse = await client.OneNote.CreateSectionAsync(sectionRequest);

// Create a page
var pageRequest = new CreatePageRequest
{
    Title = "My Page",
    Content = "<html><body><h1>Hello World</h1></body></html>",
    SectionId = "section-id"
};
var pageResponse = await client.OneNote.CreatePageAsync(pageRequest);

SharePoint Client

// Get site ID
var siteIdRequest = new GetSiteIdRequest
{
    HostName = "contoso.sharepoint.com",
    SiteRelativePath = "sites/teamsite"
};
var siteIdResponse = await client.SharePoint.GetSiteIdAsync(siteIdRequest);

// Create a folder
var folderRequest = new CreateFolderRequest
{
    SiteId = "site-id",
    FolderName = "New Folder",
    ParentPath = "/Shared Documents"
};
var folderResponse = await client.SharePoint.CreateFolderAsync(folderRequest);

Chat Client

Retrieve and harvest chat messages from Microsoft Teams chats.

// Harvest chat messages
var harvestRequest = new HarvestMessagesRequest
{
    UserId = "user-id",
    StartDateTime = DateTime.UtcNow.AddDays(-7),
    EndDateTime = DateTime.UtcNow,
    Top = 100,
    UserEmail = "user@example.com"
};
var harvestResponse = await client.Chat.HarvestMessagesAsync(harvestRequest);

if (harvestResponse.IsSuccess)
{
    Console.WriteLine($"Harvested {harvestResponse.Data?.Count} chat messages");
}

Communications Client

Access call records, online meetings, and meeting transcripts from Microsoft Teams.

// Get call records for a user
var callRecordsRequest = new GetCallRecordsRequest
{
    UserId = "user-id-or-upn",
    StartDateTime = DateTime.UtcNow.AddDays(-30),
    EndDateTime = DateTime.UtcNow,
    UserEmail = "user@example.com"
};
var callRecordsResponse = await client.Communications.GetCallRecordsAsync(callRecordsRequest);

if (callRecordsResponse.IsSuccess)
{
    foreach (var callRecord in callRecordsResponse.Data)
    {
        Console.WriteLine($"Call: {callRecord.Id} - Duration: {callRecord.Duration}");
    }
}

// Get a specific call record
var callRecordRequest = new GetCallRecordRequest
{
    ObjectId = "call-record-id",
    ObjectTypeId = 123, // Object type ID for call records
    UserEmail = "user@example.com"
};
var callRecordResponse = await client.Communications.GetCallRecordAsync(callRecordRequest);

if (callRecordResponse.IsSuccess)
{
    var callRecord = callRecordResponse.Data;
    Console.WriteLine($"Call started: {callRecord.StartDateTime}");
    Console.WriteLine($"Participants: {callRecord.Sessions?.Count}");
}

Available Operations:

  • GetCallRecordsAsync() - Retrieve call records for a user within a date range
  • GetCallRecordAsync() - Get detailed information about a specific call record

Users Client

Manage user operations including calendar events, online meetings, directory audits, and activity logs.

// Create a calendar event for a user
var createEventRequest = new CreateUserEventRequest
{
    UserIdOrUpn = "user@example.com",
    Subject = "Team Meeting",
    Start = new DateTimeTimeZone
    {
        DateTime = DateTime.UtcNow.AddDays(1).ToString("yyyy-MM-ddTHH:mm:ss"),
        TimeZone = "UTC"
    },
    End = new DateTimeTimeZone
    {
        DateTime = DateTime.UtcNow.AddDays(1).AddHours(1).ToString("yyyy-MM-ddTHH:mm:ss"),
        TimeZone = "UTC"
    },
    UserEmail = "user@example.com"
};
var eventResponse = await client.Users.CreateUserEventAsync(createEventRequest);

// Get user calendar events
var eventsRequest = new GetUserEventsRequest
{
    UserIdOrUpn = "user@example.com",
    StartDateTime = DateTime.UtcNow,
    EndDateTime = DateTime.UtcNow.AddDays(7),
    UserEmail = "user@example.com"
};
var eventsResponse = await client.Users.GetUserEventsAsync(eventsRequest);

// Get online meeting by join URL
var meetingRequest = new GetOnlineMeetingByJoinUrlRequest
{
    JoinWebUrl = "https://teams.microsoft.com/l/meetup-join/...",
    UserEmail = "user@example.com"
};
var meetingResponse = await client.Users.GetOnlineMeetingByJoinUrlAsync("user-id", meetingRequest);

// Get meeting transcripts
var transcriptsRequest = new GetMeetingTranscriptsRequest
{
    UserEmail = "user@example.com"
};
var transcriptsResponse = await client.Users.GetMeetingTranscriptsAsync("user-id", "meeting-id", transcriptsRequest);

// Get transcript content
var transcriptContentRequest = new GetTranscriptContentRequest
{
    UserEmail = "user@example.com"
};
var contentResponse = await client.Users.GetTranscriptContentAsync("user-id", "meeting-id", "transcript-id", transcriptContentRequest);

// Get user information
var userRequest = new GetUserRequest
{
    UserEmail = "user@example.com"
};
var userResponse = await client.Users.GetUserAsync("user-id-or-upn", userRequest);

// Get directory audit logs
var auditRequest = new GetDirectoryAuditsRequest
{
    StartDateTime = DateTime.UtcNow.AddDays(-30),
    EndDateTime = DateTime.UtcNow,
    UserEmail = "user@example.com"
};
var auditsResponse = await client.Users.GetDirectoryAuditsAsync("user-id", auditRequest);

// Get user activity logs
var activityRequest = new GetUserActivityLogsRequest
{
    StartDateTime = DateTime.UtcNow.AddDays(-7),
    EndDateTime = DateTime.UtcNow,
    UserEmail = "user@example.com"
};
var activityResponse = await client.Users.GetUserActivityLogsAsync("user-id", activityRequest);

Available Operations:

  • CreateUserEventAsync() - Create a calendar event for a user
  • GetUserEventsAsync() - Retrieve user calendar events within a date range
  • GetOnlineMeetingByJoinUrlAsync() - Get online meeting details by join URL
  • GetMeetingTranscriptsAsync() - Get transcripts for an online meeting
  • GetTranscriptContentAsync() - Get the content of a specific transcript
  • GetUserAsync() - Get user profile information
  • GetDirectoryAuditsAsync() - Get directory audit logs for users
  • GetUserActivityLogsAsync() - Get user activity logs (document access, OneNote, Teams, etc.)

Applications Client

Create and manage Azure AD app registrations programmatically.

// Create an app registration
var createAppRequest = new CreateAppRegistrationRequest
{
    TenantId = "tenant-id",
    ClientId = "your-admin-client-id",
    ClientSecret = "your-admin-client-secret",
    DisplayName = "My Application",
    SignInAudience = "AzureADMyOrg",
    RedirectUris = new List<string> { "https://myapp.com/callback" },
    RequiredResourceAccess = new List<ApiPermissionRequest>
    {
        new ApiPermissionRequest
        {
            ResourceAppId = "00000003-0000-0000-c000-000000000000", // Microsoft Graph
            ResourceAccess = new List<ResourceAccessRequest>
            {
                new ResourceAccessRequest
                {
                    Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d", // User.Read
                    Type = "Scope"
                }
            }
        }
    },
    UserEmail = "admin@example.com"
};
var createResponse = await client.Applications.CreateAppRegistrationAsync(createAppRequest);

if (createResponse.IsSuccess)
{
    Console.WriteLine($"App created: {createResponse.Data.DisplayName}");
    Console.WriteLine($"App ID: {createResponse.Data.AppId}");
    Console.WriteLine($"Client Secret: {createResponse.Data.ClientSecret}");
    Console.WriteLine($"Secret expires: {createResponse.Data.ClientSecretExpirationDate}");
}

// Get app registrations
var getAppsRequest = new GetAppRegistrationsRequest
{
    TenantId = "tenant-id",
    ClientId = "your-admin-client-id",
    ClientSecret = "your-admin-client-secret",
    Top = 50,
    Skip = 0,
    UserEmail = "admin@example.com"
};
var appsResponse = await client.Applications.GetAppRegistrationsAsync(getAppsRequest);

if (appsResponse.IsSuccess)
{
    Console.WriteLine($"Total apps: {appsResponse.Data.TotalCount}");
    foreach (var app in appsResponse.Data.AppRegistrations)
    {
        Console.WriteLine($"App: {app.DisplayName} ({app.AppId})");
    }
}

Available Operations:

  • CreateAppRegistrationAsync() - Create a new Azure AD app registration
  • GetAppRegistrationsAsync() - Retrieve app registrations from a tenant

Response Models:

  • CreateAppRegistrationResponse - Contains app ID, client secret, permissions, and configuration details
  • GetAppRegistrationsResponse - Contains list of app registrations with pagination info

Azure DevOps Client

Access Azure DevOps data including pull requests, work items, commits, and developer analytics.

// Get pull requests
var prRequest = new GetPullRequestsRequest
{
    Organization = "your-organization",
    Project = "your-project",
    Repository = "your-repository",
    PersonalAccessToken = "your-pat",
    StartDate = DateTime.UtcNow.AddDays(-30),
    EndDate = DateTime.UtcNow,
    Status = "completed",
    UserEmail = "user@example.com"
};
var prResponse = await client.AzureDevOps.GetPullRequestsAsync(prRequest);

if (prResponse.IsSuccess)
{
    Console.WriteLine($"Found {prResponse.Data.Count} pull requests");
    foreach (var pr in prResponse.Data.PullRequests)
    {
        Console.WriteLine($"PR #{pr.PullRequestId}: {pr.Title} - {pr.Status}");
    }
}

// Get work items
var workItemsRequest = new GetWorkItemsRequest
{
    Organization = "your-organization",
    Project = "your-project",
    PersonalAccessToken = "your-pat",
    WorkItemIds = new List<int> { 123, 456, 789 },
    UserEmail = "user@example.com"
};
var workItemsResponse = await client.AzureDevOps.GetWorkItemsAsync(workItemsRequest);

// Get commits
var commitsRequest = new GetCommitsRequest
{
    Organization = "your-organization",
    Project = "your-project",
    Repository = "your-repository",
    PersonalAccessToken = "your-pat",
    FromDate = DateTime.UtcNow.AddMonths(-1),
    ToDate = DateTime.UtcNow,
    UserEmail = "user@example.com"
};
var commitsResponse = await client.AzureDevOps.GetCommitsAsync(commitsRequest);

// Get comprehensive developer analytics
var analyticsRequest = new DeveloperAnalyticsRequest
{
    Organization = "your-organization",
    Project = "your-project",
    Repository = "your-repository",
    PersonalAccessToken = "your-pat",
    SearchCriteria = new DeveloperSearchCriteria
    {
        DeveloperEmail = "developer@example.com",
        StartDate = DateTime.UtcNow.AddMonths(-3),
        EndDate = DateTime.UtcNow
    },
    UserEmail = "admin@example.com"
};
var analyticsResponse = await client.AzureDevOps.GetDeveloperAnalyticsAsync(analyticsRequest);

if (analyticsResponse.IsSuccess)
{
    var analytics = analyticsResponse.Data;
    Console.WriteLine($"Developer: {analytics.Developer}");
    Console.WriteLine($"Total PRs: {analytics.TotalPullRequests}");
    Console.WriteLine($"Total Commits: {analytics.TotalCommits}");
    Console.WriteLine($"Total Work Items: {analytics.TotalWorkItems}");
    Console.WriteLine($"Lines Added: {analytics.TotalLinesAdded}");
    Console.WriteLine($"Lines Deleted: {analytics.TotalLinesDeleted}");
}

Available Operations:

  • GetPullRequestsAsync() - Get pull requests with developer analytics
  • GetWorkItemsAsync() - Get work items for a project
  • GetCommitsAsync() - Get commits for a repository
  • GetDeveloperAnalyticsAsync() - Get comprehensive developer analytics (PRs, commits, work items)

Response Models:

  • GetPullRequestsResponse - Pull requests with metrics (lines changed, review comments, etc.)
  • GetWorkItemsResponse - Work items with full details and history
  • GetCommitsResponse - Commits with file changes and statistics
  • DeveloperAnalyticsResponse - Aggregated developer productivity metrics

Defender Client

// Get machines
var machinesRequest = new GetMachinesRequest
{
    TenantId = "your-tenant-id",
    ClientId = "your-client-id", 
    ClientSecret = "your-client-secret",
    UserEmail = "user@example.com",
    Top = 50,
    HealthStatus = "Active",
    OsPlatform = "Windows10"
};
var machinesResponse = await client.Defender.GetMachinesAsync(machinesRequest);

if (machinesResponse.IsSuccess)
{
    // machinesResponse.Data is List<DefenderMachine>
    foreach (var machine in machinesResponse.Data)
    {
        Console.WriteLine($"Machine: {machine.ComputerDnsName} - Status: {machine.HealthStatus}");
    }
}

// Get machine alerts
var alertsRequest = new GetMachineAlertsRequest
{
    TenantId = "your-tenant-id",
    ClientId = "your-client-id",
    ClientSecret = "your-client-secret", 
    MachineId = "machine-id",
    UserEmail = "user@example.com"
};
var alertsResponse = await client.Defender.GetMachineAlertsAsync(alertsRequest);

if (alertsResponse.IsSuccess)
{
    // alertsResponse.Data is List<DefenderMachineAlert>
    foreach (var alert in alertsResponse.Data)
    {
        Console.WriteLine($"Alert: {alert.Title} - Severity: {alert.Severity}");
    }
}

// Get machine vulnerabilities
var vulnerabilitiesRequest = new GetMachineVulnerabilitiesRequest
{
    TenantId = "your-tenant-id",
    ClientId = "your-client-id",
    ClientSecret = "your-client-secret",
    MachineId = "machine-id", 
    UserEmail = "user@example.com"
};
var vulnerabilitiesResponse = await client.Defender.GetMachineVulnerabilitiesAsync(vulnerabilitiesRequest);

// Get machine software
var softwareRequest = new GetMachineSoftwareRequest
{
    TenantId = "your-tenant-id",
    ClientId = "your-client-id",
    ClientSecret = "your-client-secret",
    MachineId = "machine-id",
    UserEmail = "user@example.com"
};
var softwareResponse = await client.Defender.GetMachineSoftwareAsync(softwareRequest);

// Get machine logon users  
var logsRequest = new GetMachineLogsRequest
{
    TenantId = "your-tenant-id",
    ClientId = "your-client-id", 
    ClientSecret = "your-client-secret",
    MachineId = "machine-id",
    UserEmail = "user@example.com"
};
var logsResponse = await client.Defender.GetMachineLogsAsync(logsRequest);

// Get security recommendations
var recommendationsRequest = new GetRecommendationsRequest
{
    TenantId = "your-tenant-id",
    ClientId = "your-client-id",
    ClientSecret = "your-client-secret", 
    UserEmail = "user@example.com",
    Top = 100,
    RecommendationCategory = "Security"
};
var recommendationsResponse = await client.Defender.GetRecommendationsAsync(recommendationsRequest);

if (recommendationsResponse.IsSuccess)
{
    // recommendationsResponse.Data is List<DefenderRecommendation>
    foreach (var recommendation in recommendationsResponse.Data)
    {
        Console.WriteLine($"Recommendation: {recommendation.RecommendationName} - Severity: {recommendation.SeverityScore}");
    }
}

Available Operations:

  • GetMachinesAsync() - Get machines with filtering options (health status, OS platform, etc.)
  • GetMachineAlertsAsync() - Get alerts for a specific machine
  • GetMachineLogsAsync() - Get logon users for a machine
  • GetMachineVulnerabilitiesAsync() - Get vulnerabilities for a machine
  • GetMachineSoftwareAsync() - Get software installed on a machine
  • GetRecommendationsAsync() - Get security recommendations

Response Models:

  • List<DefenderMachine> - Machine information with health status, OS details, risk score
  • List<DefenderMachineAlert> - Security alerts with severity, status, and remediation info
  • List<DefenderMachineLog> - Logon user information
  • List<DefenderMachineVulnerability> - CVE information and severity scores
  • List<DefenderMachineSoftware> - Installed software inventory
  • List<DefenderRecommendation> - Security recommendations with exposure scores

🔒 Security Note: The Defender API endpoints use POST requests (not GET) to prevent credential exposure in URLs and server logs. Client secrets are transmitted securely in the request body over HTTPS. See CHANGELOG-DEFENDER-SECURITY.md for more details.

Audits Client

Start and manage audit jobs for Office 365 compliance and security auditing.

// Start an audit job
var startJobRequest = new StartAuditJobRequest
{
    StartDateTime = DateTime.UtcNow.AddDays(-30),
    EndDateTime = DateTime.UtcNow,
    RecordType = "AzureActiveDirectory", // Optional: filter by record type
    Operations = new List<string> { "UserLoggedIn", "FileAccessed" }, // Optional: filter by operations
    UserEmail = "admin@example.com"
};
var jobResponse = await client.Audits.StartAuditJobAsync(startJobRequest);

if (jobResponse.IsSuccess)
{
    string queryId = jobResponse.Data.QueryId;
    Console.WriteLine($"Audit job started: {queryId}");
    
    // Poll for job status
    bool isComplete = false;
    while (!isComplete)
    {
        await Task.Delay(5000); // Wait 5 seconds between polls
        
        var statusRequest = new GetAuditJobStatusRequest
        {
            QueryId = queryId,
            UserEmail = "admin@example.com"
        };
        var statusResponse = await client.Audits.GetAuditJobStatusAsync(statusRequest);
        
        if (statusResponse.IsSuccess)
        {
            Console.WriteLine($"Job status: {statusResponse.Data.Status}");
            isComplete = statusResponse.Data.Status == "Succeeded";
            
            if (isComplete)
            {
                // Get audit results
                var resultsRequest = new GetAuditJobResultsRequest
                {
                    QueryId = queryId,
                    PageSize = 1000,
                    PageToken = null, // For pagination
                    UserEmail = "admin@example.com"
                };
                var resultsResponse = await client.Audits.GetAuditJobResultsAsync(resultsRequest);
                
                if (resultsResponse.IsSuccess)
                {
                    Console.WriteLine($"Retrieved {resultsResponse.Data.Records.Count} audit records");
                    
                    foreach (var record in resultsResponse.Data.Records)
                    {
                        Console.WriteLine($"Operation: {record.Operation}");
                        Console.WriteLine($"User: {record.UserId}");
                        Console.WriteLine($"Time: {record.CreationTime}");
                        Console.WriteLine($"Workload: {record.Workload}");
                        Console.WriteLine("---");
                    }
                    
                    // Handle pagination
                    if (!string.IsNullOrEmpty(resultsResponse.Data.NextPageToken))
                    {
                        resultsRequest.PageToken = resultsResponse.Data.NextPageToken;
                        // Fetch next page...
                    }
                }
            }
        }
    }
}

Available Operations:

  • StartAuditJobAsync() - Start an audit job for a specified date range
  • GetAuditJobStatusAsync() - Check the status of an audit job
  • GetAuditJobResultsAsync() - Retrieve audit records from a completed job

Response Models:

  • StartAuditJobResponse - Contains query ID for tracking the audit job
  • GetAuditJobStatusResponse - Job status (Pending, Processing, Succeeded, Failed)
  • GetAuditJobResultsResponse - Audit records with pagination support

Audit Record Types:

  • Exchange (email operations)
  • SharePoint (document operations)
  • OneDrive (file operations)
  • AzureActiveDirectory (sign-ins, user management)
  • Teams (meetings, messages, calls)
  • and more...

Health Client

Monitor the health and status of the Wizard M365 API.

// Basic health check
var healthResponse = await client.Health.GetHealthAsync();

if (healthResponse.IsSuccess)
{
    Console.WriteLine($"API Status: {healthResponse.Data.Status}");
    Console.WriteLine($"Version: {healthResponse.Data.Version}");
}

// Detailed health check (includes component status)
var detailedHealthResponse = await client.Health.GetDetailedHealthAsync();

if (detailedHealthResponse.IsSuccess)
{
    Console.WriteLine($"Overall Status: {detailedHealthResponse.Data.Status}");
    
    foreach (var component in detailedHealthResponse.Data.Components)
    {
        Console.WriteLine($"  {component.Name}: {component.Status}");
    }
}

Available Operations:

  • GetHealthAsync() - Get basic API health status
  • GetDetailedHealthAsync() - Get detailed health status including component health

Error Handling

Response Structure

All API operations return an ApiResponse<T> object with the following structure:

public class ApiResponse<T>
{
    public bool IsSuccess { get; set; }              // True if operation succeeded
    public T? Data { get; set; }                     // Response data (if successful)
    public string? ErrorType { get; set; }           // Error category
    public int? ErrorCode { get; set; }              // Error code
    public string? ErrorMessage { get; set; }        // Human-readable error message
    public int StatusCode { get; set; }              // HTTP status code
}

Basic Error Handling

Always check IsSuccess before accessing Data:

var response = await client.Teams.PostChannelMessageAsync(request);

if (response.IsSuccess)
{
    // Success - safely access response.Data
    var message = response.Data;
    Console.WriteLine($"Message posted: {message.Id}");
}
else
{
    // Error - handle appropriately
    Console.WriteLine($"Error {response.StatusCode}: {response.ErrorMessage}");
    Console.WriteLine($"Error Type: {response.ErrorType}");
}

Advanced Error Handling

Handle different error types with pattern matching:

var response = await client.Defender.GetMachinesAsync(request);

if (!response.IsSuccess)
{
    switch (response.ErrorType)
    {
        case "ValidationError":
            // Invalid request parameters
            Console.WriteLine($"Validation failed: {response.ErrorMessage}");
            break;
            
        case "AuthenticationError":
            // Authentication/authorization failed
            Console.WriteLine($"Auth failed: {response.ErrorMessage}");
            // Perhaps refresh tokens or re-authenticate
            break;
            
        case "RateLimitError":
            // Too many requests
            Console.WriteLine($"Rate limited. Retry after: {response.ErrorMessage}");
            // Implement exponential backoff
            break;
            
        case "NotFoundError":
            // Resource not found
            Console.WriteLine($"Resource not found: {response.ErrorMessage}");
            break;
            
        case "ServerError":
            // API server error
            Console.WriteLine($"Server error: {response.ErrorMessage}");
            // Log and alert for investigation
            break;
            
        default:
            Console.WriteLine($"Unexpected error: {response.ErrorMessage}");
            break;
    }
}

Exception Handling

By default, the client does not throw exceptions for API errors. Enable exceptions if preferred:

builder.Services.AddM365ApiClient(options =>
{
    options.ThrowOnApiError = true; // Throw exceptions on API errors
});

// Now you can use try-catch
try
{
    var response = await client.Teams.PostChannelMessageAsync(request);
    var message = response.Data;
}
catch (M365ApiException ex)
{
    Console.WriteLine($"API Error: {ex.Message}");
    Console.WriteLine($"Status Code: {ex.StatusCode}");
}

Retry Logic

The client includes built-in retry logic for transient failures:

builder.Services.AddM365ApiClient(options =>
{
    options.RetryAttempts = 5;      // Retry up to 5 times
    options.TimeoutSeconds = 60;    // 60 second timeout per request
});

Retry logic applies to:

  • Network timeouts
  • HTTP 429 (Too Many Requests)
  • HTTP 500-599 (Server errors)

Logging

The client uses structured logging for debugging and monitoring:

// Configure logging in Program.cs
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Information);

// The client will log:
// - Request attempts
// - Retry attempts
// - Errors and exceptions
// - Performance metrics

Example log output:

[Information] Attempting to post message to Teams channel. TeamId: abc123, ChannelId: xyz789
[Information] Successfully posted message to Teams channel. MessageId: msg456
[Error] Failed to post message. Error: ValidationError - TeamId is required

Persistence Feature

The M365 API Client now supports automatic persistence of API operation results to your database. This feature allows you to automatically store information about Teams messages, OneNote pages, SharePoint folders, and other M365 objects in your database entities after successful API operations.

Enabling Persistence

1. Basic Setup with Configuration
{
  "M365Api": {
    "BaseUrl": "https://your-m365-api.com",
    "ApiKey": "your-api-key",
    "SystemId": "your-system-id",
    "Persistence": {
      "Enabled": true,
      "FailSilently": true,
      "PersistTeamsOperations": false,
      "PersistOneNoteOperations": false,
      "PersistSharePointOperations": false,
      "PersistChatOperations": false,
      "TimeoutSeconds": 10,
      "SetAuditFields": true
    }
  }
}
// Enable persistence using configuration
builder.Services.AddM365ApiClientWithPersistence(builder.Configuration);

// Or configure persistence manually
builder.Services.AddM365ApiClientWithPersistence(options =>
{
    options.BaseUrl = "https://your-m365-api.com";
    options.ApiKey = "your-api-key";
    options.SystemId = "your-system-id";
    options.Persistence.Enabled = true;
    options.Persistence.PersistTeamsOperations = true; // Enable Teams persistence if needed
});
2. Entity Setup

Your entities must implement the required interfaces:

using WizardM365API.Client.Persistence.Entities;

public class MsTeamsTeam : IMsTeamsTeam
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string? Description { get; set; }
    public string? Link { get; set; }
    public string ExternalId { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
    public string? UpdatedBy { get; set; }

    // Your additional properties
    public bool IsActive { get; set; } = true;
    public virtual ICollection<MsTeamsChannel> Channels { get; set; } = new List<MsTeamsChannel>();
}

public class MsTeamsChannel : IMsTeamsChannel
{
    public Guid Id { get; set; }
    public Guid TeamId { get; set; }
    public string Name { get; set; } = string.Empty;
    public string? Type { get; set; }
    public string? Link { get; set; }
    public string ExternalId { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
    public string? UpdatedBy { get; set; }

    // Your additional properties and navigation properties
    public virtual MsTeamsTeam Team { get; set; } = null!;
    public virtual ICollection<MsTeamsConversation> Conversations { get; set; } = new List<MsTeamsConversation>();
}

public class MsTeamsConversation : IMsTeamsConversation
{
    public Guid Id { get; set; }
    public Guid ChannelId { get; set; }
    public string? Link { get; set; }
    public string ExternalId { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; }
    public string? CreatedBy { get; set; }
    public DateTime? UpdatedAt { get; set; }
    public string? UpdatedBy { get; set; }
    public bool IsQueued { get; set; }
    public bool Processed { get; set; }
    public int? ObjectId { get; set; }
    public int? ObjectTypeId { get; set; }

    // Your additional properties
    public virtual MsTeamsChannel Channel { get; set; } = null!;
}
3. DbContext Setup

Ensure your DbContext includes DbSets for entities that implement the required interfaces:

public class MyDbContext : DbContext
{
    public DbSet<MsTeamsTeam> MsTeamsTeams { get; set; }
    public DbSet<MsTeamsChannel> MsTeamsChannels { get; set; }
    public DbSet<MsTeamsConversation> MsTeamsConversations { get; set; }
    public DbSet<MsOneNoteNotebook> MsOneNoteNotebooks { get; set; }
    public DbSet<MsOneNoteSection> MsOneNoteSections { get; set; }
    public DbSet<MsOneNotePage> MsOneNotePages { get; set; }
    // ... other DbSets
}

Using Persistence-Enabled Methods

Use the new overloads that accept a DbContext parameter:

public class TeamsService
{
    private readonly IM365ApiClient _m365Client;
    private readonly MyDbContext _dbContext;

    public TeamsService(IM365ApiClient m365Client, MyDbContext dbContext)
    {
        _m365Client = m365Client;
        _dbContext = dbContext;
    }

    public async Task PostMessageWithPersistenceAsync()
    {
        var request = new PostChannelMessageRequest
        {
            TeamId = "team-id",
            ChannelId = "channel-id",
            Subject = "Hello from Client SDK",
            Message = "This message will be automatically saved to database!",
            Importance = "Important",
            UserEmail = "user@example.com"
        };

        // This will post the message AND automatically save it to your database
        var response = await _m365Client.Teams.PostChannelMessageAsync(request, _dbContext);
        
        if (response.IsSuccess)
        {
            // Message posted and persisted successfully
            Console.WriteLine($"Message posted and saved: {response.Data?.Id}");
        }
    }

    public async Task GetChannelWithPersistenceAsync()
    {
        var request = new GetM365ObjectRequest
        {
            ObjectId = "channel-id",
            UserEmail = "user@example.com"
        };

        // This will get the channel AND automatically save it to your database
        var response = await _m365Client.Teams.GetChannelAsync(request, _dbContext);
        
        if (response.IsSuccess)
        {
            // Channel retrieved and persisted successfully
            Console.WriteLine($"Channel retrieved and saved: {response.Data?.DisplayName}");
        }
    }
}

Available Persistence Interfaces

  • Teams: IMsTeamsTeam, IMsTeamsChannel, IMsTeamsConversation
  • OneNote: IMsOneNoteNotebook, IMsOneNoteSection, IMsOneNotePage
  • SharePoint: IMsSharePointSite, IMsSharePointList, IMsSharePointFolder, IMsSharePointFile
  • Chat: IMsChatConversation, IMsChatMessage

Configuration Options

Option Description Default
Enabled Whether persistence is enabled false
FailSilently Whether to fail silently if persistence fails true
PersistTeamsOperations Whether to persist Teams operations false
PersistOneNoteOperations Whether to persist OneNote operations false
PersistSharePointOperations Whether to persist SharePoint operations false
PersistChatOperations Whether to persist Chat operations false
TimeoutSeconds Timeout for persistence operations 10
SetAuditFields Whether to automatically set audit fields true

How It Works

  1. Automatic Detection: The persistence service uses reflection to find entity types in your DbContext that implement the required interfaces
  2. Mapping: API responses are automatically mapped to your entity properties
  3. Upsert Logic: Existing entities are updated, new ones are created
  4. Error Handling: Persistence failures don't break API operations (configurable)
  5. Audit Fields: Automatically sets CreatedAt, UpdatedAt, UpdatedBy fields
  6. Object IDs: Uses ObjectId and ObjectTypeId from the request DTOs

Benefits

  • Zero Code Changes: Existing API calls work unchanged
  • Opt-in Persistence: Use persistence-enabled methods only when needed
  • Flexible Entity Design: Your entities can have additional properties beyond the required interface
  • Robust Error Handling: Persistence failures don't break your application flow
  • Automatic Relationships: Handles parent-child relationships (Team → Channel → Conversation)

Best Practices

1. Security

Never Hardcode Credentials
// ❌ BAD - Credentials in code
options.SubscriptionKey = "abc123-secret-key";
options.OAuth2.ClientSecret = "my-client-secret";

// ✅ GOOD - Use configuration
options.SubscriptionKey = configuration["M365Api:SubscriptionKey"];
options.OAuth2.ClientSecret = configuration["M365Api:OAuth2:ClientSecret"];
Use Azure Key Vault for Production
builder.Configuration.AddAzureKeyVault(
    new Uri($"https://{keyVaultName}.vault.azure.net/"),
    new DefaultAzureCredential());
Secure Credential Storage
  • Use User Secrets for local development
  • Use Azure Key Vault for production
  • Use Environment Variables for CI/CD pipelines
  • Never commit .config or appsettings.json with secrets to source control

2. Performance

Use Cancellation Tokens
// Allow operations to be cancelled
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var response = await client.Teams.GetChannelMessagesAsync(request, cts.Token);
Implement Pagination
// For large datasets, use pagination
var request = new GetChannelMessagesRequest
{
    TeamId = "team-id",
    ChannelId = "channel-id",
    Top = 50  // Fetch in batches
};
Configure Appropriate Timeouts
builder.Services.AddM365ApiClient(options =>
{
    options.TimeoutSeconds = 60;  // Increase for operations that may take longer
    options.RetryAttempts = 3;    // Balance between resilience and latency
});

3. Error Handling

Always Check IsSuccess
// ❌ BAD - Accessing Data without checking
var message = response.Data.Id;  // NullReferenceException if failed!

// ✅ GOOD - Check first
if (response.IsSuccess && response.Data != null)
{
    var message = response.Data.Id;
}
Log Errors for Debugging
if (!response.IsSuccess)
{
    _logger.LogError(
        "Failed to post Teams message. Error: {ErrorType} - {ErrorMessage}. RequestId: {RequestId}",
        response.ErrorType, response.ErrorMessage, request.RequestId);
}
Implement Retry with Exponential Backoff for Rate Limits
async Task<ApiResponse<T>> RetryWithBackoff<T>(Func<Task<ApiResponse<T>>> operation, int maxAttempts = 3)
{
    for (int attempt = 1; attempt <= maxAttempts; attempt++)
    {
        var response = await operation();
        
        if (response.IsSuccess || response.ErrorType != "RateLimitError")
            return response;
            
        if (attempt < maxAttempts)
        {
            var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt)); // Exponential backoff
            await Task.Delay(delay);
        }
    }
    
    return await operation(); // Final attempt
}

4. Resource Management

Use Dependency Injection
// ✅ GOOD - Let DI manage lifecycle
public class MyService
{
    private readonly IM365ApiClient _client;
    
    public MyService(IM365ApiClient client)
    {
        _client = client;
    }
}
Avoid Creating Multiple Client Instances
// ❌ BAD - Creating clients manually
var client1 = new M365ApiClient(...);
var client2 = new M365ApiClient(...);

// ✅ GOOD - Use singleton from DI
builder.Services.AddM365ApiClient(builder.Configuration);

5. Testing

Use Interfaces for Testability
// Service depends on interface, making it easy to mock
public class TeamsService
{
    private readonly IM365ApiClient _client;
    
    public TeamsService(IM365ApiClient client)
    {
        _client = client;
    }
}

// In tests, mock the interface
var mockClient = new Mock<IM365ApiClient>();
mockClient.Setup(x => x.Teams.PostChannelMessageAsync(It.IsAny<PostChannelMessageRequest>(), default))
    .ReturnsAsync(ApiResponse<ChatMessage>.Success(new ChatMessage()));
Integration Testing
// Use a test configuration with a sandbox environment
public class IntegrationTestsFixture : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly IM365ApiClient _client;
    
    public IntegrationTestsFixture(WebApplicationFactory<Program> factory)
    {
        var services = factory.Services;
        _client = services.GetRequiredService<IM365ApiClient>();
    }
}

6. Monitoring

Health Check Integration
// Add health checks for M365 API
builder.Services.AddHealthChecks()
    .AddCheck("m365-api", async () =>
    {
        var client = serviceProvider.GetRequiredService<IM365ApiClient>();
        var response = await client.Health.GetHealthAsync();
        
        return response.IsSuccess 
            ? HealthCheckResult.Healthy("M365 API is healthy")
            : HealthCheckResult.Unhealthy($"M365 API is down: {response.ErrorMessage}");
    });
Application Insights Integration
builder.Services.AddApplicationInsightsTelemetry();

// The client's built-in logging will automatically flow to App Insights

Troubleshooting

Common Issues

1. Authentication Errors

Problem: AuthenticationError: Unauthorized (401)

Solutions:

  • Verify your SubscriptionKey is correct and active
  • Check OAuth2 credentials (TenantId, ClientId, ClientSecret)
  • Ensure the Azure AD app has the required permissions
  • Verify tokens haven't expired
  • Check if Azure AD app permissions have been admin-consented
// Test authentication
var response = await client.Health.GetHealthAsync();
if (!response.IsSuccess && response.ErrorType == "AuthenticationError")
{
    // Check your credentials
}
2. Rate Limiting

Problem: RateLimitError: Too Many Requests (429)

Solutions:

  • Implement exponential backoff retry logic
  • Reduce request frequency
  • Use pagination to fetch data in smaller batches
  • Cache responses when possible
  • Consider upgrading your API subscription tier
// Check rate limit headers in responses
if (response.ErrorType == "RateLimitError")
{
    // Wait and retry
    await Task.Delay(TimeSpan.FromSeconds(60));
}
3. Timeout Errors

Problem: TimeoutException: Request timed out

Solutions:

  • Increase timeout configuration
  • Use pagination for large datasets
  • Check network connectivity
  • Verify API endpoint health
builder.Services.AddM365ApiClient(options =>
{
    options.TimeoutSeconds = 120; // Increase timeout
});
4. Validation Errors

Problem: ValidationError: Required field missing

Solutions:

  • Check all required fields are populated
  • Verify data types and formats
  • Review API documentation for parameter requirements
  • Enable detailed logging to see request details
// Ensure all required fields are set
var request = new PostChannelMessageRequest
{
    TeamId = "...",      // Required
    ChannelId = "...",   // Required
    Message = "...",     // Required
    UserEmail = "..."    // Required
};
5. Persistence Errors

Problem: Persistence fails but API operation succeeds

Solutions:

  • Check FailSilently setting in configuration
  • Verify DbContext is registered correctly
  • Ensure entity interfaces are implemented
  • Check database connection string
  • Review persistence timeout settings
// Enable persistence error logging
builder.Services.AddM365ApiClientWithPersistence(options =>
{
    options.Persistence.Enabled = true;
    options.Persistence.FailSilently = false; // Throw exceptions for debugging
});
6. SSL/TLS Errors

Problem: SSL connection could not be established

Solutions:

  • Ensure your application can access HTTPS endpoints
  • Check firewall and proxy settings
  • Verify SSL certificates are trusted
  • Update root certificates on the server

Debug Mode

Enable detailed logging for troubleshooting:

builder.Logging.AddConsole();
builder.Logging.AddDebug();
builder.Logging.SetMinimumLevel(LogLevel.Debug);

// Or for even more detail
builder.Logging.SetMinimumLevel(LogLevel.Trace);

Getting Help

If you encounter issues not covered here:

  1. Check the logs - Enable detailed logging to see what's happening
  2. Review API documentation - Ensure you're using the API correctly
  3. Check API health - Use the Health Client to verify API status
  4. Contact support - Reach out with:
    • Error messages and stack traces
    • Request/response details (sanitized)
    • Configuration (without credentials)
    • Steps to reproduce

License

MIT License

Support

For issues and questions:

  • Documentation: Refer to the main Wizard M365 API documentation
  • Issues: Create an issue in the repository
  • Security: Report security issues privately to the security team

Made with ❤️ by the Wizard AI Team

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
15.3.0 97 3/3/2026
15.2.3 580 10/21/2025
15.2.2 199 10/21/2025
15.2.1 203 10/21/2025
15.2.0 202 10/21/2025
15.1.1 208 10/16/2025
15.1.0 274 10/9/2025
15.0.0 214 10/8/2025
14.1.0 197 10/7/2025
14.0.0 206 10/7/2025
13.3.0 247 9/24/2025
13.2.0 199 9/24/2025
13.1.0 197 9/24/2025
13.0.0 209 9/24/2025
12.3.0 334 9/18/2025
12.2.0 348 9/17/2025
12.1.0 348 9/17/2025
12.0.0 330 9/17/2025
11.1.0 343 9/16/2025
11.0.0 326 9/16/2025
Loading failed