Skip to main content

Authentication

BI Endpoints uses the standard Business Central OAuth 2.0 model: tokens are issued by Microsoft Entra ID to the app registration described in App registration and permissions, and sent as a bearer token with every request.

Request a token​

POST https://login.microsoftonline.com/{domain}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={clientId}
&client_secret={clientSecret}
&scope=https%3A%2F%2Fapi.businesscentral.dynamics.com%2F.default
&grant_type=client_credentials

{domain} is the tenant's primary domain (for example contoso.onmicrosoft.com) or the tenant ID.

{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}

Send the access_token on every BI Endpoints request:

GET https://api.businesscentral.dynamics.com/v2.0/{tenantId}/{environment}/api/datawareness/endpoints/v1.0/companies({companyId})/glEntries
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...

Code samples​

curl

curl -X POST "https://login.microsoftonline.com/$DOMAIN/oauth2/v2.0/token" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "client_secret=$CLIENT_SECRET" \
--data-urlencode "scope=https://api.businesscentral.dynamics.com/.default" \
--data-urlencode "grant_type=client_credentials"

PowerShell

$body = @{
client_id = $env:BC_CLIENT_ID
client_secret = $env:BC_CLIENT_SECRET
scope = "https://api.businesscentral.dynamics.com/.default"
grant_type = "client_credentials"
}
$token = Invoke-RestMethod -Method Post `
-Uri "https://login.microsoftonline.com/$env:BC_DOMAIN/oauth2/v2.0/token" `
-ContentType "application/x-www-form-urlencoded" `
-Body $body

$accessToken = $token.access_token

Python

import requests

token = requests.post(
f"https://login.microsoftonline.com/{domain}/oauth2/v2.0/token",
data={
"client_id": client_id,
"client_secret": client_secret,
"scope": "https://api.businesscentral.dynamics.com/.default",
"grant_type": "client_credentials",
},
).json()["access_token"]

C# (MSAL)

using Microsoft.Identity.Client;

var app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.Build();

var result = await app
.AcquireTokenForClient(new[] { "https://api.businesscentral.dynamics.com/.default" })
.ExecuteAsync();

Token lifetime​

  • A token is valid for about an hour (expires_in, in seconds).
  • Reuse it until shortly before it expires, then request a new one. MSAL libraries cache tokens for you.
  • Don't request a token per call; Entra ID throttles that.

Postman​

The Postman collection's General > Token request does the same with the clientId, clientSecret and domain environment variables. See Quickstart with Postman.

When it fails​

AnswerCheck
401 on every callThe app registration is enabled on Microsoft Entra Applications in Business Central, admin consent is granted for API.ReadWrite.All, and the token is sent as Authorization: Bearer ….
401 after a whileThe token expired. Request a new one.
400 invalid_client from the token endpointThe client secret is wrong or has expired. Create a new one.
403The DTW BI ENDPOINTS permission set is assigned to the app in Business Central, and the company ID is correct.

More in Troubleshooting.

Security​

  • Store client secrets in a vault — Azure Key Vault, your pipeline's secret store — never in source control or a shared notebook.
  • Use a separate app registration per environment.
  • Grant only the permission sets you need; see Permissions.