AgentStack
SKILL verified MIT Self-run

Creating Bc Custom Api

skill-emgreppi-business-central-ai-skill-creating-bc-custom-api · by emgreppi

Creates custom API pages (CRUD) and API queries (read-only joins) in AL for Business Central. Use when exposing custom tables, adding business logic to APIs, creating reporting endpoints, or joining multiple tables for external consumption.

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

Install

$ agentstack add skill-emgreppi-business-central-ai-skill-creating-bc-custom-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.

Are you the author of Creating Bc Custom Api? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Skill: Creating BC Custom APIs

Key Concept

| Type | Object | Operations | Use Case | |------|--------|------------|----------| | API Page | PageType = API | CRUD (Read-Write) | Expose single table | | API Query | QueryType = API | Read-only | Join multiple tables |

For consuming Microsoft standard APIs, use al-standard-api skill instead.

Validation Gates

  1. After Step 2: API page compiles, GET returns 200 with fields
  2. After Step 3: Subpage works, navigation property expands correctly
  3. Final: All CRUD operations work (GET 200, POST 201, PATCH 200, DELETE 204)

Note: OData UI endpoints deprecated BC30 (2027). Always use API pages.

Placeholder Reference

| Placeholder | Description | Example | |-------------|-------------|---------| | ` | Object ID from app.json idRanges | 50100 | | | Company prefix from app.json | SL, ABC | | | Source table name | "Rental Machine" | | | Singular entity name (camelCase) | rentalMachine | | | Plural entity name (camelCase) | rentalMachines | | | APIPublisher value | contoso | | | APIGroup value | rental | | | Primary key field | "No." | | , | Table fields to expose | "Description", "Status"` |

Procedure

Step 1: Decide API Type

Use API Page when:

  • Need CRUD operations (Create, Read, Update, Delete)
  • Exposing a single table
  • Need to execute business logic on insert/modify

Use API Query when:

  • Read-only access sufficient
  • Need to join multiple tables
  • Reporting/analytics endpoint

Step 2: Create API Page (CRUD)

page  " API "
{
    APIGroup = '';
    APIPublisher = '';
    APIVersion = 'v2.0';
    EntityName = '';
    EntitySetName = '';
    EntityCaption = '';
    EntitySetCaption = '';
    PageType = API;
    SourceTable = ;
    DelayedInsert = true;
    ODataKeyFields = SystemId;

    layout
    {
        area(Content)
        {
            repeater(General)
            {
                field(id; Rec.SystemId)
                {
                    Caption = 'ID';
                    Editable = false;
                }
                field(; Rec.)
                {
                    Caption = '';
                }
                field(; Rec.)
                {
                    Caption = '';
                }
                field(; Rec.)
                {
                    Caption = '';
                    Editable = false;  // Calculated fields
                }
                field(lastModifiedDateTime; Rec.SystemModifiedAt)
                {
                    Caption = 'Last Modified';
                    Editable = false;
                }
            }
        }
    }

    trigger OnInsertRecord(BelowxRec: Boolean): Boolean
    begin
        // Optional: Set default values or execute business logic
        Rec.Insert(true);
        exit(false);  // Return false to prevent double insert
    end;
}

Step 3: Add Navigation Property (Lines/Details)

For header-line relationships, add a part to the parent API page:

layout
{
    area(Content)
    {
        repeater(General)
        {
            // ... header fields ...
        }
        part(Lines; " API  Lines")
        {
            EntityName = 'Line';
            EntitySetName = 'Lines';
            SubPageLink =  = field();
        }
    }
}

Subpage (Lines):

page  " API  Lines"
{
    APIGroup = '';
    APIPublisher = '';
    APIVersion = 'v2.0';
    EntityName = 'Line';
    EntitySetName = 'Lines';
    PageType = API;
    SourceTable = ;
    DelayedInsert = true;
    ODataKeyFields = SystemId;

    layout
    {
        area(Content)
        {
            repeater(General)
            {
                field(id; Rec.SystemId) { Editable = false; }
                field(; Rec.) { }
                field(lineNo; Rec."Line No.") { }
                // ... other line fields ...
            }
        }
    }
}

Step 4: Create API Query (Read-Only Joins)

query  " API "
{
    QueryType = API;
    APIPublisher = '';
    APIGroup = '';
    APIVersion = 'v2.0';
    EntityName = '';
    EntitySetName = '';

    elements
    {
        dataitem(; )
        {
            column(id; SystemId) { }
            column(; ) { }
            column(; ) { }

            dataitem(; )
            {
                DataItemLink =  = .;
                SqlJoinType = InnerJoin;  // or LeftOuterJoin

                column(lineNo; "Line No.") { }
                column(; ) { }
                column(; ) { }
            }
        }
    }
}

Required Properties

| Property | Required | Description | |----------|----------|-------------| | APIPublisher | Yes | Your company/publisher name | | APIGroup | Yes | Logical grouping | | APIVersion | Yes | v2.0 recommended, beta for development | | EntityName | Yes | Singular, camelCase | | EntitySetName | Yes | Plural, camelCase | | ODataKeyFields | Yes | Always use SystemId | | PageType/QueryType | Yes | API | | DelayedInsert | Recommended | true for API pages |

API URL Structure

Custom API endpoint:

https://api.businesscentral.dynamics.com/v2.0///api////companies()/

Example:

https://api.businesscentral.dynamics.com/v2.0/contoso.com/Production/api/contoso/rental/v2.0/companies(xxx)/rentalMachines

Schema Version and OData Features (BC v24+)

Enum Values (schemaversion 2.0)

Starting BC v24, enum values are returned as XML-encoded names:

  • Return OrderReturn_x0020_Order

Add ?$schemaversion=1.0 for old behavior.

IN Operator (schemaversion 2.1)

?$schemaversion=2.1&$filter=status in ('Open', 'Released')

Without it → BadRequest_MethodNotImplemented.

Filter inside $expand

?$expand=lines($filter=type eq 'Item')

Parentheses required around nested options.

Multi-level Expand

?$expand=lines($expand=item($expand=category))

Best Practices

  1. Always use ODataKeyFields = SystemId - stable across renames
  2. Use EntityCaption/EntitySetCaption - for localization at /entityDefinitions
  3. camelCase for API names - rentalMachine not RentalMachine
  4. Alphanumeric only - no special characters in field names
  5. Version your APIs - v1.0, v2.0, or beta
  6. Include SystemModifiedAt - enables delta sync with $filter
  7. DelayedInsert = true - prevents partial record creation

CRUD Control

Make API read-only:

page  " API Read Only"
{
    PageType = API;
    InsertAllowed = false;
    ModifyAllowed = false;
    DeleteAllowed = false;
}

Troubleshooting

| Error | Cause | Fix | |-------|-------|-----| | 400 | Invalid field name | Check camelCase, no spaces | | 404 | Entity not found | Verify ODataKeyFields = SystemId, entity names | | 500 | Server error | Check OnInsertRecord trigger, BC event log | | API not visible | Not published | Restart service tier, check compilation |

Feedback loop: Fix issue → Recompile → Test GET first → Then test POST/PATCH/DELETE.

References

See references/ folder for:

  • api-page-template.al - Complete AL template for API Page (CRUD), Subpage (Lines), and Read-Only API
  • api-query-template.al - Complete AL template for API Query with join examples

External Documentation

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.