If you're trying to manage expenses with the Microsoft Business Central API, you've probably realized it's surprisingly complex. Knowing whether to use a purchase invoice, a vendor bill, or an expense report endpoint is a common struggle for developers.
This guide focuses on the practical implementation of expense management through Business Central's REST APIs. We'll work with real examples, show you the exact JSON payloads you need, and then demonstrate how a unified API approach can eliminate most of the complexity.
Understanding Business Central's API Architecture
Business Central exposes its functionality through RESTful APIs using the OData protocol. Every API call follows a consistent URL pattern that developers need to understand before diving into expense management.
The base URL structure looks like this:
https://api.businesscentral.dynamics.com/v2.0/{tenant-id}/{environment}/api/v2.0/
For on-premises installations, the pattern changes slightly:
https://{server}:{port}/{instance}/api/v2.0/
Business Central comes with built-in APIs that require no configuration. These APIs handle standard business objects like customers, vendors, items, and, importantly for us, purchase invoices and expense documents. The API stack is optimized for performance and follows OData v4 conventions, allowing you to use standard query parameters like $filter
, $select
, and $expand
.
Expense Management in Business Central
In Business Central, expenses typically flow through purchase invoices. When an employee submits an expense report or when your company receives a vendor bill, these transactions create purchase invoice records.
A purchase invoice in Business Central consists of two main components: the header containing vendor information and metadata, and the lines containing individual expense items. This structure allows for complex expense scenarios like split allocations, multiple tax rates, and departmental chargebacks.
Authentication and Setup
Before making any API calls, you need proper authentication. Business Central supports several authentication methods, but OAuth 2.0 is recommended for production environments.
First, register your application in Azure Active Directory:
const tokenEndpoint = 'https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token';
const tokenRequest = {
client_id: 'your-client-id',
client_secret: 'your-client-secret',
scope: 'https://api.businesscentral.dynamics.com/.default',
grant_type: 'client_credentials'
};
// Get access token
const tokenResponse = await fetch(tokenEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(tokenRequest)
});
const { access_token } = await tokenResponse.json();
For development and testing, you can use basic authentication with web service access keys generated from within Business Central. Navigate to Users, select your integration user, and generate a web service access key.
Creating Expense Records: Purchase Invoice API
The purchase invoice API is your primary tool for creating expense records. Here's a complete example of creating an expense submission:
const baseUrl = 'https://api.businesscentral.dynamics.com/v2.0/tenant-id/production/api/v2.0';
const companyId = 'your-company-id';
// Per Business Central's data model, we must first create the invoice header...
const purchaseInvoice = {
"vendorNumber": "V00100",
"invoiceDate": "2025-01-15",
"postingDate": "2025-01-15",
"dueDate": "2025-02-15",
"vendorInvoiceNumber": "EXP-2025-001",
"currencyCode": "USD",
"paymentTermsId": "00000000-0000-0000-0000-000000000001"
};
const invoiceResponse = await fetch(`${baseUrl}/companies(${companyId})/purchaseInvoices`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(purchaseInvoice)
});
const invoice = await invoiceResponse.json();
After creating the header, add expense line items:
// ...and then attach line items to its unique ID
const expenseLine = {
"documentId": invoice.id,
"sequence": 10000,
"lineType": "G/L Account",
"lineObjectNumber": "6420", // Travel expenses account
"description": "Flight to client meeting - NYC",
"quantity": 1,
"unitCost": 450.00,
"amountExcludingTax": 450.00,
"taxCode": "STANDARD"
};
await fetch(`${baseUrl}/companies(${companyId})/purchaseInvoices(${invoice.id})/purchaseInvoiceLines`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(expenseLine)
});
Retrieving and Updating Expenses
To retrieve existing expense records, use GET requests with OData query parameters:
// Get all unposted purchase invoices for expense review
const response = await fetch(
`${baseUrl}/companies(${companyId})/purchaseInvoices?$filter=status eq 'Draft'&$expand=purchaseInvoiceLines`,
{
headers: { 'Authorization': `Bearer ${access_token}` }
}
);
const expenses = await response.json();
// Update an expense amount after manager review
const updateData = {
"unitCost": 425.00,
"amountExcludingTax": 425.00
};
await fetch(
`${baseUrl}/companies(${companyId})/purchaseInvoiceLines(${lineId})`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json',
'If-Match': '*'
},
body: JSON.stringify(updateData)
}
);
Note the 'If-Match': '*'
header, which Business Central requires to prevent accidental overwrites when updating records.
Handling Attachments
Expense reports often require receipt attachments. Business Central's API supports document attachments through a separate endpoint:
// Upload receipt attachment
const attachmentData = {
"parentType": "Purchase Invoice",
"parentId": invoice.id,
"fileName": "receipt_2025_001.pdf",
"attachmentContent": base64EncodedContent
};
await fetch(`${baseUrl}/companies(${companyId})/attachments`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(attachmentData)
});
Common Integration Challenges
Directly integrating with Business Central's API is challenging for several key reasons. You have to learn Business Central's entire data model, including the relationships between vendors, G/L accounts, dimensions, and tax codes. Field names don't always match business terminology, and error messages can be cryptic.
Rate limiting is another concern. Business Central enforces throttling to prevent service degradation. Production environments typically allow 100 requests per user per minute, with burst capacity up to 200 requests. Exceeding these limits results in 429 responses that require retry logic.
The complexity multiplies when supporting multiple accounting systems. Each platform has its own authentication method, data structure, and business rules. What Business Central calls a "purchaseInvoice" might be a "bill" in QuickBooks or a "supplier invoice" in Xero.
Simplifying with Unified APIs: The Apideck Approach
Instead of building and maintaining separate integrations for each accounting platform, Apideck provides a unified API that abstracts away platform-specific complexity. This approach transforms weeks of integration work into days.
With Apideck's Accounting API, the same expense creation looks like this:
import { Apideck } from '@apideck/unify';
// Initialize the Apideck client
const apideck = new Apideck({
apiKey: 'your-api-key', // Your Apideck API key
consumerId: 'customer-id', // Unique ID for the end-user or customer
appId: 'your-app-id' // Your Apideck application ID
});
// Create a simple expense
async function createExpense() {
try {
const response = await apideck.accounting.expenses.create({
serviceId: 'microsoft-dynamics-365-business-central', // Target service
account: { id: 'travel-expenses' }, // Ledger account ID
transaction_date: '2025-01-15', // ISO date format
line_items: [{
description: 'Flight to client meeting - NYC',
total_amount: 450.00, // Line item amount
tax_rate: { id: 'standard-rate' } // Tax rate ID
}],
currency: 'USD' // ISO currency code
});
console.log('Expense created successfully:', response);
} catch (error) {
console.error('Error creating expense:', error);
}
}
createExpense();
The same code works across Business Central, QuickBooks, Xero, NetSuite, and 20+ other accounting platforms. Apideck handles the translation between your unified data model and each platform's specific requirements.
Real-World Implementation Benefits
Consider a typical expense management application that needs to support multiple accounting systems. Without a unified API, you would need to:
- Learn each platform's API documentation
- Handle different authentication flows
- Map data fields for each system
- Maintain separate error handling logic
- Keep up with API version changes
Apideck eliminates this complexity by providing:
One API to Learn, Not Dozens: Single integration point with one SDK to implement and one set of documentation to reference.
Stop Worrying About Field Names: Apideck translates between your data model and each platform's requirements. Send "expense" and it becomes "purchaseInvoice" for Business Central, "bill" for QuickBooks, or "supplierInvoice" for Xero.
Consistent Errors You Can Actually Debug: Clear error messages that make sense across all platforms, instead of cryptic platform-specific codes.
Automatic Retries for Rate Limits: Built-in handling of throttling with exponential backoff, so you don't have to write retry logic for each platform.
For Business Central specifically, Apideck handles the complexity of purchase invoice creation, line item management, tax calculations, and attachment uploads through a single, consistent interface.
Conclusion
Microsoft Business Central's expense APIs are powerful but require significant investment to implement correctly. Between authentication setup, understanding the data model, and handling edge cases, a direct integration can take weeks to build and months to stabilize.
A unified API approach through platforms like Apideck cuts this complexity dramatically. Instead of maintaining separate integrations for each accounting system your customers use, you build once and deploy everywhere. This saves you from integration maintenance hell and lets you focus on your actual product.
Whether you integrate directly or use a unified API, remember that expense management is about more than just moving data. Handle edge cases gracefully, provide clear feedback when things break, and make sure your solution scales as transaction volumes grow.
For teams developing expense management, accounts payable, or financial automation tools, the decision between direct and unified APIs often hinges on resources and scope. If you're only supporting Business Central, a direct integration might work. But if you need to support multiple platforms or want to move fast, a unified API gives you the abstraction layer that lets you ship features instead of debugging integrations.
Ready to get started?
Scale your integration strategy and deliver the integrations your customers need in record time.