17.1 API Pages (PageType=API), OData Web Services & REST Endpoints

Key Takeaways

  • API Pages in Business Central are lightweight, headless pages created by setting PageType = API, designed specifically for high-performance REST/OData v4 data exchange rather than user interface rendering.
  • Endpoint routing follows a rigid URI schema: https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/api/{APIPublisher}/{APIGroup}/{APIVersion}/companies({companyId})/{EntitySetName}.
  • Every API Page requires APIPublisher, APIGroup, APIVersion, EntityName, EntitySetName, DelayedInsert = true, and ODataKeyFields = SystemId (or Id), binding to an underlying GUID key.
  • API Pages support standard OData v4 query options: $select (sparse fieldsets), $filter (logical filtering), $expand (nested sub-entities like lines), $orderby (sorting), $top (paging limit), and $skip (paging offset).
  • Deep insert capabilities allow creating a header entity and all its child line entities within a single atomic POST request containing nested JSON arrays.
Last updated: August 2026

17.1 API Pages (PageType=API), OData Web Services & REST Endpoints

Modern enterprise application integration demands robust, standards-compliant, and high-throughput REST APIs. In Dynamics 365 Business Central, developers expose application data and business logic to external applications—such as Power Apps, Azure Logic Apps, third-party eCommerce engines, customer web portals, and enterprise service buses—primarily through API Pages (PageType = API). For the MB-820 exam, developers must master API page anatomy, endpoint URL routing mechanics, entity GUID key management, OData v4 query parameters, and deep insert patterns.


1. API Page Architecture & Fundamentals

Unlike standard UI pages (Card, List, Document) that render controls for human interaction in the Business Central Web Client, an API Page is a dedicated, headless integration endpoint optimized exclusively for machine-to-machine REST/OData v4 communication.

+-------------------------------------------------------------------------+
|                         EXTERNAL REST CLIENT                            |
|          (Power Automate / Custom SPA / E-Commerce Backend)             |
+-----------------------------------┬-------------------------------------+
                                    │ (HTTPS REST / OData v4 Request)
                                    ▼
+-------------------------------------------------------------------------+
|                   BUSINESS CENTRAL SERVER PIPELINE (NST)                |
|                                                                         |
|   [URL Routing Engine: /api/{publisher}/{group}/{version}/...]          |
|   [OData Key Resolution: SystemId (GUID) / ODataKeyFields]              |
|   [JSON Payload Deserialization & DelayedInsert Buffer]                 |
+-----------------------------------┬-------------------------------------+
                                    │ (AL Runtime Execution)
                                    ▼
+-------------------------------------------------------------------------+
|                       AL OBJECTS & DATA ACCESS                          |
|                                                                         |
|   [page 50130 "Custom Customer API" (PageType = API)]                   |
|   [Business Logic / Table Validation Triggers (OnInsert, OnModify)]     |
|   [Underlying Database Storage / Primary Keys / SQL Operations]         |
+-------------------------------------------------------------------------+

Why Use API Pages Over Standard Web Service Pages?

In earlier versions of Dynamics NAV and Business Central, developers exposed standard Card or List pages as web services via the Web Services admin page. While functional, publishing UI pages as web services introduces significant performance and architectural drawbacks:

  • Performance Overhead: Standard UI pages load FactBoxes, page actions, fast tab layouts, and UI trigger logic that have no relevance to API consumers. API pages bypass all client-side UI rendering metadata, resulting in drastically lower memory consumption and sub-millisecond JSON serialization.
  • Strict REST/OData v4 Compliance: API pages automatically conform to modern OData v4 standards, enforcing standardized camelCase field naming, ISO 8601 timestamps, enum string representations, and uniform JSON error payloads.
  • Versioning & Namespacing: API pages provide built-in URI versioning (v1.0, v2.0, beta) and publisher/group namespaces. This prevents breaking changes across extension updates and allows multiple API versions to coexist simultaneously.
  • Deep Hierarchy Support: API pages natively support nested entities (e.g., Sales Order Lines inside Sales Order Headers) using part controls, enabling single-payload atomic deep inserts.

