Basic authentication for Business Central APIs was deprecated some time ago, and OAuth 2.0 via Microsoft Entra ID (formerly Azure AD) is now the standard path for any integration. It's not complicated once you've done it, but there are a handful of steps that are easy to get wrong on the first attempt — and they fail with error messages that don't always point at the actual problem.
Decide which OAuth flow you need
Two patterns cover most integration scenarios:
- Client credentials flow (app-only, S2S) — no user is present; a service authenticates as itself. This is what you want for background jobs, scheduled syncs, and server-to-server integrations.
- Authorization code flow (delegated, on behalf of a user) — a real user logs in and grants consent; the integration acts as that user. This is what you want when actions should be attributed to a specific person, or when you need access limited to what that user can see.
Get this decision right first — retrofitting a client-credentials integration into a delegated one (or vice versa) usually means redoing the app registration from scratch.
Step 1: Register the application in Microsoft Entra ID
In the Azure/Entra portal:
- Go to App registrations → New registration
- Give it a clear name — you'll thank yourself later when you have a dozen of these
- Choose the supported account type appropriate to your tenant setup (usually "Accounts in this organizational directory only" for a single-tenant integration)
- Note the Application (client) ID and Directory (tenant) ID — you'll need both
Step 2: Create a client secret or certificate
Under Certificates & secrets, create a new client secret. Two things people get wrong here:
- Expiry. Secrets have a maximum lifetime (commonly up to 24 months). Set a calendar reminder before it expires, or your integration will fail suddenly with an authentication error that looks unrelated to expiry.
- Copy it immediately. The secret value is only shown once. If you navigate away without copying it, you have to generate a new one.
For production integrations with higher security requirements, consider a certificate instead of a secret — it doesn't need rotation on the same short cycle and isn't a plaintext credential sitting in configuration.
Step 3: Grant the right API permissions
Under API permissions, add the Dynamics 365 Business Central API permission — typically Dynamics 365 Business Central → Application permissions → API.ReadWrite.All for app-only access, or the delegated equivalent for user-context access.
This step is the one that most commonly gets missed: after adding the permission, you must click Grant admin consent. Without this, the permission shows as "added" but isn't actually active — the integration will authenticate successfully but get 403 errors calling the API, which is a confusing failure mode if you don't know to check this.
Step 4: Enable the app in Business Central
Inside Business Central, you generally don't need to do anything extra for API.ReadWrite.All app-only access — Entra ID handles the authentication boundary. But if your integration needs to act as a specific user (delegated flow), that user needs an active BC license and the appropriate permission set assigned, same as any normal user.
Step 5: Request the token
For the client credentials flow, the token request looks like:
POST https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={client_id}
&client_secret={client_secret}
&scope=https://api.businesscentral.dynamics.com/.default
&grant_type=client_credentialsThe response gives you a bearer token, valid for roughly an hour. Cache it and reuse it until it's close to expiry rather than requesting a fresh token on every API call — this matters both for performance and because Entra ID will throttle excessive token requests.
Step 6: Call the BC API with the token
GET https://api.businesscentral.dynamics.com/v2.0/{tenant_id}/{environment}/api/v2.0/companies
Authorization: Bearer {access_token}If this returns a 401, recheck the token audience and scope. If it returns a 403, that's almost always the missing admin consent step above.
Common failure modes and what they actually mean
- AADSTS700016 (application not found) — usually a wrong client ID or tenant ID, or the app registration is in a different tenant than you're requesting the token from.
- 401 on the BC API call — token scope is wrong, or the token has expired.
- 403 on the BC API call — permission was added but admin consent wasn't granted, or the permission granted doesn't match what the endpoint requires.
- Works in Postman, fails in code — almost always a difference in how the scope or content-type is being sent; compare the raw HTTP request byte for byte.
A note on webhooks
If your integration needs real-time updates rather than polling, BC supports webhook subscriptions on top of the same OAuth setup. The subscription itself needs to be created via an authenticated API call, and BC will send a validation request to your endpoint that you need to echo back correctly before the subscription becomes active — a step that's easy to miss if you're building the receiving endpoint for the first time.
The takeaway
OAuth 2.0 setup for BC is mostly configuration, not code — but each step has a specific failure mode if skipped, and the error messages don't always point directly at the missing step. Working through the checklist above in order avoids nearly all of the common first-integration headaches.
Need help with this on your project?
We do this kind of work daily. Tell us what you're facing and we'll give you an honest read on effort and approach.
Start a project →