Simple Object Designer Documentation
Index

Consuming APIs

This page explains how to use the APIs you've created with SOD from external applications and tools.

API Access Prerequisites

Before consuming APIs:

  1. Published Extension - the app must be published and installed. An app that shows as installed with symbols updated but was never successfully published will produce 404 errors from every endpoint.
  2. Authentication - Azure AD/OAuth2 credentials
  3. Permissions - the calling identity needs Business Central permissions, in two separate layers (see below)
  4. Network Access - Outbound HTTPS to BC

Exposing custom fields to external apps

A custom field you add to a standard table is a customization, and external apps cannot see it automatically — it is not part of the standard API surface the external app was built against.

The supported route is to create a new API endpoint that exposes the custom field. There is no way to inject a custom field into an existing standard API endpoint so that an unmodified external app picks it up.

Whether this works therefore depends on the external app: it must be capable of being pointed at an additional or different API endpoint. That is a limitation on the external app's side, not something SOD controls.

"Can I add the field to Master Data instead?" No. Placing a custom field into a Master Data structure does not make it accessible to an external app reading Business Central through an API. A new API endpoint is still required.

API Endpoint URL

URL Structure

https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/api/hougaard/{objectPrefix}/v2.0/companies({companyId})/{entitySetName}?Company={CompanyName}

Components

Component Example
Base https://api.businesscentral.dynamics.com/v2.0
Tenant Tenant GUID or domain
Environment production or sandbox
API Publisher segment hougaard (fixed)
Object Prefix The prefix you configured in SOD Setup (e.g. SOD)
API Version segment v2.0 (fixed)
Company ID GUID of the company
Entity Set customEquipments
Company query ?Company={CompanyName} — required

Finding Values

Tenant: Your Azure AD tenant ID or domain

Environment:

  • Check BC admin center
  • Or use environment name in BC

Company ID:

  • Query companies endpoint first
  • Or find in BC URL when logged in

Finding the URL in production when SOD is not installed there

If you deployed your customization to production by uploading the .app file, SOD itself is not present in that environment, so the API URLs are not displayed anywhere there.

Take the URL shown in the sandbox and replace the environment segment with the target environment name (usually production). Everything else — the hougaard publisher segment, your object prefix, the entity set name and the API version segment — is identical for the same deployed app.

Authentication

OAuth 2.0

BC APIs require OAuth 2.0 authentication:

  1. Register application in Azure AD
  2. Grant API permissions for BC
  3. Obtain access token
  4. Include token in API requests

Two separate permission layers

This is the most common cause of "authentication works but the call fails". Azure AD permissions and Business Central permissions are different layers, and you need both.

Layer What it grants How to assign
Azure AD / Entra API permission Access at the API layer — Dynamics 365 Business Central > API.ReadWrite.All, or something more granular Azure AD app registration
Business Central permission set Access to the actual tables and page objects the API touches The Microsoft Entra Apps page inside Business Central

API.ReadWrite.All alone does not let an Entra application read or write your tables.

What a service-to-service caller needs

For an Entra (Azure AD) application consuming SOD-generated APIs:

  1. Assign the permission set that ships with your SOD-published app to the Entra application. Without it, the app can authenticate, and may even succeed against standard tables and other custom APIs, while failing on tables it lacks rights to.
  2. Grant permission to the generated API page objects, not just data. A data-level permission set such as "SUPER (Data)" is not sufficient — the identity also needs execute permission on the API page objects SOD generates. A single endpoint can map to more than one page object, so several page permissions may be needed.

The page object number depends on your Object Prefix and Start Object Number, so read it from your own setup rather than assuming a fixed number.

Interactive BC users usually have Page 0 (all pages) access, which is why they never hit this error; dedicated service principals typically do not.

  1. Business logic APIs need this too. A method such as "Release Sales Document" executes BC logic touching many tables, so the caller needs those table permissions through the app's permission set — Azure AD permissions are not enough. See Expose Codeunits.

Company-scoped permissions

Business Central permissions — including those assigned to an Entra App — can be scoped to specific companies. If the permission exists only for a company other than the one the call targets, the call fails with "the current permissions prevented the action" even though the permission looks present.

