AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Skill Api

skill-louage-frw-agentic-coding-skill-api · by Louage

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.

No reviews yet
0 installs
32 views
0.0% view→install

Install

$ agentstack add skill-louage-frw-agentic-coding-skill-api

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-louage-frw-agentic-coding-skill-api)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Skill Api? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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 use SystemId for stable, immutable keys
  • DelayedInsert = 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, not Customer_Number
  • Editable = false on 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/json

Pattern 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.

/// 
/// Evaluates whether the customer qualifies as VIP based on sales volume
/// and persists the result on Customer."VIP Customer".
/// 
/// The customer number to evaluate. Exits silently if blank or not found.
procedure EvaluateCustomer(CustomerNo: Code[20])
begin
    // ...
end;
  • `` (required), what the procedure does and why a caller would invoke it.
  • `` (required for each non-trivial parameter), what value to pass and constraints.
  • `` (required when there is a return value), what the value means.
  • local and internal procedures: 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:

  1. Resource model, entities, relationships, navigation properties
  2. Operations, which HTTP methods per resource (GET/POST/PATCH/DELETE)
  3. Actions, custom operations (bound: per entity, unbound: global)
  4. Filtering, which fields consumers can $filter on (add corresponding keys)
  5. Versioning, initial version and deprecation plan
  6. Authentication, OAuth 2.0 scope, permission sets needed

Document in specs/Plans/{req_name}.architecture.md or a dedicated API design section. PAUSE, wait for user approval before implementing.

Step 2: Implement API Pages

  1. Create header API page (Pattern 1)
  2. Create subpage(s) for lines/children (Pattern 2)
  3. Add bound actions for entity operations (Pattern 3)
  4. Add unbound actions if needed (Pattern 4)
  5. Add error handling triggers (Pattern 7)
  6. 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 $deltatoken returns 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, $expand query 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

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 beta for previewing changes, then promote
  • Do NOT skip error handling, OnInsertRecord, OnModifyRecord, OnDeleteRecord must validate
  • Permission set hierarchy → skill-permissions.md | Performance deep-dive → skill-performance.md | API testing → skill-testing.md

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.