Tutorial: Use portal Web API


Effective October 12, 2022, Power Apps portals is Power Pages. More information: Microsoft Power Pages is now generally available (blog)
We will soon migrate and merge the Power Apps portals documentation with Power Pages documentation.

In this tutorial, you'll set up a webpage and custom web template that will use the Web API to read, write, update, and delete records from the contact table.


You can change the column names, or use a different table, while following the steps in this example.

Step 1. Create site settings

Before you can use the portals Web API, you have to enable the required site settings with the Portal Management app. The site settings depend on the table that you want to use when interacting with the Web API.

  1. Go to Power Apps.

  2. On the left pane, select Apps.

  3. Select the Portal Management app.

    Launch Portal Management app.

  4. On the left pane of the Portal Management app, select Site Settings.

    Open site settings in Portal Management app.

  5. Select New.

  6. In the Name box, enter Webapi/contact/enabled.

  7. In the Website list, select your website record.

  8. In the Value box, enter true.

    Enable contact table for WebAPI site setting.

  9. Select Save & Close.

  10. Select New.

  11. In the Name box, enter Webapi/contact/fields.

  12. In the Website list, select your website record.

  13. In the Value box, enter

    Enable Web API contact table fields site setting.

  14. Select Save & Close.

  15. Select New.

  16. In the Name box, enter Webapi/error/innererror.

    Enable Web API inner error site setting.

  17. In the Website list, select your website record.

  18. In the Value box, enter true.

  19. Select Save & Close.

  20. Verify the site settings for Web API.

Step 2. Configure permissions

You'll have to configure permissions so that users are able to use the Web API feature. In this example, you'll enable the Contact table for table permissions, create a web role using the Web API, add the table permissions for the Contact table to this web role, and then add the web role to users in order to allow them to use the Web API.

  1. On the left pane of the Portal Management app, select Table Permissions.

  2. Select New.

  3. In the Name box, enter Contact Table Permission.

  4. In the Table Name list, select Contact (contact).

  5. In the Website list, select your website record.

  6. In the Access Type list, select Global.

  7. Select Read, Write, Create, and Delete privileges.

  8. Select Save & Close.

    Contact table permissions.

Create a web role

You can use an existing web role in your website or create a new web role.

  1. On the left pane, select Web Roles .

  2. Select New.

  3. In the Name box, enter Web API User (or any name that best reflects the role of the user accessing this functionality).

  4. In the Website list, select your website record.

    Add Web API User web role.

  5. Select Save.

  1. With the new or existing web role, select Related > Table Permissions.

    Add related table permissions to web role.

  2. Select Add Existing Table Permission.

  3. Select Contact Table Permission, created earlier.

    Select contact table permission.

  4. Select Add.

  5. Select Save & Close.

    Table permissions view.

Add contacts to the web role

  1. On the left pane, select Contacts.

  2. Select a contact that you want to use in this example for the Web API.


    This contact is the user account used in this example for testing the Web API. Be sure to select the correct contact in your portal.

  3. Select Related > Web Roles.

    Selecting related web roles.

  4. Select Add Existing Web Role.

  5. Select the Web API User role, created earlier.

  6. Select Add.

    Web role associated view.

  7. Select Save & Close.

Step 3. Create a webpage

