To call Business Central APIs without a signed-in user, register an application in Microsoft Entra ID, give it the API.ReadWrite.All application permission on Dynamics 365 Business Central with admin consent, and add its client ID on the Microsoft Entra Applications page in each Business Central environment with the permission sets it needs. Your integration then requests a token with the client credentials grant and the scope https://api.businesscentral.dynamics.com/.default, and sends it as a bearer token. Most 401 Unauthorized errors come from a step being missed on one side: no admin consent, the app not added or not enabled in that environment, or a token issued for the wrong resource.
Who this is for and what you will have at the end
This guide is for developers and administrators who build unattended integrations with Business Central online: middleware, scheduled jobs, Azure Functions, data platforms and partner products that read or write through the REST API, custom APIs or web services.
At the end you will have:
- A Microsoft Entra app registration with a client secret or certificate and consented Business Central application permissions.
- The application set up in Business Central with least-privilege permission sets.
- A working token request and API call in PowerShell and curl.
- A troubleshooting map from
AADSTSerrors and Business Central401responses to their causes.
Service-to-service or user impersonation
Business Central supports two OAuth models for web services:
| Area | User impersonation (delegated) | Service-to-service (application) |
|---|---|---|
| Sign-in | Requires an interactive user | No interactive sign-in |
| License | User must be licensed | Application account in Business Central, no license needed |
| Refresh token | Used to keep access after first sign-in | Not needed; request a new token when it expires |
| Permissions | The user's permissions | Permissions assigned to the application account |
Service-to-service (S2S) authentication uses the OAuth 2.0 client credentials flow. Delegated flows can be subject to multifactor authentication, which blocks unattended integrations because MFA is required to get the token. The old Resource Owner Password Credentials flow isn't supported in Business Central online since version 23, and web service access keys stopped working online in October 2022, so S2S is the model for anything that runs on a schedule. S2S is available online from version 18.3 for API v2.0, custom APIs and web services, and from version 17.0 for the automation APIs.
Prerequisites
- A Microsoft Entra account that can register applications and grant tenant-wide admin consent.
- A Business Central user in each target environment who can open the Microsoft Entra Applications page and assign permission sets.
- The tenant ID and environment names you will call, for example
productionandsandbox. - A place to store the secret or certificate, such as Azure Key Vault or your platform's secret store.
Step 1: Register the application in Microsoft Entra ID
- Sign in to the Microsoft Entra admin center and register a new application.
- Give it a unique name that identifies the integration, for example
bc-integration-crm-sync. One registration per integration makes auditing and throttling easier to reason about. - Under Supported account types, choose Accounts in this organizational directory only for your own integration, or the multitenant option if you're building a product that many customers consent to.
- Leave Redirect URI empty unless you want to grant consent from inside Business Central in Step 3; that option needs a Web redirect URI.
- From the Overview page, copy the Application (client) ID and Directory (tenant) ID.
Add a credential
Select Certificates & secrets > New client secret, add a description, choose a duration and select Add. Copy the secret value immediately; it isn't shown again after you leave the page.
For a higher level of assurance, Microsoft Entra ID also accepts a certificate, or a federated credential, instead of a shared secret. With a certificate, the token request replaces client_secret with client_assertion_type set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer and a client_assertion JWT signed by the certificate. Whichever you choose, never put the credential in source code.
Step 2: Grant Business Central application permissions
- Select API permissions > Add a permission > Microsoft APIs.
- Select Dynamics 365 Business Central.
- Select Application permissions, then select the permissions you need and Add permissions:
| Permission | Type | Use it for |
|---|---|---|
API.ReadWrite.All | Application | API v2.0, custom APIs, OData and SOAP web services |
Automation.ReadWrite.All | Application | Automation APIs under /api/microsoft/automation, for example company setup |
- Select Grant admin consent for your tenant. Application permissions only take effect after an administrator consents; there's no user to consent in this flow.
Only add Automation.ReadWrite.All if the integration really uses the automation APIs. A data integration needs API.ReadWrite.All only.
Step 3: Set up the application in Business Central
The token alone doesn't grant data access. Business Central needs an application account with permissions.
- In Business Central, search for Microsoft Entra Applications and open the page.
- Select New to open the Microsoft Entra Application Card.
- In Client ID, enter the application (client) ID from Step 1.
- Fill in Description. If a partner set up the application, include enough partner-identifying information to trace it later.
- Set State to Enabled.
- Assign permission sets. Applications can't be assigned SUPER. Give the account only what the integration needs; for automation scenarios Microsoft points to the D365 AUTOMATION and EXTEN. MGT. - ADMIN permission sets.
- If you didn't grant admin consent in the Microsoft Entra admin center, select Grant Consent and follow the wizard. This only works if you configured a redirect URI on the app registration.
Do this in every environment the integration calls. A common cause of an integration that works in the sandbox but fails in production is that the application account, or its permissions, exists in only one of them.
Step 4: Request a token and call the API
The token endpoint is https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token and the scope is https://api.businesscentral.dynamics.com/.default. The .default suffix tells Microsoft Entra ID to issue a token containing every application permission already granted to the app for that resource.
PowerShell
$tenantId = "<tenant-id>"
$clientId = "<application-client-id>"
$clientSecret = "<client-secret>" # load from a secret store in real code
$environment = "production"
$tokenResponse = Invoke-RestMethod -Method Post `
-Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" `
-ContentType "application/x-www-form-urlencoded" `
-Body @{
grant_type = "client_credentials"
client_id = $clientId
client_secret = $clientSecret
scope = "https://api.businesscentral.dynamics.com/.default"
}
$headers = @{ Authorization = "Bearer $($tokenResponse.access_token)" }
$baseUrl = "https://api.businesscentral.dynamics.com/v2.0/$tenantId/$environment/api/v2.0"
$companies = Invoke-RestMethod -Uri "$baseUrl/companies" -Headers $headers
$companies.value | Select-Object id, name
$companyId = $companies.value[0].id
(Invoke-RestMethod -Uri "$baseUrl/companies($companyId)/customers?`$top=5" -Headers $headers).value |
Select-Object number, displayNamecurl
TOKEN=$(curl -s -X POST "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
--data-urlencode "client_secret=$CLIENT_SECRET" \
-d "scope=https://api.businesscentral.dynamics.com/.default" | jq -r .access_token)
curl -s -H "Authorization: Bearer $TOKEN" \
"https://api.businesscentral.dynamics.com/v2.0/$TENANT_ID/production/api/v2.0/companies"The client secret must be URL-encoded in the request body, which is why the curl example uses --data-urlencode for it.
A successful token response contains token_type, expires_in and access_token. Access tokens are short-lived (one hour by default), and the client credentials flow never returns a refresh token: when the token expires, request a new one. Use a token cache, such as the one in the Microsoft Authentication Library (MSAL), rather than requesting a token per call.
The same token works for custom APIs (/api/{publisher}/{group}/{version}) and for OData web services. For Business Central on-premises configured for Microsoft Entra authentication with OpenID Connect, the server's ValidAudiences setting must include https://api.businesscentral.dynamics.com.
Verify the setup
- Token claims. Decode a test token in a local tool. For an app-only token, the
rolesclaim lists the granted application permissions and should includeAPI.ReadWrite.All. Theaudclaim identifies the resource the token was issued for and must be Business Central, not Microsoft Graph. Don't build production logic on reading tokens for APIs you don't own; this is a debugging step. - Companies call.
GET .../api/v2.0/companiesreturns the companies in the environment. - Write test. In a sandbox, create and then delete a test record to confirm the permission sets allow writes.
- Telemetry. Incoming calls appear as RT0008 web service events with category
API, which lets you confirm the integration's traffic and error rates.
Troubleshooting
Errors from the token endpoint
These come back as 400 Bad Request with an error and an error_description that starts with an AADSTS code.
| Error | Meaning | Fix |
|---|---|---|
AADSTS7000215 | Invalid client secret is provided | The secret in the request doesn't match a valid secret on the app. Check that you stored the secret's value rather than its ID, or create a new secret |
AADSTS7000222 | The provided client secret keys are expired | Create a new secret or move to a certificate, then update your secret store |
AADSTS700016 | The application wasn't found in the directory/tenant | Wrong client ID, or the request went to the wrong tenant. Check both values |
AADSTS90002 | The tenant name wasn't found | Wrong tenant ID or domain in the token URL |
AADSTS70011 (invalid_scope) | The scope requested is invalid | Use exactly https://api.businesscentral.dynamics.com/.default; all scopes in one request must be for a single resource |
AADSTS500011 | The resource principal wasn't found in the tenant | The Business Central resource isn't available in the tenant you requested a token from. Confirm the tenant is the one that owns the Business Central environment |
AADSTS700027 | Client assertion failed signature validation | The certificate used to sign the assertion doesn't match the one uploaded to the app registration |
401 from Business Central
Business Central returns error code Authentication_InvalidCredentials with the message The server has rejected the client credentials when it doesn't accept the token. Work through these checks in order:
- Audience. The token was requested with the Business Central
.defaultscope, not a Microsoft Graph or other scope. - Consent. The
rolesclaim containsAPI.ReadWrite.All. If it's missing, admin consent wasn't granted; grant it and request a new token. - Application account. The client ID is on the Microsoft Entra Applications page of the environment in the URL, and State is Enabled.
- Tenant and environment. The tenant ID or domain in the API URL is the tenant that issued the token, and the environment name is spelled correctly.
- On-premises only.
ValidAudiencesincludeshttps://api.businesscentral.dynamics.com.
Other errors after authentication
Authorization_*codes or permission errors. The application account authenticated but lacks permissions for the object or data. Add the missing permission set on the application card; don't reach for broad sets.- Works in sandbox, fails in production. Compare the application account, its state and its permission sets between the two environments.
429 Too Many Requests. Operational limits apply to service principals exactly as they do to users, for example 6,000 OData requests per user in a 5-minute sliding window. Add retry with back-off, and spread heavy workloads across several applications if one hits the per-user limits.
Security checklist
- One app registration per integration, named so its owner is obvious.
- Certificate or federated credential preferred over a client secret; if you use a secret, record its expiry and rotate it before
AADSTS7000222stops the integration. - Only
API.ReadWrite.Allunless automation APIs are needed, with admin consent recorded. - Application account enabled only in the environments it needs, with least-privilege permission sets and never SUPER.
- Tokens cached and never logged; in AL code that handles tokens, mark the procedure
[NonDebuggable]. - Disable the application card in Business Central, or remove the credential in Microsoft Entra ID, as soon as an integration is retired.
The same identity-first approach applies beyond Business Central; see zero trust enterprise remote access architecture. Once authentication works, the next steps are usually moving off SOAP and replacing polling with webhooks.
References
- Using service-to-service authentication
- Using OAuth to authenticate Business Central web services
- OAuth 2.0 client credentials flow on the Microsoft identity platform
- Microsoft Entra authentication and authorization error codes
- Access token claims reference
- API endpoint structure
- Endpoints for the APIs for Business Central
- Troubleshooting REST API/OData calls
- Troubleshooting web service errors
- Deprecated features in the client, server, database
- Operational limits in Business Central online