Exchange Server on premises REST API
Overview
Installation
overview
Postman
Postman is a collaboration platform for API development
With Postman you can send requests and view responses easily.
Prerequisites
- Exchange Server 2016 or later (Exchange Server 2016 must be upgraded to CU3 or later)
- Allow access to the REST API /api virtual directory and to the /autodiscover/autodiscover.json virtual directory file
IIS - Internet Information Services
To access the REST API, basic authentication must be enabled on the /api virtual directory in IIS.
Powershell examples
Some examples on how to consume the REST API using powershell.
Get mails from the authenticated user
$mails = Invoke-RestMethod -Uri "https://mail.domain.ch/api/v2.0/me/messages" -Credential (Get-Credential)
get email recipients
$mails.value.ToRecipients.EmailAddress
get mail received date time
$mails.value.ReceivedDateTime | get-date
get mail sender and subject only
$mails = Invoke-RestMethod -Uri "https://mail.domain.ch/api/v2.0/me/MailFolders/sentitems/messages/?`$select=Sender,Subject" -Credential (Get-Credential)
send email
$mail = '{
"Message": {
"Subject": "first mail sent over REST API",
"Body": {
"ContentType": "Text",
"Content": "This is the content"
},
"ToRecipients": [
{
"EmailAddress": {
"Address": "john@domain.ch"
}
}
]
},
"SaveToSentItems": "true"
}'
invoke-RestMethod -Uri "https://mail.domain.ch/api/v2.0/me/sendmail"
-Method Post -Body $mail -ContentType "application/json" -Credential (get-credential)
Appointments
Get appointments from the authenticated user
$events = Invoke-RestMethod -Uri "https://mail.domain.ch/api/v2.0/me/events" -Credential (Get-Credential)
get event subject
$events.value.Subject
get event created date times
$events.value.CreatedDateTime | get-date
get event subject only
$events = Invoke-RestMethod -Uri "https://mail.domain.ch/api/v2.0/me/events?$select=Subject" -Credential (Get-Credential)
C# Application
overview
Json.Net
Information about serialization and deserialization
Creating a c# Windows Application
For sending HTTP request and receiving HTTP response we are using the class HttpClient.
baseAddress
Gets or Sets the base address of URI of the Internet Resource used when sending requests.
DefaultRequestHeaders
Sets the headers which should be sent with each request.
ApiHelper Class
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace rest_api
{
public abstract class ApiHelper
public static HttpClient ApiClient { get; set; }
private static string Username { get; set; }
private static string Password { get; set; }
private static ApiVersion { get; set; }
public static void InitializeClient()
{
ApiClient = new HttpClient();
/*----- set credentials -----*/
string username = "jane";
string password = "youshallnotpass";
string apiVersion = "v2.0";
var auth = Encoding.ASCII.GetBytes($"{username}:{password});
/*----- set base endpoint url -----*/
string baseUrl = $"https://mail.domain.ch//api//{apiVersion}//me//";
ApiClient.baseAddress = new Uri(baseUrl);
/*----- set default request headers -----*/
ApiClient.DefaultRequestHeaders.Accept.Clear();
ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
ApiClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(auth));
/// <summary>
/// Send a PATCH request to the specified Uri as an asynchronous operation.
/// </summary>
/// <param name="requestUri">The Uri the request is sent to</param>
/// <param name="content">The HTTP request content sent to the server.</param>
/// <returns>The Task object representing the asynchronous operation</returns>
public static Task<HttpResponseMessage> PatchAsync( string requestUri, HttpContent content)
{
return ApiHelper.PatchAsync(CreateUri(requestUri), content);
}
/// <summary>
/// Send a PATCH request to the specified Uri as an asynchronous operation.
/// </summary>
/// <param name="requestUri">The Uri the request is sent to</param>
/// <param name="content">The HTTP request content sent to the server.</param>
/// <returns>The Task object representing the asynchronous operation</returns>
public static Task<HttpResponseMessage> PatchAsync(Uri requestUri, HttpContent content)
{
return ApiHelper.PatchAsync(requestUri, content, CancellationToken.None);
}
/// <summary>
/// Send a PATCH request with a cancellation token as an asynchronous operation.
/// </summary>
/// <param name="requestUri">The Uri the request is sent to</param>
/// <param name="content">The HTTP request content sent to the server</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns> The task object representing the asynchronous operation</returns>
public static Task<HttpResponseMessage> PatchAsync( string requestUri, HttpContent content, CancellationToken cancellationToken)
{
return ApiHelper.PatchAsync(CreateUri(requestUri), content, cancellationToken);
}
/// <summary>
/// Send a PATCH request with a cancellation token as an asynchronous operation.
/// </summary>
/// <param name="requestUri">The Uri the request is sent to</param>
/// <param name="content">The HTTP request content sent to the server</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns> The task object representing the asynchronous operation</returns>
public static Task<HttpResponseMessage> PatchAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
{
return ApiClient.SendAsync(new HttpRequestMessage(new HttpMethod("PATCH"), requestUri)
{
Content = content
}, cancellationToken);
}
/// <summary>
/// format uri from string
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
private static Uri CreateUri(string uri)
{
return string.IsNullOrEmpty(uri) ? null : new Uri(uri, UriKind.RelativeOrAbsolute);
}
}
}
Appointment Container Class
using Newtonsoft.Json;
using System.Collections.Generic;
namespace rest_api.Classes.Appointment
{
class AppointmentContainer
{
[JsonProperty("@odata.context")]
public string odata_context { get; set; }
[JsonProperty("value")]
public List<Appointment> Appointments { get; set; }
public void ResetAppointmentList()
{
Appointments = new List<Appointment>();
}
}
}
Appointment Class
using System;
using Newtonsoft.Json;
namespace rest_api.Classes.Appointment
{
class Appointment
{
[JsonProperty("@odata.id")]
public string OdataId { get; set; }
[JsonProperty("@odata.etag")]
public string OdataEtag { get; set; }
[JsonProperty("Id")]
public string Id { get; set; }
[JsonProperty("CreatedDateTime")]
public DateTime CreatedDateTime { get; set; }
[JsonProperty("LastModifiedDateTime")]
public DateTime LastModifiedDateTime { get; set; }
[JsonProperty("ChangeKey")]
public string ChangeKey { get; set; }
[JsonProperty("ReminderMinutesBeforeStart")]
public int ReminderMinutesBeforeStart { get; set; }
[JsonProperty("IsReminderOn")]
public bool? IsReminderOn { get; set; }
[JsonProperty("HasAttachments")]
public bool? HasAttachments { get; set; }
[JsonProperty("Subject")]
public string Subject { get; set; }
[JsonProperty("BodyPreview")]
public string BodyPreview { get; set; }
[JsonProperty("Importance")]
public string Importance { get; set; }
[JsonProperty("Sensitivity")]
public string Sensitivity { get; set; }
[JsonProperty("IsAllDay")]
public bool? IsAllDay { get; set; }
[JsonProperty("IsCancelled")]
public bool? IsCancelled { get; set; }
[JsonProperty("IsOrganizer")]
public bool? IsOrganizer { get; set; }
[JsonProperty("ResponseRequested")]
public bool? ResponseRequested { get; set; }
[JsonProperty("ShowAs")]
public string ShowAs { get; set; }
[JsonProperty("Body")]
public AppointmentBody Body { get; set; }
[JsonProperty("Start")]
public AppointmentStart Start { get; set; }
[JsonProperty("End")]
public AppointmentEnd End { get; set; }
}
public class AppointmentEnd
{
[JsonProperty("DateTime")]
public DateTime DateTime { get; set; }
[JsonProperty("TimeZone")]
public string TimeZone { get; set; }
}
public class AppointmentStart
{
[JsonProperty("DateTime")]
public DateTime DateTime { get; set; }
[JsonProperty("TimeZone")]
public string TimeZone { get; set; }
}
class AppointmentBody
{
[JsonProperty("ContentType")]
public string ContentType { get; set; }
[JsonProperty("Content")]
public string Content { get; set; }
}
}
RestAppointment Class
using System;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Text;
using System.Net.Http;
using System.Threading.Tasks;
using rest_api.Classes.Appointment;
namespace rest_api
{
class RestAppointment
{
public AppointmentContainer AppointmentContainer { get; set; }
public RestAppointment()
{
AppointmentContainer = new AppointmentContainer();
}
/// <summary>
/// Get one or more appointments with the provided url
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task GetAppointment(string url = "events")
{
/*----- send a GET request with the provided url -----*/
using (HttpResponseMessage response = await ApiHelper.ApiClient.GetAsync(url))
{
/*----- if the Response code is in the successful range (200-299) -----*/
if (response.IsSuccessStatusCode)
{
try
{
/*----- Serialize the HTTP content to a string -----*/
string result = await response.Content.ReadAsStringAsync();
/*----- Deserialize the json string to AppointmentContainer -----*/
AppointmentContainer = JsonConvert.DeserializeObject<AppointmentContainer>(result);
/*----- verify if appointments is null - appointments is null if there's just one appointment -----*/
if (AppointmentContainer.Appointments == null)
{
/*----- reset appointment list and add the appointment to it-----*/
AppointmentContainer.ResetAppointmentList();
AppointmentContainer.Appointments.Add(JsonConvert.DeserializeObject<Appointment>(result));
}
/*----- Debug: print for each appointment some properties -----*/
foreach (Appointment appointment in AppointmentContainer.Appointments)
{
Debug.WriteLine("Appointment subject: " + appointment.Subject);
Debug.WriteLine("Appointment IsAllDay: " + appointment.IsAllDay);
Debug.WriteLine("Appointment CreatedDateTime: " + appointment.CreatedDateTime);
Debug.WriteLine("Appointment Start DateTime: " + appointment.Start.DateTime);
Debug.WriteLine("Appointment LastModifiedDateTime: " + appointment.LastModifiedDateTime);
Debug.WriteLine("Appointment ReminderMinutesBeforeStart: " + appointment.ReminderMinutesBeforeStart);
}
}
catch (Exception err)
{
/*----- TODO: add exception handling -----*/
Debug.WriteLine("Error in GetAppointment: " + err);
}
}
else
{
/*-----TODO: add error handling - this block will execute if IsSuccessStatusCode is false -----*/
Debug.WriteLine("Failed: " + response);
}
}
}
/// <summary>
/// Create an appointment
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task CreateAppointment(Appointment appointment, string url = "events")
{
/*----- verify if appointments is null - appointments is null if there's just one appointment -----*/
if (AppointmentContainer.Appointments == null)
{
/*----- reset appointment list and add the appointment to it-----*/
AppointmentContainer.ResetAppointmentList();
AppointmentContainer.Appointments.Add(appointment);
}
/*----- Serialize appointment object to json and ignore null properties -----*/
var content = new StringContent(JsonConvert.SerializeObject(AppointmentContainer.Appointments[0], Formatting.Indented,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
}),
Encoding.UTF8, "application/json");
/*----- send a POST request with the provided url -----*/
var response = await ApiHelper.ApiClient.PostAsync(url, content);
var responseString = await response.Content.ReadAsStringAsync();
Debug.WriteLine("response string: " + responseString);
}
/// <summary>
/// Update an appointment with the provided id and optionally a specified url
/// </summary>
/// <param name="id"></param>
/// <param name="url"></param>
/// <returns></returns>
public async Task UpdateAppointment(string id, Appointment appointment, string url = "events/")
{
/*----- format the url -----*/
if (!url.EndsWith("/"))
url += "/";
url += id;
Debug.WriteLine("url: " + url);
/*----- verify if appointments is null - appointments is null if there's just one appointment -----*/
if (AppointmentContainer.Appointments == null)
{
/*----- reset appointment list and add the appointment to it-----*/
AppointmentContainer.ResetAppointmentList();
AppointmentContainer.Appointments.Add(appointment);
}
/*----- Serialize appointment object to json and ignore null properties -----*/
var content = new StringContent(JsonConvert.SerializeObject(AppointmentContainer.Appointments[0], Formatting.Indented,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
}),
Encoding.UTF8, "application/json");
/*----- send a PATCH request with the provided url -----*/
var response = await ApiHelper.PatchAsync(url, content);
var responseString = await response.Content.ReadAsStringAsync();
Debug.WriteLine("response string: " + responseString);
}
/// <summary>
/// delete an appointment with the provided id and optionally a specified url
/// </summary>
/// <param name="id"></param>
/// <param name="url"></param>
/// <returns></returns>
public async Task DeleteAppointment(string id, string url = "events")
{
/*----- format the url -----*/
url += "/" + id;
/*----- send a DELETE request with the provided url -----*/
await ApiHelper.ApiClient.DeleteAsync(url);
/*----- TODO: add exception handling -----*/
}
public async Task SyncAppointment(string id, string url = "events")
{
/*----- format the url -----*/
url += "/calendars" + "/" + id;
/*----- send a GET request with the provided url -----*/
using (HttpResponseMessage response = await ApiHelper.ApiClient.GetAsync(url))
{
/*----- if the Response code is in the successful range (200-299) -----*/
if (response.IsSuccessStatusCode)
{
try
{
/*----- Serialize the HTTP content to a string -----*/
string result = await response.Content.ReadAsStringAsync();
/*----- Deserialize the json string to AppointmentContainer -----*/
AppointmentContainer = JsonConvert.DeserializeObject<AppointmentContainer>(result);
/*----- verify if appointments is null - appointments is null if there's just one appointment -----*/
if (AppointmentContainer.Appointments == null)
{
/*----- reset appointment list and add the appointment to it-----*/
AppointmentContainer.ResetAppointmentList();
AppointmentContainer.Appointments.Add(JsonConvert.DeserializeObject<Appointment>(result));
}
/*----- Debug: print for each appointment some properties -----*/
foreach (Appointment appointment in AppointmentContainer.Appointments)
{
Debug.WriteLine("Appointment subject: " + appointment.Subject);
Debug.WriteLine("Appointment IsAllDay: " + appointment.IsAllDay);
Debug.WriteLine("Appointment CreatedDateTime: " + appointment.CreatedDateTime);
Debug.WriteLine("Appointment Start DateTime: " + appointment.Start.DateTime);
Debug.WriteLine("Appointment LastModifiedDateTime: " + appointment.LastModifiedDateTime);
Debug.WriteLine("Appointment ReminderMinutesBeforeStart: " + appointment.ReminderMinutesBeforeStart);
}
}
catch (Exception err)
{
/*----- TODO: add exception handling -----*/
Debug.WriteLine("Error: " + err);
throw;
}
}
else
{
/*-----TODO: add error handling - this block will execute if IsSuccessStatusCode is false -----*/
Debug.WriteLine("Failed: " + response);
}
}
}
}
}
FAQ
This error occurs when something went wrong with a Datetime value.
Possibility 1: Error with contacts REST API
You're trying to create a contact with a birthday in the future.
Possibility 1: Error with END attribute
The END attribute is required. Exchange doesn't support creating events without an END date.
Great info. Lucky me I discovered your blog by accident (stumbleupon). I’ve bookmarked it for later!