AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
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
36 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.

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-emgreppi-business-central-ai-skill-creating-bc-custom-api)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
7mo 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 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.