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
| Answer | Check |
|---|---|
401 on every call | The 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 while | The token expired. Request a new one. |
400 invalid_client from the token endpoint | The client secret is wrong or has expired. Create a new one. |
403 | The 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.