Exposing business logic via APIs to the Power Platform

In this video, I take a look at how easy it is with a few lines of AL to expose business logic via APIs. Check it out:

https://youtu.be/fvu1scGT2Qo

In this video, Erik Hougaard demonstrates how to expose standard Business Central business logic as API bound actions that can be called from Power Platform (Power Automate). He walks through creating a custom API page that converts a sales quote to a sales order, triggered directly from a Power Automate flow — no UI required.

The Problem: Limited Standard API Actions

Power Automate’s Business Central connector offers an “Execute an action” step that lets you trigger bound actions on API pages. Out of the box, Microsoft provides actions like “Sales Quote – Make Order” under the v2.0 API category. However, the standard action requires a system ID (GUID) to identify the record. Erik’s goal is to create a custom variant that accepts the actual quote number — a much more natural primary key for users and integrations.

What is a Bound Action?

A bound action is a function that is linked to a specific data set (an API page). The page represents a table, and the action performs business logic on that data. The table can serve as either:

  • A means of passing parameters to the action
  • The actual record you want to operate on (e.g., a sales order or quote)

Creating the API Page

Erik uses the ASet AL Dev Tools extension in Visual Studio Code to scaffold the page. Using the “New AL File Wizard,” he creates an API page backed by the Sales Header table. The key API properties are:

  • APIPublisher: hougaard
  • APIGroup: youtube
  • APIVersion: v2.0
  • EntityName: quote
  • EntitySetName: quotes
  • PageType: API

The page exposes just the primary key fields (Document Type and Number) and uses a table view filter to restrict results to quotes only:

page 50140 "Convert Quote to Order"
{
    APIGroup = 'youtube';
    APIPublisher = 'hougaard';
    APIVersion = 'v2.0';
    ApplicationArea = All;
    Caption = 'Convert Quote to Order';
    DelayedInsert = true;
    EntityName = 'quote';
    EntitySetName = 'quotes';
    PageType = API;
    SourceTable = "Sales Header";
    SourceTableView = where("Document Type" = const(Quote));

    layout
    {
        area(content)
        {
            repeater(General)
            {
                field(documentType; Rec."Document Type")
                {
                    Caption = 'Document Type';
                }
                field(number; Rec."No.")
                {
                    Caption = 'No.';
                }
            }
        }
    }
}

Since this is an API page, it’s self-exposing — you don’t need to manually register it in web services. It becomes available automatically once deployed.

Tracing the Standard Business Logic

Before writing the action, Erik traces through the base application to understand how the standard “Make Order” action works on the Sales Quote page:

  1. The page action checks if the quote is approved
  2. It runs the Sales-Quote to Order (Yes/No) codeunit
  3. That codeunit performs validation (test fields) and then calls Sales-Quote to Order codeunit via .Run()

This pattern is common across Business Central: posting routines, quote-to-order conversions, blanket order processing — they’re all codeunits that accept a record and perform the operation.

Adding the Bound Action

The actual action is a procedure on the API page that calls the standard codeunit. Three key elements make it work as a bound action:

  1. The [ServiceEnabled] attribute — tells Business Central this procedure can be called via web services
  2. A WebServiceActionContext parameter — provides the plumbing for the API response
  3. Action context configuration — sets the result code, object type, object ID, and entity key
[ServiceEnabled]
procedure ConvertToOrder(var ActionContext: WebServiceActionContext)
var
    SalesQuoteToOrder: Codeunit "Sales-Quote to Order (Yes/No)";
begin
    SalesQuoteToOrder.Run(Rec);

    ActionContext.SetObjectType(ObjectType::Page);
    ActionContext.SetObjectId(Page::"Convert Quote to Order");
    ActionContext.AddEntityKey(Rec.FieldNo(SystemId), Rec.SystemId);
    ActionContext.SetResultCode(WebServiceActionResultCode::Created);
end;

Let’s break down the action context lines:

  • SetObjectType: Declares that we’re working with a Page object (the only valid type for API bound actions)
  • SetObjectId: References the page this action belongs to
  • AddEntityKey: Returns identifying information about the record that was processed. Erik uses FieldNo() instead of hard-coded field numbers — a best practice
  • SetResultCode: Returns the HTTP result code. Created maps to HTTP 201, which is appropriate since a new sales order is being created

Exposing Multiple Actions on One Page

You can add multiple bound actions to the same API page. Erik demonstrates adding a second action to convert a quote to an invoice:

[ServiceEnabled]
procedure ConvertToInvoice(var ActionContext: WebServiceActionContext)
var
    SalesQuoteToInvoice: Codeunit "Sales-Quote to Order (Yes/No)"; // Use the appropriate codeunit
begin
    // Call the appropriate conversion codeunit
    // ...

    ActionContext.SetObjectType(ObjectType::Page);
    ActionContext.SetObjectId(Page::"Convert Quote to Order");
    ActionContext.AddEntityKey(Rec.FieldNo(SystemId), Rec.SystemId);
    ActionContext.SetResultCode(WebServiceActionResultCode::Created);
end;

In Power Automate, these appear as separate actions in the naming convention: entity-functionName. So you’d see quote-ConvertToOrder and quote-ConvertToInvoice in the action dropdown.

Testing from Power Automate

To test, Erik creates a simple Power Automate flow with a manual trigger:

  1. Add the Business Central (cloud) connector
  2. Select the Execute an action step
  3. Choose the environment and company
  4. Find the API category (hougaard / youtube)
  5. Select the action (quote - ConvertToOrder)
  6. Fill in the Document Type (Quote) and the quote number

Because the custom API uses the real primary key fields instead of a GUID, users can simply enter the quote number they see in Business Central. Running the flow successfully converts quote S-QUO1002 to a sales order — on the first try.

The Viewer Challenge: Spot the Bug

When Erik tests the second action (ConvertToInvoice), it fails with the error: “Action quotes-ConvertToInvoice cannot be found.” He leaves this as a challenge for viewers to spot the mistake. The issue likely relates to the function naming or a mismatch between the action name registered in the API and what Power Automate expects — a common pitfall when adding multiple bound actions.

Provided Source Code Reference

The repository also includes a separate API page example showing a more typical API structure. Here’s the app.json for the project:

{
  "id": "276669e0-9a44-4938-a71c-728feaf4c7e8",
  "name": "APIsInBC24",
  "publisher": "Default Publisher",
  "version": "1.0.0.0",
  "platform": "1.0.0.0",
  "application": "23.0.0.0",
  "idRanges": [
    {
      "from": 50100,
      "to": 50149
    }
  ],
  "runtime": "12.0",
  "features": [
    "NoImplicitWith"
  ]
}

Key Takeaways

  • Bound actions are the mechanism for exposing custom business logic through Business Central’s API pages to Power Automate and other OData consumers
  • The recipe is straightforward: create an API page, add a [ServiceEnabled] procedure with a WebServiceActionContext parameter, and configure the result context
  • Many standard Business Central operations (posting, quote conversion, blanket order processing) follow the same pattern of Codeunit.Run(Record), making them easy to wrap as bound actions
  • Using real primary key fields (like document number) instead of system GUIDs makes the API far more user-friendly for Power Automate flows
  • You can expose multiple actions on a single API page, each appearing as a separate selectable action in the Power Automate connector