2. API Page Anatomy & AL Implementation

To construct an API page, developers declare specific page properties that define its REST metadata, routing URI, and data-binding behavior:

API Page PropertyData TypeRequired?Description & Exam Importance
PageTypeEnumYesMust be set to API to instruct the compiler and NST to expose the page as a REST endpoint.
APIPublisherStringYesThe publisher namespace (e.g., 'custom', 'contoso'). Lowercase alphanumeric characters.
APIGroupStringYesThe functional grouping (e.g., 'integrations', 'logistics'). Lowercase alphanumeric characters.
APIVersionStringYesThe API version identifier (e.g., 'v1.0', 'v2.0', 'beta').
EntityNameStringYesThe singular name of the entity in camelCase (e.g., 'customCustomer', 'shipment').
EntitySetNameStringYesThe plural name of the entity set in camelCase (e.g., 'customCustomers', 'shipments').
DelayedInsertBooleanYesMust be set to true. Ensures record insertion is delayed until all field values from the request payload are populated into the buffer.
ODataKeyFieldsField listYesSpecifies the field(s) used as the unique REST resource key. Typically set to SystemId (or Id).
SourceTableTableYesThe underlying Business Central table mapped by this API page.
DataAccessIntentEnumOptionalCan be set to ReadOnly to route GET queries to read-scale-out database replicas.
ChangeTrackingAllowedBooleanOptionalSet to true to enable OData delta tokens for incremental data synchronization.

Complete AL Code Example: Custom Customer API Page

page 50130 "Custom Customer API"
{
    PageType = API;
    Caption = 'customCustomer';
    APIPublisher = 'custom';
    APIGroup = 'integrations';
    APIVersion = 'v2.0';
    EntityName = 'customCustomer';
    EntitySetName = 'customCustomers';
    SourceTable = Customer;
    DelayedInsert = true;
    ODataKeyFields = SystemId;
    Extensible = false;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field(id; Rec.SystemId)
                {
                    Caption = 'Id';
                    Editable = false;
                }
                field(number; Rec."No.")
                {
                    Caption = 'Number';
                }
                field(displayName; Rec.Name)
                {
                    Caption = 'DisplayName';
                }
                field(customerPostingGroup; Rec."Customer Posting Group")
                {
                    Caption = 'CustomerPostingGroup';
                }
                field(email; Rec."E-Mail")
                {
                    Caption = 'Email';
                }
                field(phoneNumber; Rec."Phone No.")
                {
                    Caption = 'PhoneNumber';
                }
                field(blocked; Rec.Blocked)
                {
                    Caption = 'Blocked';
                }
                field(balanceLCY; Rec."Balance (LCY)")
                {
                    Caption = 'BalanceLCY';
                    Editable = false;
                }
            }
        }
    }
}

Exam Watchout — DelayedInsert = true and SystemId: In standard UI pages, DelayedInsert is optional. On API pages, DelayedInsert = true is mandatory. When an external client sends an HTTP POST request with a JSON payload, setting DelayedInsert = true ensures that the runtime assigns all incoming JSON field values to the record buffer before executing the OnInsertRecord trigger or calling Rec.Insert(true). Without DelayedInsert = true, table validation logic expecting dependent field values will fail. Furthermore, REST endpoints use SystemId (the platform-generated immutable GUID) rather than compound primary keys (e.g., "No."), ensuring stable URI addresses even if human-readable codes are renamed.

Loading diagram...
API Page Request Processing & DelayedInsert Execution Lifecycle

3. REST / OData Endpoint URL Routing Mechanics

Business Central constructs deterministic REST endpoint URIs based on the declared API page properties and tenant deployment topology.