Now that you've enabled the Web API and configured user permissions, create a webpage with sample code to view, edit, create, and delete records.

  1. On the left pane of the Portal Management app, select Web Pages.

  2. Select New.

  3. In the Name box, enter webapi.

  4. In the Website list, select your website record.

  5. For Parent Page, select Home.

  6. For Partial URL, enter webapi.

  7. For Page Template, select Home.

  8. For Publishing State, select Published.

  9. Select Save.

    Web page.

  10. Select Related > Web Pages.

    Related Web Pages

  11. From Web Page Associated View, select webapi.

    Web Page Associated View.

  12. Scroll down to the Content section, and then go to Copy (HTML) (HTML designer).

    Copy HTML content

  13. Select the HTML tab.

    Select the HTML tab

  14. Copy the following sample code snippet and paste it in the HTML designer.

        <!-- Sample code for Web API demonstration -->
        #processingMsg {
            width: 150px;
            text-align: center;
            padding: 6px 10px;
            z-index: 9999;
            top: 0;
            left: 40%;
            position: fixed;
            -webkit-border-radius: 0 0 2px 2px;
            border-radius: 0 0 2px 2px;
            -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
            display: none;
        table td[data-attribute] .glyphicon-pencil {
            margin-left: 5px;
            opacity: 0;
        table td[data-attribute]:hover .glyphicon-pencil {
            opacity: 0.7;
      $(function() {
        //Web API ajax wrapper
        (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;
                .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)
        // Notification component
        var notificationMsg = (function() {
          var $processingMsgEl = $('#processingMsg'),
            _msg = 'Processing...',
            _stack = 0,
          return {
            show: function(msg) {
              $processingMsgEl.text(msg || _msg);
              if (_stack === 0) {
            hide: function() {
              if (_stack <= 0) {
                _stack = 0;
                _endTimeout = setTimeout(function() {
                }, 500);
        // Inline editable table component
        var webAPIExampleTable = (function() {
          var trTpl = '<% _.forEach(data, function(data){ %>' +
            '<tr data-id="<%=data.id%>" data-name="<%=data.fullname%>">' +
            '<% _.forEach(columns, function(col){ %>' +
            '<td data-attribute="<%=col.name%>" data-label="<%=col.label%>" data-value="<%=data[col.name]%>">' +
            '<%-data[col.name]%><i class="glyphicon glyphicon-pencil"></i>' +
            '</td>' +
            '<% }) %>' +
            '<td>' +
            '<button class="btn btn-default delete" type="submit"><i class="glyphicon glyphicon-trash" aria-hidden="true"></i></button>' +
            '</td>' +
            '</tr>' +
            '<% }) %>';
          var tableTpl = '<table class="table table-hover">' +
            '<thead>' +
            '<tr>' +
            '<% _.forEach(columns, function(col){ %>' +
            '<th><%=col.label%></th>' +
            '<% }) %>' +
            '<th>' +
            '<button class="btn btn-default add" type="submit">' +
            '<i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add Sample Record' +
            '</button>' +
            '</th>' +
            '</tr>' +
            '</thead>' +
            '<tbody>' + trTpl + '</tbody>' +
          function getDataObject(rowEl) {
            var $rowEl = $(rowEl),
              attrObj = {
                id: $rowEl.attr('data-id'),
                name: $rowEl.attr('data-name')
            $rowEl.find('td').each(function(i, el) {
              var $el = $(el),
                key = $el.attr('data-attribute');
              if (key) {
                attrObj[key] = $el.attr('data-value');
            return attrObj;
          function bindRowEvents(tr, config) {
            var $row = $(tr),
              $deleteButton = $row.find('button.delete'),
              dataObj = getDataObject($row);
            $.each(config.columns, function(i, col) {
              var $el = $row.find('td[data-attribute="' + col.name + '"]');
              $el.on('click', $.proxy(col.handler, $el, col, dataObj));
            //User can delete record using this button
            $deleteButton.on('click', $.proxy(config.deleteHandler, $row, dataObj));
          function bindTableEvents($table, config) {
            $table.find('tbody tr').each(function(i, tr) {
              bindRowEvents(tr, config);
            $table.find('thead button.add').on('click', $.proxy(config.addHandler, $table));
          return function(config) {
            var me = this,
              columns = config.columns,
              addHandler = config.addHandler,
              deleteHandler = config.deleteHandler,
            me.render = function(el) {
              $table = $(el).html(_.template(tableTpl)({
                columns: columns,
                data: me.data
              bindTableEvents($table, {
                columns: columns,
                addHandler: addHandler,
                deleteHandler: deleteHandler
            me.addRecord = function(record) {
              $table.find('tbody tr:first').before(_.template(trTpl)({
                columns: columns,
                data: [record]
              bindRowEvents($table.find('tbody tr:first'), config);
            me.updateRecord = function(attributeName, newValue, record) {
              $table.find('tr[data-id="' + record.id + '"] td[data-attribute="' + attributeName + '"]').text(newValue);
            me.removeRecord = function(record) {
              $table.find('tr[data-id="' + record.id + '"]').fadeTo("slow", 0.7, function() {
        //Applicaton ajax wrapper 
        function appAjax(processingMsg, ajaxOptions) {
          return webapi.safeAjax(ajaxOptions)
            .fail(function(response) {
              if (response.responseJSON) {
                alert("Error: " + response.responseJSON.error.message)
              } else {
                alert("Error: Web API is not available... ")
        function loadRecords() {
          return appAjax('Loading...', {
            type: "GET",
            url: "/_api/contacts?$select=fullname,firstname,lastname,emailaddress1,telephone1",
            contentType: "application/json"
        function addSampleRecord() {
          //Sample data to create a record - change as appropriate
          var recordObj = {
            firstname: "Willie",
            lastname: "Huff" + _.random(100, 999),
            emailaddress1: "Willie.Huff@contoso.com",
            telephone1: "555-123-4567"
          appAjax('Adding...', {
            type: "POST",
            url: "/_api/contacts",
            contentType: "application/json",
            data: JSON.stringify(recordObj),
            success: function(res, status, xhr) {
              recordObj.id = xhr.getResponseHeader("entityid");
              recordObj.fullname = recordObj.firstname + " " + recordObj.lastname;
          return false;
        function deleteRecord(recordObj) {
          var response = confirm("Are you sure, you want to delete \"" + recordObj.name + "\" ?");
          if (response == true) {
            appAjax('Deleting...', {
              type: "DELETE",
              url: "/_api/contacts(" + recordObj.id + ")",
              contentType: "application/json",
              success: function(res) {
          return false;
        function updateRecordAttribute(col, recordObj) {
          var attributeName = col.name,
            value = recordObj[attributeName],
            newValue = prompt("Please enter \"" + col.label + "\"", value);
          if (newValue != null && newValue !== value) {
            appAjax('Updating...', {
              type: "PUT",
              url: "/_api/contacts(" + recordObj.id + ")/" + attributeName,
              contentType: "application/json",
              data: JSON.stringify({
                "value": newValue
              success: function(res) {
                table.updateRecord(attributeName, newValue, recordObj);
          return false;
        var table = new webAPIExampleTable({
          columns: [{
            name: 'firstname',
            label: 'First Name',
            handler: updateRecordAttribute
          }, {
            name: 'lastname',
            label: 'Last Name',
            handler: updateRecordAttribute
          }, {
            name: 'emailaddress1',
            label: 'Email',
            handler: updateRecordAttribute
          }, {
            name: 'telephone1',
            label: 'Telephone',
            handler: updateRecordAttribute
          data: [],
          addHandler: addSampleRecord,
          deleteHandler: deleteRecord
        loadRecords().done(function(data) {
          table.data = _.map(data.value, function(record){
            record.id = record.contactid;
            return record;
    <div id="processingMsg" class="alert alert-warning" role="alert"></div>
    <div id="dataTable"></div>

    Paste code.

  15. Select Save & Close.

Step 4. Clear the portals cache

You've created a webapi sample page to test the Web API functionality. Before you get started, ensure that the Power Apps portals cache has been cleared so that the changes from the Portal Management app are reflected on your portal.

IMPORTANT: Clearing the portal server-side cache causes temporary performance degradation of the portal while data gets reloaded from Microsoft Dataverse.

To clear the cache:

  1. Sign in to your portal as a member of the Administrators web role.

  2. Change the URL by appending /_services/about at the end. For example, if the portal URL is https://contoso.powerappsportals.com, change it to https://contoso.powerappsportals.com/_services/about.

    Clear the cache.

    NOTE: You must be a member of the Administrators web role to clear the cache. If you see a blank screen, check the web role assignments.

  3. Select Clear cache.

More information: Clear the server-side cache for a portal

Step 5. Use the Web API to read, view, edit, create, and delete

The sample webpage with the URL webapi created earlier is now ready for testing.

To test the Web API functionality:

  1. Sign in to your portal with the user account that has been assigned the Web API User role you created earlier.

  2. Go to the webapi webpage created earlier. For example, https://contoso.powerappsportals.com/webapi. The WebAPI will retrieve records from Micrsoft Dataverse.

    Sample webapi webpage.

  3. Select Add Sample Record to add the sample record from the script.

  4. Select a field. In this example, we've selected Email to change the email address of a contact.

    Edit email

  5. Select Delete button to delete a record.

Now that you've created a webpage with a sample to read, edit, create, and delete records, you can customize the forms and layout.

Next step

Compose HTTP requests and handle errors

See also

Portals Web API overview
Portals write, update and delete operations using the Web API
Portals read operations using the Web API
Configure column permissions