Deep dive into Power Apps Portal Web API

IMPORTANT
Help UKRAINE ! Your action matters! Donate to support Ukrainian Army! Donate to charity funds! Organize/join street protests in your city to support Ukraine and condemn Russian aggression! Expose and report Russian disinformation! #StandWithUkraine

One of the most requested and long-awaited features for the Portals is finally here - Web API is available in preview. Finally, one of my portals was updated to the necessary version (to have Web API your portal version must be 9.2.6.41 or higher) and I was able to play around. Below I will describe what Web API is and will have a deep dive on how to use it.

You can find full official documentation on Web API here.

Why Portal Web API

Before we started our journey into the depth of the API lets first define what is the main purpose of the Portal Web API. Unlike CDS Web API you cannot use it outside the portal for integration or any other purpose. So why do we need it? Well, the answer is pretty straightforward - enhanced user experience.

Let me explain. Before Web API we need to rely only on provided standard functionality - forms (entity and web) on creating/updating records, entity lists on showing lists of records. And this was limiting especially from a user experience perspective. With the introduction of Portal Web API, we can create complex custom experiences - creative forms, editable lists and much more.

Microsoft opens the door for front end developers by making their Power Apps Portal less low code, which is great.

Web API vs CDS API

First of all, let’s compare Portal Web API to the CDS Web API. As per documentation, they tried to make it as similar as possible to the CDS Web API to make it easier to learn and use it. Web API operations available for the portal:

  • Create (including creating related records in one request)
  • Update
  • Delete
  • Associate
  • Disassociate

Now here you cannot see Retrieve operations which do make sense - fetchxml liquid is present on the portal for a long time and there a plenty of ways use it not only as part of the page during load but to turn it to some sort of web API as well. However, I do hope that this was a prioritization decision and in the future, we will receive proper Retrieve possibilities.

Security

Web API and Site Settings

By default, Web API on the portal is disabled. To enable it you need to configure it support per entity as a site setting.

Webapi/ENTITY NAME/enabled

Furthermore, you need to specify which fields will be available for web API as another site setting.

Webapi/ENTITY NAME/fields

Both these options give you more control over the security of your data (unlike odata entity list option).

Also, you can enable the ability to see inner Web API error with the next setting

Webapi/error/innererror

For example we want to enable Web API for contact entity for first name, last name and email address. Our setting will look like this:

Web APi

Entity permissions and supported entities

Web API respects entity permissions granted to a user via web roles.

Web API will support OOTB entities like contact, account and custom entities. It will not support portal configuration entities (like adx_webpage etc). As in preview, you will be able to change them, but this is NOT SUPPORTED and need to be avoided.

Authentication

Portal will manage authentication and authorization, so no authentication code is required. However, you need to include CSRF token with all web API requests. In short - CSRF token is a specific unique, secret value generated by the server that needs to be included in the request to prevent CSRF (cross-site request forgery) attack. Don’t worry about how to get token - MS included a code snippet that you will be able to copy-paste into the portal that handles it for you - I will review it in the How to use Web API section below. Audit

To see actions performed by the portal user in the Office 365 Audit log events you need to include contact id in the request header.

How to use Web API

Portal web api can be accessed via next url: YOUR_PORTAL_URL/_api.

To perform any action you will need to send a request to correct URL (api + entityset name) with the proper payload. Below you can find a small overview between action and request type:

  • Create - POST
  • Update - PATCH
  • Update single property - PUT
  • Delete - DELETE
  • Associate:
  • Add reference - POST
  • Update reference - PUT
  • Disassociate - DELETE

For example to create contact you will send POST request with json payload to next url YOUR_PORTAL_URL/_api/contacts.

Unlike XRM library inside CDS, we don’t have anything like this OOTB, so you will need to make sure that you are using proper URL and request type. Luckily Aung Khaing in his awesome library XrmPortalJs already added support for Web API to make our lives easier - check it out.

To be fair to Microsoft they provide us with a convenient wrapper that can be found below (for the latest version check documentation).

(function (webapi, $) {
    function safeAjax(ajaxOptions) {
        var deferredAjax = $.Deferred();

        shell.getTokenDeferred().done(function (token) {
            // add headers for AJAX
            if (!ajaxOptions.headers) {
                $.extend(ajaxOptions, {
                    headers: {
                        "__RequestVerificationToken": token
                    }
                });
            } else {
                ajaxOptions.headers["__RequestVerificationToken"] = token;
            }
            $.ajax(ajaxOptions)
                .done(function (data, textStatus, jqXHR) {
                    validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve);
                }).fail(deferredAjax.reject); //AJAX
        }).fail(function () {
            deferredAjax.rejectWith(this, arguments); // on token failure pass the token AJAX and args
        });

        return deferredAjax.promise();
    }
    webapi.safeAjax = safeAjax;
})(window.webapi = window.webapi || {}, jQuery)

Let’s split this code snippet apart to better understand how it works and what’s inside.

To call Web API we will need to call webapi.safeAjax function which accepts one argument ajaxOptions which is a standard object with properties for ajax request.

First thing this function creates deferredAjax object

var deferredAjax = $.Deferred();

By definition from jQuery official docs:

Deferred - a factory function that returns a chainable utility object with methods to register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.

In short, Deferred is an implementation of the Promise from jQuery. It was introduced way before Promise became part of the Javascript. Usually, the deferred object is used when you need to create a function that will return a promise for consumer function to call. That’s why the wrapper function is returning deferredAjax.promise(). For the more detailed introduction to Deferred object check out this article.

Let’s take a look at the next line:

shell.getTokenDeferred().done(function (token) {

Remember earlier I was mentioning some CSRF token that needs to be included with each request? Well, that’s how you get it. MS included a special object called shell with function getTokenDeferred that returns the necessary token.

Next lines are pretty straightforward - we are just adding received token to the request headers.

if (!ajaxOptions.headers) {
    $.extend(ajaxOptions, {
        headers: {
            "__RequestVerificationToken": token
        }
    });
} else {
    ajaxOptions.headers["__RequestVerificationToken"] = token;
}

After that, we have everything that we need to perform our ajax request.

$.ajax(ajaxOptions)
    .done(function (data, textStatus, jqXHR) {
        validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve);
    })
    .fail(deferredAjax.reject); //AJAX

You might notice that in the call we are using validateLoginSession function. This function is also provided to us by MS. As the name suggest it validates if the user is logged in and if his session is still valid. If the user is not logged in it will redirect to the login screen by another build-in function called redirectToLogin.

Example API call

Hopefully, now you understand how MS snippet works. If not - don’t worry as you can just use it as is. Just paste the snippet in your page and use it according to docs.

Let’s say we want to create a contact with name John Smith and email john.smith@dancingwithcrm.com. Also we want to show in console id of newly created entity. Our request will look like this:

webapi.safeAjax({
  type: "POST",
  url: "/_api/contacts",
  contentType: "application/json",
  data: JSON.stringify({
    "firstname": "John",
    "lastname": "Smith",
    "emailaddress1": "john.smith@dancingwithcrm.com"
  }),
  success: function (res, status, xhr) {
    console.log("entityID: " + xhr.getResponseHeader("entityid"))
  }
});
IMPORTANT
The URL for your entity will contain not the entity name ie contact but rather entity set name ie contacts. This might be obvious if you used CDS Web API before, but if not might be a bit confusing.

Conclusion

In this article, I described what is Portal Web API, why we need it and also how to use it. I hope you find this article useful. Keep an eye on my blog as soon I will provide a detailed example on how to use Portal Web API to create Excel-like edit experience for your Portal