Standard Microsoft APIs vs. Custom Extension APIs

  1. Standard Microsoft v2.0 APIs:

    GET https://api.businesscentral.dynamics.com/v2.0/{tenantId}/{environmentName}/api/v2.0/companies({companyId})/customers
    

    Standard Microsoft APIs omit the publisher and group segments and use the built-in /api/v2.0/ namespace.

  2. Custom Extension APIs:

    GET https://api.businesscentral.dynamics.com/v2.0/{tenantId}/{environmentName}/api/{APIPublisher}/{APIGroup}/{APIVersion}/companies({companyId})/{EntitySetName}
    

    For the custom customer API page created above, the resulting URI is:

    GET https://api.businesscentral.dynamics.com/v2.0/tenant-guid/Production/api/custom/integrations/v2.0/companies(12345678-1234-1234-1234-123456789abc)/customCustomers
    
  3. Company-Agnostic Top-Level Queries: To list all available companies in a tenant environment, clients query the root company endpoint:

    GET https://api.businesscentral.dynamics.com/v2.0/{tenantId}/{environmentName}/api/v2.0/companies
    

Accessing a Specific Entity Instance

To retrieve, update (PATCH), or delete (DELETE) a single record, append the entity's SystemId GUID in parentheses:

GET https://api.businesscentral.dynamics.com/v2.0/{tenant}/{env}/api/custom/integrations/v2.0/companies({companyId})/customCustomers(d4f5a6b7-8901-2345-6789-abcdef012345)

4. OData v4 Query Parameters & Performance Optimization

Business Central API pages support the full spectrum of OData v4 system query options, allowing clients to control projection, filtering, pagination, and relational expansions.

Query OptionPurposeExample SyntaxAL Performance Impact
$selectSparse fieldset projection?$select=number,displayName,emailOptimizes SQL SELECT statement to fetch only requested columns, minimizing network payload and NST memory buffer size.
$filterBoolean expression filtering?$filter=blocked eq false and balanceLCY gt 5000Translates directly to SQL WHERE clauses. Supports eq, ne, gt, ge, lt, le, and, or, not, startswith, endswith, contains.
$expandRelational entity expansion?$expand=salesOrderLinesExecutes SQL joins to return parent headers and nested child lines in a single response payload.
$orderbySorting results?$orderby=displayName asc,number descTranslates to SQL ORDER BY. Fields should be backed by appropriate secondary table keys to avoid SQL table scans.
$topMaximum record count?$top=50Restricts the page size. In SaaS, Business Central enforces an upper server limit (default: 20,000 records).
$skipRecord offset pagination?$skip=100&$top=50Skips the first 100 records and retrieves the subsequent 50 records.
$countTotal entity count?$count=trueReturns an @odata.count property in the response indicating total matching records.

Handling Pagination with @odata.nextLink

When an API query returns more records than the requested $top or the server-enforced page size limit, Business Central includes an @odata.nextLink property in the response JSON:

{
  "@odata.context": "https://api.businesscentral.dynamics.com/v2.0/.../$metadata#customCustomers",
  "value": [
    {
      "id": "d4f5a6b7-8901-2345-6789-abcdef012345",
      "number": "C00010",
      "displayName": "Contoso Ltd.",
      "email": "contact@contoso.com"
    }
  ],
  "@odata.nextLink": "https://api.businesscentral.dynamics.com/v2.0/.../customCustomers?$skip=50&$top=50"
}

External integration clients must check for the presence of @odata.nextLink and iterate through subsequent pages until @odata.nextLink is null.

5. Designing Nested Sub-Entities & Deep Inserts

Many business documents require header-line relationships (e.g., Sales Orders with Sales Order Lines, Purchase Invoices with Lines). In AL, nested sub-entities are exposed on an API page using a part control linked to a sub-API page.

Sub-API Page Definition (Lines)

