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 propertycustomerNumber- Always expose
SystemIdas theidfield and setODataKeyFields = SystemId— this gives each record a stable GUID key. - Set
DelayedInsert = trueso 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: Bearerheader. - 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
Post a Comment