Lessons learned from working with Project Server API

Recently I worked on a project involving the Microsoft Project Server 2013. My task was to create a data synchronization using the available Project Server API’s. I never worked with the Project Server before, so this was all new to me. The way to reach the goal was bumpy and sometimes frustrating. But at the end I learned much during this project, so I would like to share it with you.

Project Server API’s

The Project Server is based on SharePoint, so if you worked with SharePoint API’s, it should be familiar to you. The Project Server offers following API’s:

Choosing the API

The data sync project is build up on .Net Core, so I tested first the API’s to see which suits best our needs.

JSOM was out of the run from the beginning. PSI is deprecated, although still available. The available CSOM NuGet Packages for Project Server 2013 are only available for the .Net Framework. And OData is mainly intended for reporting and has various limitations on the number of returning records. So the decision finally came down to the REST API.

The research took me weeks, I wished I knew all this earlier. It would have saved me a lot of time.

Working with the REST API

First I looked for a listing of the REST API endpoints, but there is none. All the endpoints are hidden in the JavaScript library reference, so I had to search the whole reference to collect the endpoints. After that I created a Postman collection, to make it easier to test the API.

The API uses the HATEOAS architecture, which is pretty cool, and make it more self explained. Testing the API I noticed that it returns a lot of data that I don’t need, and the request aren’t the fastest at all. I also thought that I have to use the others endpoints to load additional data. At the end the synchronization would need quite some time. I was not satisfied with that, there had to be another way to make the whole process much performant.

Finally I learned that using the OData query operations I can achieve exactly what I wanted. With one request I could load page based all the data I needed (including related objects and custom fields). Although the requests were still slow, I finally could significant reduce the time needed for the synchronization.

Here an example how I did it:

&$filter=ProjectType eq 0


It was a long, challenging and time consuming journey. But at the end I learned so much. And that’s it what counts at the end.