page 50131 "Custom Sales Order Line API"
{
    PageType = API;
    Caption = 'customSalesOrderLine';
    APIPublisher = 'custom';
    APIGroup = 'integrations';
    APIVersion = 'v2.0';
    EntityName = 'customSalesOrderLine';
    EntitySetName = 'customSalesOrderLines';
    SourceTable = "Sales Line";
    DelayedInsert = true;
    ODataKeyFields = SystemId;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field(id; Rec.SystemId) { }
                field(documentId; Rec."Document Id") { }
                field(lineNumber; Rec."Line No.") { }
                field(lineType; Rec.Type) { }
                field(itemNumber; Rec."No.") { }
                field(quantity; Rec.Quantity) { }
                field(unitPrice; Rec."Unit Price") { }
            }
        }
    }
}

Parent Header API Page with Subpage Part

page 50132 "Custom Sales Order API"
{
    PageType = API;
    Caption = 'customSalesOrder';
    APIPublisher = 'custom';
    APIGroup = 'integrations';
    APIVersion = 'v2.0';
    EntityName = 'customSalesOrder';
    EntitySetName = 'customSalesOrders';
    SourceTable = "Sales Header";
    DelayedInsert = true;
    ODataKeyFields = SystemId;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field(id; Rec.SystemId) { }
                field(number; Rec."No.") { }
                field(customerNumber; Rec."Sell-to Customer No.") { }
                field(orderDate; Rec."Order Date") { }
                
                part(salesOrderLines; "Custom Sales Order Line API")
                {
                    Caption = 'Lines';
                    EntityName = 'customSalesOrderLine';
                    EntitySetName = 'customSalesOrderLines';
                    SubPageLink = "Document Id" = field(SystemId);
                }
            }
        }
    }
}

Deep Insert JSON Payload & Atomic Transaction Rules

A Deep Insert allows an external application to submit the header and all associated child lines in a single atomic HTTP POST transaction.

POST https://api.businesscentral.dynamics.com/v2.0/{tenant}/{env}/api/custom/integrations/v2.0/companies({id})/customSalesOrders
Content-Type: application/json

{
  "customerNumber": "C00010",
  "orderDate": "2026-08-29",
  "salesOrderLines": [
    {
      "lineType": "Item",
      "itemNumber": "1000",
      "quantity": 2,
      "unitPrice": 150.00
    },
    {
      "lineType": "Item",
      "itemNumber": "1001",
      "quantity": 5,
      "unitPrice": 45.50
    }
  ]
}

Deep Insert Execution Semantics

  1. Atomic Transaction Boundary: The Business Central Server processes the header record insertion first, creates the parent buffer, and then sequentially iterates through each nested line in the salesOrderLines array.
  2. Automatic Link Propagation: The platform automatically assigns the parent header's SystemId to the child line's Document Id field based on the SubPageLink property definition.
  3. Rollback on Line Validation Failure: If validation on any child line fails (e.g., insufficient inventory, blocked item, or invalid posting group), the entire database transaction is rolled back. No orphaned header record remains in the database, and an HTTP 400 Bad Request error is returned to the client.
Test Your Knowledge

Why is it mandatory to set DelayedInsert = true on AL Page objects of type API (PageType = API)?

A
B
C
D
Test Your Knowledge

An external integration submits a Deep Insert HTTP POST request containing a sales header and three nested sales lines. During processing of the third line, an AL validation error occurs due to an invalid item number. What is the resulting state of the Business Central database?

A
B
C
D
Test Your Knowledge

A developer creates a custom API page with properties APIPublisher = 'fabrikam', APIGroup = 'logistics', APIVersion = 'v1.0', and EntitySetName = 'warehouseShipments'. Which relative URL path must an external client call to query this entity set for a specific company?

A
B
C
D
Test Your Knowledge

An integration developer needs to retrieve customer records from an API page. The query must return only the customer number and name, filter for customers with a balance greater than 1000, sort results alphabetically by name, skip the first 50 records, and retrieve the next 25 records. Which OData query string is formulated correctly?

A
B
C
D