Assign permissions for the specific company being accessed, or for all companies. The company being accessed comes from the Company ID in the URL path and the ?Company={CompanyName} query parameter.

Token Header

Authorization: Bearer {access_token}

Common Operations

GET - List Records

Retrieve all records:

GET /api/hougaard/SOD/v2.0/companies({id})/customEquipments?Company={CompanyName}

Response:

{
  "value": [
    { "id": "...", "code": "EQ001", "name": "Laptop" },
    { "id": "...", "code": "EQ002", "name": "Phone" }
  ]
}

GET - Single Record

Retrieve one record by key:

GET /api/hougaard/SOD/v2.0/companies({id})/customEquipments({recordId})?Company={CompanyName}

POST - Create Record

Insert new record:

POST /api/hougaard/SOD/v2.0/companies({id})/customEquipments?Company={CompanyName}
Content-Type: application/json

{
  "code": "EQ003",
  "name": "Tablet",
  "category": "Computing"
}

PATCH - Update Record

Update existing record:

PATCH /api/hougaard/SOD/v2.0/companies({id})/customEquipments({recordId})?Company={CompanyName}
Content-Type: application/json

{
  "name": "Updated Name"
}

DELETE - Remove Record

Delete a record:

DELETE /api/hougaard/SOD/v2.0/companies({id})/customEquipments({recordId})?Company={CompanyName}

OData Query Options

$select

Return specific fields only:

/customEquipments?$select=code,name

$filter

Filter records:

/customEquipments?$filter=category eq 'Computing'
/customEquipments?$filter=amount gt 100
/customEquipments?$filter=startswith(name,'Laptop')

$orderby

Sort results:

/customEquipments?$orderby=name asc
/customEquipments?$orderby=amount desc

$top and $skip

Pagination:

/customEquipments?$top=10
/customEquipments?$top=10&$skip=20

About @odata.nextLink: consumers often expect a next-link to follow to the following page. It is not returned in every scenario — in particular, when an entity set is called with $top limiting the row count, Business Central does not add a continuation link. This is standard Business Central OData platform behaviour, not something specific to SOD-generated endpoints, and it applies to SaaS as well. See Microsoft Learn, "Use OData to Retrieve a Set with the Top Number of Rows".

$count

Get record count:

/customEquipments?$count=true

$expand — not supported

Although $expand is a standard OData option, SOD-generated APIs do not declare navigation properties, so $expand and deep insert are not available.

To work with related parent and child records, call the parent endpoint and the child endpoint separately and join them on the client using their key fields.

Record Limits and Paging

Business Central serves a maximum of approximately 20,000 records in a single API call. This is a Business Central-wide limit applying to all APIs. SOD neither imposes nor can override it.

Beyond that limit, records are simply not in the response — no error is raised. The typical symptom is a consumer showing older data and appearing to miss the most recently added rows, even though those rows are plainly visible in the Business Central table view.

To retrieve everything, use OData server-driven paging: follow the @odata.nextLink returned in the response and keep fetching until no next-link remains. This applies equally to Power Query, Power BI, Excel and any other client. See Microsoft's documentation on server-driven paging in OData web services for Business Central.

Enum Values and the x0020 Encoding

Symptom: an API exposing an enum or option field — Document Type, for example — returns values with encoded spaces, such as Recurring_x0020_Invoice instead of Recurring Invoice. Downstream consumers then reject the value:

ErrorCode: Application_InvalidOptionEnumValue
'Recurring_x0020_Invoice' is not an option. The existing options are:
Quote,Order,Invoice,Credit Memo,Blanket Order,Return Order,Recurring Invoice

Cause: a Microsoft change in how enum and option values are represented in API output. The underlying Business Central data has not changed, and this can appear after a BC version upgrade even though you did not touch the query.

An explanation video is available at https://youtu.be/600q1uTMIuk.

Solution: SOD provides a helper on the API card called Add Enum Companion Text Field. It adds an extra field carrying the old, unencoded value, which the downstream flow can consume instead.

  1. Open the API card in SOD
  2. Find Add Enum Companion Text Field
  3. Use it to add the companion field for the affected enum field

If that function is not present on your API card, update SOD — see Updating SOD.

Consuming from Power Platform

Power BI

