Writing Your First Business Central API from Scratch (AL Tutorial)

Sooner or later every Business Central developer needs to expose data to the outside world — a website, a Power Automate flow, a mobile app, or another system. The modern way to do this is with a custom API page in AL. Unlike the older "publish as web service" approach, an API page gives you a clean, versioned, high-performance REST endpoint that follows Microsoft's OData v4 conventions.

Note: If want to Check Standard API then visit Standard API List 

In this tutorial we will build a simple Customer Rating API from scratch, publish it, and call it. You only need VS Code with the AL extension and a Business Central sandbox.

Step 1: The table we will expose

We will use a small custom table so the example is self-contained.

table 50100 "Customer Rating"
{
    DataClassification = CustomerContent;

    fields
    {
        field(1; "No."; Code[20]) { }
        field(2; "Customer No."; Code[20])
        {
            TableRelation = Customer."No.";
        }
        field(3; Rating; Integer) { }
        field(4; Comment; Text[250]) { }
    }

    keys
    {
        key(PK; "No.") { Clustered = true; }
    }
}

Step 2: Create the API page

An API page is just a page with PageType = API and a few required properties. These properties become part of your endpoint URL, so choose them carefully — they are hard to change later without breaking consumers.

page 50100 "Customer Rating API"
{
    PageType = API;
    Caption = 'Customer Rating API';
    APIPublisher = 'mibutech';
    APIGroup = 'sales';
    APIVersion = 'v1.0';
    EntityName = 'customerRating';
    EntitySetName = 'customerRatings';
    SourceTable = "Customer Rating";
    DelayedInsert = true;
    ODataKeyFields = SystemId;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field(id; Rec.SystemId)
                {
                    Caption = 'Id';
                    Editable = false;
                }
                field(number; Rec."No.") { Caption = 'No'; }
                field(customerNumber; Rec."Customer No.")
                {
                    Caption = 'Customer Number';
                }
                field(rating; Rec.Rating) { Caption = 'Rating'; }
                field(comment; Rec.Comment) { Caption = 'Comment'; }
            }
        }
    }
}

Step 3: Understand your endpoint URL

Once published, the properties above assemble into a predictable URL. For a SaaS environment it looks like this:

https://api.businesscentral.dynamics.com/v2.0/{tenantId}/{environment}
    /api/mibutech/sales/v1.0/customerRatings

Notice how APIPublisher, APIGroup, APIVersion, and EntitySetName all appear in the path. That is why naming them well matters.

Step 4: Field naming rules (where beginners get stuck)

API field names must be camelCase and must not contain spaces or special characters — they become JSON property names. Use the control name (the part before the semicolon), not the caption, to control the JSON output:

  • field(customerNumber; Rec."Customer No.") → JSON property customerNumber
  • Always expose SystemId as the id field and set ODataKeyFields = SystemId — this gives each record a stable GUID key.
  • Set DelayedInsert = true so a record is only created once all fields arrive in the POST body.

Step 5: Publish and test

Press F5 in VS Code to publish the extension to your sandbox. To test the API you need an authenticated call. The two common options:

  • OAuth 2.0 (required for production and SaaS): register an app registration in Entra ID, grant it Business Central API permissions, and use the token in an Authorization: Bearer header.
  • A REST client like Postman or the VS Code REST Client extension to send requests while developing.

A GET request returns your records as JSON:

GET .../api/mibutech/sales/v1.0/customerRatings
Authorization: Bearer {token}

// Response
{
  "value": [
    {
      "id": "a1b2c3d4-...",
      "number": "R0001",
      "customerNumber": "10000",
      "rating": 5,
      "comment": "Excellent"
    }
  ]
}

Step 6: Create a record with POST

POST .../api/mibutech/sales/v1.0/customerRatings
Content-Type: application/json
Authorization: Bearer {token}

{
  "number": "R0002",
  "customerNumber": "20000",
  "rating": 4,
  "comment": "Good"
}

To update a record you send a PATCH to the record's URL (with its id), and you must include the If-Match header — either the record's ETag or * to skip the concurrency check.

Common mistakes to avoid

  • Changing APIVersion after go-live. Consumers hard-code the URL. Treat v1.0 as a contract; add v2.0 for breaking changes instead of editing v1.0.
  • Exposing too many fields. Only include what the consumer needs — every field is part of your public contract.
  • Forgetting DelayedInsert. Without it, POSTs that set fields in an unexpected order can fail.
  • Using Basic auth in production. It is deprecated for SaaS; use OAuth 2.0.
  • Skipping the id/SystemId setup. Without a stable key, PATCH and DELETE calls break.

Final thoughts

A custom API page is one of the most useful things you can build in AL — it turns Business Central into a data source any modern system can talk to. Start with a single, well-named entity like the one above, get GET and POST working end to end, and expand from there. Once you understand the pattern, exposing any table is a five-minute job.

Want a follow-up on securing your API with OAuth 2.0 step by step, or on calling an external API from Business Central? Let me know in the comments.

Comments

Popular posts from this blog

IF - THEN And IF - THEN - ELSE Satement In Microsoft Dynamics NAV

WITH Loop / Statement

CASE