skill-api
Skill115 starsChanged 10 days agoMITView on GitHub
AL API development patterns for Business Central. Use when creating OData/REST API pages, HttpClient integrations, webhook implementations, or any external system integration via API.
In this repository55 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- claude-plugin/CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- instructions/copilot-instructions.mdCopilot instructions
- agent-builderSkill
- al-agent-build-instructionsSkill
- al-agent-createSkill
- al-agent-instructionsSkill
- al-agent-taskSkill
- al-agent-testSkill
- al-buildSkill
- al-context-createSkill
- al-initializeSkill
- al-memory-createSkill
- al-pr-prepareSkill
- al-spec-createSkill
- architectSkill
- auditSkill
- conductSkill
- developSkill
- presalesSkill
- reviewSkill
- skill-agent-instructionsSkill
- skill-agent-task-patternsSkill
- skill-agent-toolkitSkill
- skill-al-review-pipelineSkill
- skill-apiSkill
- skill-contribution-assistantSkill
- skill-copilotSkill
- skill-debugSkill
- skill-estimationSkill
- skill-eventsSkill
- skill-migrateSkill
- skill-pagesSkill
- skill-performanceSkill
- skill-permissionsSkill
- skill-testingSkill
- skill-translateSkill
- specSkill
- triageSkill
- skill-agent-instructionsSkill
- skill-agent-task-patternsSkill
- skill-agent-toolkitSkill
- skill-al-review-pipelineSkill
- skill-contribution-assistantSkill
- skill-copilotSkill
- skill-debugSkill
- skill-estimationSkill
- skill-eventsSkill
- skill-migrateSkill
- skill-pagesSkill
- skill-performanceSkill
- skill-permissionsSkill
- skill-testingSkill
- skill-translateSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
19 rules. About 0 are common, 11 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 334
Document in .github/plans/{req_name}.architecture.md or a dedicated API design section.
- Distinctive: line 339
Create header API page (Pattern 1)
- Distinctive: line 340
Create subpage(s) for lines/children (Pattern 2)
- Distinctive: line 341
Add bound actions for entity operations (Pattern 3)
- Distinctive: line 342
Add unbound actions if needed (Pattern 4)
- Distinctive: line 343
Add error handling triggers (Pattern 7)
- Distinctive: line 348
Add keys for filterable fields:
- Very specific: line 370
Create role-based permission sets (full access + read-only) for the API pages.
- Distinctive: line 374
Test CRUD operations (create, read, update, delete)
- Distinctive: line 375
Test bound actions (post, release, reopen)
- Very specific: line 376
Test $filter, $select, $expand query options
- Distinctive: line 377
Test error responses (missing required fields, blocked customer, invalid state)
- Very specific: line 378
Test permission sets (read-only user cannot POST/PATCH/DELETE)
- Very specific: line 379
Test If-Match / ETag for optimistic concurrency on PATCH and DELETE
- Distinctive: line 393
Do NOT modify base BC objects — create API pages as extensions only
- Distinctive: line 394
Do NOT expose internal implementation details (codeunit internals, temp tables) in API responses
- Very specific: line 395
Do NOT skip APIVersion — every API page MUST have an explicit version
- Very specific: line 396
Do NOT create breaking changes on stable versions — use beta for previewing changes, then promote
- Very specific: line 397
Do NOT skip error handling — OnInsertRecord, OnModifyRecord, OnDeleteRecord must validate
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Skill: AL API Development
Purpose
Design and implement RESTful API pages for Business Central: API page v2.0 patterns, OData conventions, versioning, bound/unbound actions, webhooks, header-lines navigation, and performance-optimized endpoints.
When to Load
This skill should be loaded when:
- A new API page (PageType = API) needs to be designed or implemented
- Existing BC data must be exposed to external consumers (Power Platform, mobile apps, 3rd-party)
- Custom bound or unbound actions are needed on API endpoints
- An API versioning strategy or deprecation plan is required
- Webhook subscriptions need to be configured for change notifications
- API performance or filtering needs optimization
Core Patterns
Pattern 1: API Page v2.0 (Standard CRUD Endpoint)
page 50100 "Contoso Sales Orders API"
{
APIVersion = 'v2.0';
APIPublisher = 'contoso';
APIGroup = 'sales';
EntityCaption = 'Sales Order';
EntitySetCaption = 'Sales Orders';
EntityName = 'salesOrder'; // singular — used in URL for single entity
EntitySetName = 'salesOrders'; // plural — used in URL for collection
PageType = API;
SourceTable = "Sales Header";
SourceTableView = where("Document Type" = const(Order));
DelayedInsert = true; // required — defers insert until all fields set
ODataKeyFields = SystemId; // required — use SystemId for stable GUIDs
layout
{
area(Content)
{
repeater(Group)
{
field(id; Rec.SystemId)
{
Caption = 'Id';
Editable = false;
}
field(number; Rec."No.")
{
Caption = 'Number';
Editable = false;
}
field(orderDate; Rec."Order Date")
{
Caption = 'Order Date';
}
field(customerNumber; Rec."Sell-to Customer No.")
{
Caption = 'Customer Number';
}
field(customerName; Rec."Sell-to Customer Name")
{
Caption = 'Customer Name';
Editable = false;
}
field(totalAmountIncludingVAT; Rec."Amount Including VAT")
{
Caption = 'Total Amount Including VAT';
Editable = false;
}
field(status; Rec.Status)
{
Caption = 'Status';
Editable = false;
}
field(lastModifiedDateTime; Rec.SystemModifiedAt)
{
Caption = 'Last Modified Date Time';
Editable = false;
}
}
}
}
}Resulting endpoint:
GET /api/contoso/sales/v2.0/companies({companyId})/salesOrders
GET /api/contoso/sales/v2.0/companies({companyId})/salesOrders({id})
POST /api/contoso/sales/v2.0/companies({companyId})/salesOrders
PATCH /api/contoso/sales/v2.0/companies({companyId})/salesOrders({id})
DELETE /api/contoso/sales/v2.0/companies({companyId})/salesOrders({id})Property rules:
ODataKeyFields = SystemId— always useSystemIdfor stable, immutable keysDelayedInsert = true— mandatory on API pages (lets BC set defaults before committing)SourceTableView— pre-filter the source if the table serves multiple document types- Field names use camelCase (OData convention):
customerNumber, notCustomer_Number Editable = falseon computed/system fields to prevent consumer confusion
Pattern 2: Header-Lines with Navigation Property
Expose parent-child relationships via part subpages:
// Add inside the header API page's repeater:
part(salesOrderLines; "Contoso Sales Order Lines API")
{
Caption = 'Lines';
EntityName = 'salesOrderLine';
EntitySetName = 'salesOrderLines';
SubPageLink = "Document Type" = field("Document Type"),
"Document No." = field("No.");
}// Lines API page (subpage)
page 50101 "Contoso Sales Order Lines API"
{
APIVersion = 'v2.0';
APIPublisher = 'contoso';
APIGroup = 'sales';
EntityCaption = 'Sales Order Line';
EntitySetCaption = 'Sales Order Lines';
EntityName = 'salesOrderLine';
EntitySetName = 'salesOrderLines';
PageType = API;
SourceTable = "Sales Line";
DelayedInsert = true;
ODataKeyFields = SystemId;
layout
{
area(Content)
{
repeater(Group)
{
field(id; Rec.SystemId) { Editable = false; }
field(lineNumber; Rec."Line No.") { Editable = false; }
field(lineType; Rec.Type) { Caption = 'Type'; }
field(itemNumber; Rec."No.") { Caption = 'Item Number'; }
field(description; Rec.Description) { Caption = 'Description'; }
field(quantity; Rec.Quantity) { Caption = 'Quantity'; }
field(unitPrice; Rec."Unit Price") { Caption = 'Unit Price'; }
field(lineAmount; Rec."Line Amount") { Caption = 'Line Amount'; Editable = false; }
}
}
}
}Consumer usage:
# Get order with lines expanded
GET /salesOrders({id})?$expand=salesOrderLines
# Get lines for a specific order
GET /salesOrders({id})/salesOrderLines
# Add a line to an order
POST /salesOrders({id})/salesOrderLines
{ "lineType": "Item", "itemNumber": "ITEM-001", "quantity": 10 }Pattern 3: Bound Actions (Operate on Entity)
Bound actions trigger business logic on a specific entity:
// Inside the API page's actions area:
actions
{
area(Processing)
{
// POST /salesOrders({id})/Microsoft.NAV.post
action(post)
{
ApplicationArea = All;
Caption = 'Post';
trigger OnAction()
var
SalesPost: Codeunit "Sales-Post";
begin
Rec.TestField(Status, Rec.Status::Released);
SalesPost.Run(Rec);
end;
}
// POST /salesOrders({id})/Microsoft.NAV.release
action(release)
{
ApplicationArea = All;
Caption = 'Release';
trigger OnAction()
var
ReleaseSalesDoc: Codeunit "Release Sales Document";
begin
ReleaseSalesDoc.PerformManualRelease(Rec);
end;
}
// POST /salesOrders({id})/Microsoft.NAV.reopen
action(reopen)
{
ApplicationArea = All;
Caption = 'Reopen';
trigger OnAction()
var
ReleaseSalesDoc: Codeunit "Release Sales Document";
begin
ReleaseSalesDoc.PerformManualReopen(Rec);
end;
}
}
}Consumer call:
POST /salesOrders({id})/Microsoft.NAV.post
Content-Type: application/jsonPattern 4: Unbound Actions (Standalone Operations)
Unbound actions are not tied to a specific entity — use a virtual/dummy source table:
page 50102 "Contoso Utility API"
{
APIVersion = 'v2.0';
APIPublisher = 'contoso';
APIGroup = 'utilities';
EntityName = 'utilityFunction';
EntitySetName = 'utilityFunctions';
PageType = API;
SourceTable = "Company Information"; // read-only singleton as base
SourceTableTemporary = true;
InsertAllowed = false;
ModifyAllowed = false;
DeleteAllowed = false;
layout
{
area(Content)
{
repeater(Group)
{
field(companyName; Rec.Name) { Editable = false; }
}
}
}
actions
{
area(Processing)
{
// POST /utilityFunctions/Microsoft.NAV.calculateShipping
action(calculateShipping)
{
ApplicationArea = All;
Caption = 'Calculate Shipping';
trigger OnAction()
var
ShippingMgt: Codeunit "Contoso Shipping Management";
Weight: Decimal;
DestCode: Code[20];
begin
Evaluate(Weight, GetActionContext().GetText('weight'));
DestCode := CopyStr(GetActionContext().GetText('destinationCode'), 1, 20);
SetActionResponse(CreateJsonResponse(
ShippingMgt.CalculateCost(Weight, DestCode)));
end;
}
}
}
}When you need versioning/deprecation, webhooks, or trigger-level error handling, load references/api-advanced-patterns.md.
XML Documentation for Public Procedures
Any public procedure that other modules call carries XML doc comments. This covers API pages (above), and equally the library codeunits invoked by API logic or by other codeunits — anything outside the unit's own boundary.
/// <summary>
/// Evaluates whether the customer qualifies as VIP based on sales volume
/// and persists the result on Customer."VIP Customer".
/// </summary>
/// <param name="CustomerNo">The customer number to evaluate. Exits silently if blank or not found.</param>
procedure EvaluateCustomer(CustomerNo: Code[20])
begin
// ...
end;<summary>(required) — what the procedure does and why a caller would invoke it.<param name="...">(required for each non-trivial parameter) — what value to pass and constraints.<returns>(required when there is a return value) — what the value means.localandinternalprocedures: doc is optional.
This surface is what IntelliSense presents to consumers and what AL's missing-documentation diagnostics flag.
Workflow
Step 1: Design API Contract
Before implementing, define:
- Resource model — entities, relationships, navigation properties
- Operations — which HTTP methods per resource (GET/POST/PATCH/DELETE)
- Actions — custom operations (bound: per entity, unbound: global)
- Filtering — which fields consumers can
$filteron (add corresponding keys) - Versioning — initial version and deprecation plan
- Authentication — OAuth 2.0 scope, permission sets needed
Document in .github/plans/{req_name}.architecture.md or a dedicated API design section.
PAUSE — wait for user approval before implementing.
Step 2: Implement API Pages
- Create header API page (Pattern 1)
- Create subpage(s) for lines/children (Pattern 2)
- Add bound actions for entity operations (Pattern 3)
- Add unbound actions if needed (Pattern 4)
- Add error handling triggers (Pattern 7)
- Build:
al_build
Step 3: Optimize for Performance
Add keys for filterable fields:
tableextension 50100 "Contoso Sales Header Ext" extends "Sales Header"
{
keys
{
key(APICustomerDate; "Sell-to Customer No.", "Order Date") { }
key(APIStatus; Status, "Order Date") { }
}
}Key OData query patterns:
- Projection:
?$select=number,customerNumber— reduces payload - Filtering:
?$filter=customerNumber eq 'C00001' and orderDate ge 2025-01-01— server-side - Expansion:
?$expand=salesOrderLines— inline children - Delta links: initial GET returns
@odata.deltaLink; subsequent call with$deltatokenreturns only changes - Pagination:
?$top=50&$skip=100
Step 4: Generate Permission Sets
Create role-based permission sets (full access + read-only) for the API pages. Follow skill-permissions.md for the hierarchy pattern. Minimum: one set granting X on all API pages + RIMD on table data, one read-only set with R only.
Step 5: Test
- Test CRUD operations (create, read, update, delete)
- Test bound actions (post, release, reopen)
- Test
$filter,$select,$expandquery options - Test error responses (missing required fields, blocked customer, invalid state)
- Test permission sets (read-only user cannot POST/PATCH/DELETE)
- Test
If-Match/ ETag for optimistic concurrency on PATCH and DELETE
References
- API Page Type — Microsoft Docs
- API v2.0 Standard Endpoints
- OData Query Parameters
- Custom APIs
- Webhook Subscriptions
- API Performance
Constraints
- This skill covers API page design, implementation patterns, and OData conventions
- Do NOT modify base BC objects — create API pages as extensions only
- Do NOT expose internal implementation details (codeunit internals, temp tables) in API responses
- Do NOT skip
APIVersion— every API page MUST have an explicit version - Do NOT create breaking changes on stable versions — use
betafor previewing changes, then promote - Do NOT skip error handling —
OnInsertRecord,OnModifyRecord,OnDeleteRecordmust validate - Permission set hierarchy →
skill-permissions.md| Performance deep-dive →skill-performance.md| API testing →skill-testing.md
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