Use the Business Central connector, not the OData feed connector.

  • Do not connect by pasting the API URL into Get Data > OData feed. This can produce metadata and connection-resolution errors even when the API is correctly configured and full of data.
  • Do use Get Data > Online Services > Dynamics 365 Business Central, navigate to the environment, and pick the API from the list.

This has been observed across multiple customers: the same API that fails via the OData URL works through the Business Central online service connector.

New fields do not appear automatically. When you publish additional fields to an API, an existing Power BI report will not pick them up on a data refresh — refreshing updates row values, it does not add columns. Adding the new columns is a Power BI-side operation on the existing query. The fields are correctly exposed once published; Hougaard support does not provide Power BI-specific configuration guidance, so consult Power BI documentation for the exact steps.

Parent/child data. Because SOD APIs have no nested sub-tables or $expand, create two endpoints — one for the header table, one for the lines table — load both in Power BI, and relate them on their shared key. (When the standard BC Power BI connector shows nested table columns, those come from Microsoft's hand-coded v2.0 endpoints, not from SOD.)

New queries or APIs not showing in Power BI

Older objects work, newly created ones never appear, although the app published successfully:

  1. Verify the app is installed. In Extension Management, confirm your generated customer app — not the Simple Object Designer app — is present and Installed.
  2. Verify the object is in the published app. Confirm the new API or query is actually part of the latest published version.
  3. Isolate Power BI from Business Central. Test the API from another client, such as Excel using the Dynamics 365 online connector with the API URL shown on the SOD API page. Note that Excel may authenticate successfully and still return no table data — that alone does not prove the API is broken.
  4. Suspect Power BI Desktop caching. If the app is installed, the objects are confirmed present, and the API is reachable from another client, the cause is often client-side caching in the Power BI Desktop installation. In one reported case the same objects were invisible on one machine and appeared correctly under the publisher's folder from a different Power BI Desktop installation with no settings changed. Try another machine, or clear/refresh the Power BI Desktop cache so it re-reads the available Advanced APIs.

The Get Data window layout can differ between machines on the same Power BI version — that is expected and not the cause.

Power Apps

  1. Add Data > Business Central connector
  2. Or use Custom connector with API URL
  3. Select table/entity
  4. Build app screens

If Power Apps keeps showing an old table state after you change the schema, that is usually consumer-side schema caching. Verify by calling the endpoint with an authenticated tool — see Confirming whether stale data is the API or the client.

Power Automate

  1. Add Business Central connector
  2. Select action (Get records, Create record, etc.)
  3. Configure entity and parameters
  4. Build flow logic

If your endpoints do not appear in Power Automate, check that the app published without errors. Exposure configuration can be perfectly correct, but a failed or partial publish means the endpoints never exist. This is why the same configuration works in one sandbox and not another — suspect environment-specific publishing errors rather than a configuration mistake.

Two publishing errors that commonly block exposure:

  • Dependency name or publisher changed — "A package that satisfies the dependency on app with ID <id> … was found, but the name or publisher has changed." Update the dependency reference to the new name/publisher.
  • Runtime reference version mismatch — "The referenced module <module> (<version>) with runtime reference version <X.Y> cannot be loaded by the compiler with version <X.Y>." The module was built against a newer runtime than the compiler supports; this must be resolved before the app publishes cleanly.

"For a selected record (V3)" and the Automate menu. APIs created with SOD are technically identical to APIs created any other way — nothing about them excludes them from Power Automate. When SOD-created APIs or their flows do not appear under the Automate menu on a document page, the usual culprit is the new Power Automate Designer, which changes the syntax of the "For a selected record (V3)" connector incorrectly. Turn the new Designer off when building flows with that trigger.

It is not fully documented which requirements an API must meet to appear under each Business Central menu group. One suspected requirement is that some groups need a specifically styled primary key — treat this as an area of uncertainty rather than a rule.

Run Actions cannot be exposed. Run Actions in SOD are manual, user-invoked actions. There is no feature to expose one so it can be called from or populate a Power Automate flow, and no video demonstrating it exists. Building an unattended, scheduled, multi-step process — detect a file, validate, route errors, create records, run a follow-up step — is beyond the "Simple" scope and requires AL development. See Limitations.

Creating project data. You do not need a codeunit to create project task lines and project planning lines from Power Automate. Expose the Job Task table and the Job Planning Line table as APIs and post records to them directly. ("Project task lines" are the Job Task table; "project planning lines" are the Job Planning Line table.) Exposing a table as an API is the supported way to let an external tool create records in it.

Consuming from Excel

Using Get & Transform

  1. Data > Get Data > From Other Sources > From OData Feed
  2. Enter API URL
  3. Configure authentication
  4. Select tables
  5. Load to worksheet

Power Query

  1. Data > Get Data > From Web
  2. Enter full API URL
  3. Transform data as needed
  4. Load to Excel

Known issues with Excel

There are known issues consuming SOD-published APIs in Excel through OData Data Queries. The same APIs frequently work correctly in Power BI through the Business Central connector.

If an API will not come into Excel via a Data Query, test it in another client first — Power BI with the BC connector, or Postman — to establish whether the API itself is working before treating it as an API configuration problem.

Exporting query data to Excel

Symptom: a Query built in SOD is opened as a list page and exported with Open in Excel; the export contains extra columns at the end, columns whose data is shifted relative to their headers, or a duplicated field (for example Posting Date twice, when two source tables both contain it, though it was selected once).

Status: this has been reproduced on a brand-new query with a single table and no filters, and rebuilding the query under a different name does not help. The current assessment is that this is a bug in Business Central's Open in Excel feature when used on a query displayed on screen, rather than in SOD's export logic.

Try first: if the query defines columns used for filtering, remove them. Some consumer and export tools do not interpret filter columns correctly, and they can surface as extra columns in the export.

Alternative: publish the query as an API and point Excel at the query's API endpoint instead of using Open in Excel from the list page.

Edit in Excel on document features

For a Document Table feature (header plus lines), the line section offers only Open in Excel — a read-only export — not Edit in Excel.

Edit in Excel relies on API pages carrying the correct header/line relationship metadata, which is not exposed for the dynamically generated line tables. Standard objects such as Sales Orders support it because Microsoft implemented dedicated API pages and the relationship plumbing for them.

A plain data table (no header/line structure) does offer Edit in Excel. Enabling it for the line section of custom Document Table features is not on the roadmap — it is beyond the intended "Simple" scope.

Downloading Attachment Files

Retrieving a DocumentReferenceId does not give you the file. Downloading an attachment is a two-step process:

  1. Get the attachment ID from the attachment table/API
  2. Use that attachment ID with the Business Central attachment GET API to download the file content

See Microsoft's reference: https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-reference/v2.0/api/dynamics_attachment_get

Attachments are not exposed as a sub-resource of every entity — they are not listed under the Items resource, for example — so you must query the attachment table/API to get the ID rather than expecting an attachments navigation from the parent record.

Error Handling

Common Error Codes

Code Meaning
400 Bad request (invalid data)
401 Unauthorized (authentication issue)
403 Forbidden (permission denied)
404 Not found (wrong URL or record)
500 Server error

Error Response Format

{
  "error": {
    "code": "BadRequest",
    "message": "Description of the error"
  }
}

Troubleshooting

Problem Check
401 errors Token valid? Permissions granted?
404 errors URL correct? Entity exists? Is the app published as well as installed?
400 errors Data format correct? Required fields present? Stray encoded characters in the URL?
403 errors User has BC permissions?
500 Internal_ServerError — "the current permissions prevented the action (TableData … Read: …)" Has the Entra application been assigned the app's permission set?
"the current permissions prevented the action (Page {number} API … Execute: …)" Does the identity have permission to the generated API page object, and for the right company?
Authenticates but returns a permissions error Grant page-level permission for the SOD-generated API pages; a data-only permission set is not enough
Not all records returned / newest rows missing Result set exceeds the ~20,000-record BC limit — implement server-driven paging via @odata.nextLink
Far fewer records than expected Are you querying a lookup/category table endpoint instead of the parent table?
Consumer shows an old schema Consumer-side caching — verify with an authenticated direct call

404 after an app change

The first thing to verify is that the app containing the API is both published and installed. An app can sit in an "installed, symbols updated" state and still have failed to publish — that is not the same as being published, and every endpoint returns 404 until it is.

400 Bad Request

A 400 means something is wrong with the request, not that the API has degraded — APIs do not deteriorate over time, and a recurring need to delete and recreate an endpoint is a sign of a malformed request rather than a corrupt endpoint.

Inspect the URL for stray or encoded control characters. Encoded whitespace such as %09 (a tab) embedded in the object prefix or entity segment — MyPrefix%09 instead of MyPrefix — produces a 400. Confirm the object prefix and entity set name match your SOD configuration exactly, with no leading or trailing whitespace.

Isolate the client: reproduce the request from a different client (Postman, curl). If it succeeds there, the problem is how the first client builds or encodes the URL, not the endpoint.

Fewer records than expected

If a consumer returns only a handful of records while many parent records exist, check which endpoint you are querying. It is easy to point at a lookup or category table endpoint — which contains only the distinct category values — instead of the parent table holding the business records. Query the parent table's endpoint and make sure the field you need is included in that API; create an API on the parent table if none exists.

Testing APIs

Using Postman

  1. Import BC collection or create requests
  2. Configure OAuth 2.0 authentication
  3. Test each operation
  4. Verify responses

A token that works in the Power Platform can still fail in a direct Postman call if the identity's Business Central permissions are insufficient or scoped to the wrong company.

Using a browser

Clicking the API endpoint link in a browser does not work. The browser is not authenticated against the Business Central API endpoint. This has never worked and is not a regression introduced by any SOD version.

For a browser to return anything, you must already have an authenticated Business Central session, and even then this is not a reliable test. Use Postman with OAuth 2.0 configured instead.

Confirming whether stale data is the API or the client

When a consumer keeps showing an old schema or old data after you changed the API:

  1. Call the endpoint directly with an authenticated tool — Postman, or an authenticated BC session. Do not use a plain browser click on the raw URL; it always fails regardless of schema state, so it proves nothing.
  2. If the authenticated call returns the updated schema, the problem is consumer-side caching.
  3. If the authenticated call returns the old state, the problem is on the SOD/API side.

Using curl

curl -H "Authorization: Bearer {token}" \
     https://api.businesscentral.dynamics.com/v2.0/.../customEquipments

Stale Dataverse Tables After Changing or Deleting APIs

When you change Object Prefix or Extension Name, or delete APIs and publish again, Dataverse may keep listing tables built from the old settings. You can end up with two tables that look identically named but carry different API routes — one with a route such as item/hougaard/bf/v2.0 alongside the current one.

These stale tables reflect what Dataverse cached and generated, not what Business Central currently exposes.

Confirm they are not live: open a tool that reads the exposed API directly — Power BI, or any OData client. Only currently live APIs appear there. Anything that does not show up in the live API view is a stale Dataverse artifact and can be ignored.

Symptoms you may see:

  • Making the stale table visible errors with Failed to create entity with logical name … An entity with the specified name … already exists for data source
  • Deleting it produces no error, but the table remains
  • Deleting it reports dependency errors referencing managed-solution objects owned by "Dynamics 365 Business Central" — a Business Central Configuration sitemap, a model-driven app, or an SDK message processing step such as GenerateVirtualEntityPlugin

Because these tables belong to Microsoft's Business Central Dataverse managed solution and its virtual entity generation, they cannot be freely deleted from the Dataverse side.

Before concluding it is a harmless artifact, rule out a genuine duplicate exposure:

  1. Verify the table is exposed on only a single API
  2. Check Extension Management to confirm you do not have two separate apps exposing the same API

If both checks pass, the extra table with the outdated route is a Dataverse-side artifact.

Rate Limiting

BC API Limits

Business Central has API limits:

Best Practices

  • Cache data where appropriate
  • Batch operations when possible
  • Implement retry logic
  • Respect rate limit headers
  • Implement @odata.nextLink paging for any dataset that could exceed 20,000 rows

Security Considerations

Access Control

  • Use dedicated service accounts
  • Grant minimum required permissions
  • Review API access regularly
  • Remember that permissions can be company-scoped — verify the scope matches the company being called

Data Protection

  • Use HTTPS only
  • Don't expose sensitive data via API
  • Log API access for audit

Token Management

  • Store tokens securely
  • Refresh tokens before expiry
  • Revoke tokens when not needed