Scenario: Create, approve and post an expense report
Goal. Your expense tool (a receipt-capture app, a travel platform, a corporate card feed) files a worker's expenses in D365 as an expense report, sends it to the expense report approval workflow, and posts it once it is approved. Lattice creates the report and its lines the way the Expense reports page does, so the legal entity's expense categories, payment methods, sales tax defaults, financial dimensions, project rules and expense policies apply. Posting pays the worker through the worker's expense vendor account and charges project lines to their project.
When to use. Travel and expense reporting from an external tool, with approval in D365 (or approval driven from the tool through Workflow approvals). Project costs that need no approval are posted with a project expense journal instead (Project journals and invoicing).
Prerequisites
- Security.
MGCLatticeExpenseReportServiceMaintain("Expense reports - create and post") grantscreateExpenseReport,postExpenseReportand their batch variants. It is in dutyMGCLatticeIntegrationServiceOperationsDuty(integration operator and administrator roles). Creating a report for a worker also follows the delegate rule of Expense management: only the worker's own user, a delegate of the worker, or a user with the expense administrator, expense agent or system administrator role may file expenses for the worker. Approving through Lattice needsMGCLatticeWorkflowServiceMaintain. Reading the standardMobileExpReports,ExpensesandExpAttachedDocumentsentities needs D365's own privileges. See Security model. - D365 configuration in the legal entity:
- the Expense management parameters with the number sequences for expense report numbers and expense vouchers (Expense management > Setup > General > Expense management parameters, Number sequences);
- expense categories with the payment methods they allow. USMF:
TaxiandTravel(expense type Expense, posting to601500); payment methodCashposts to the worker's expense vendor account,CreditCard(every category's default) to the company card vendor; - the worker, employed in the legal entity, with an expense vendor account for employee-paid expenses. USMF: worker
000020Julia Funderburk, theAdminuser's worker, whose expenses post to vendorJULIAF; - an active expense report workflow (Expense management > Setup > General > Expense management
workflows). USMF: "Expense report - USMF", one approval step assigned to user
Admin; - for project lines, expense categories used in projects (USMF
Travel;Entertainis not) and the project (USMF000184); - an open ledger period for the expense dates.
- Lattice. A valid license (an expired or invalid one refuses the call with
LicenseNotValid).
Steps
The operations are on MGCLatticeExpenseReportService in service group MGCLatticeExpenseServiceGroup. Send a
POST with an OAuth 2.0 bearer token and the request in _request (see Trial and day-one guide).
-
Create the report and submit it.
POST https://<your-environment>/api/services/MGCLatticeExpenseServiceGroup/MGCLatticeExpenseReportService/createExpenseReport{"_request": {"RequestId": "3b5d7f9a-2c4e-4a6b-8d0f-1e3a5c7e9b2d","LegalEntityId": "USMF","UserContext": "expense-app","WorkerPersonnelNumber": "000020","Purpose": "Customer visit","SubmitToWorkflow": true,"Lines": [{"CategoryId": "Taxi","TransDate": "2026-10-21","Amount": 23.50,"CurrencyCode": "USD","PaymentMethod": "Cash","Merchant": "City Cabs","BusinessPurpose": "Airport to customer site"},{"CategoryId": "Travel","TransDate": "2026-10-21","Amount": 41.25,"PaymentMethod": "Cash","ProjId": "000184"}]}}WorkerPersonnelNumbernames the worker; empty means the calling user's worker. The worker must be employed in the legal entity.- Every line needs
CategoryIdand a non-zeroAmount.TransDatedefaults to today,CurrencyCodeto the accounting currency,PaymentMethodto the category's default (it must be one the category allows),Descriptionto the category's description, and the sales tax groups as the expense line defaults them (from the worker, the project and the category). - A line with
ProjId(and optionallyActivityNumber) is charged to the project; its category is also the project category, so it must be a category used in projects. Dimensions(a list of{ "DimensionName", "DimensionValue" }) on the report or on a line are merged over the dimensions the report and line default from the worker and the project.Description,Locationand, for the submit,Commentare optional on the report;Merchant,BusinessPurpose,AdditionalInformation,SalesTaxGroupandItemSalesTaxGroupon a line.
The answer:
DocumentIdis the expense report number. WithSubmitToWorkflowthe report runs the standard submit checks (policies, budget, itemization, required fields) and goes to the workflow that applies to it; it is then In review, andPostedDocumentIdis the workflow's correlation id (a lower-case GUID without braces), the key the workflow operations take:Expense report 000031 created for worker 000020 with 2 line(s) and submitted to workflow (workflow instance be1322c8-5c4f-4a0e-9d0b-7f3e2a1c6d84).WithoutSubmitToWorkflowthe report stays a draft, for the worker to complete and submit in D365.A request that fails after the report was created deletes the report and its lines again, so a refused request leaves nothing behind and can be sent again with the same
RequestId. -
Approve it. The approver approves the report in D365, or your system approves the work item as its assignee with
approveWorkItemonMGCLatticeWorkflowActionService(MGCLatticeWorkflowServiceGroup), withWorkflowCorrelationId= thePostedDocumentIdfrom step 1 andActingUserId= the assignee (USMFAdmin):{ "_request": { "RequestId": "8e0a2c4e-6f1b-4d3a-9c5e-7f9b1d3f5a8c", "LegalEntityId": "USMF","WorkflowCorrelationId": "be1322c8-5c4f-4a0e-9d0b-7f3e2a1c6d84", "ActingUserId": "Admin","Comment": "Approved in the expense app" } }Work items are created, and completed, asynchronously by the Workflow message processing batch job: poll
getWorkflowStatusuntil the work item appears and, after the approval, untilDocumentStatusisCompleted. Workflow approvals has the polling, reject, delegate and recall details. The report is then Approved. -
Post it.
POST https://<your-environment>/api/services/MGCLatticeExpenseServiceGroup/MGCLatticeExpenseReportService/postExpenseReport{ "_request": { "RequestId": "5f7b9d1f-3a5c-4e7a-8b9d-2c4e6a8c0e3f", "LegalEntityId": "USMF","UserContext": "expense-app", "ExpenseReportNumber": "000031" } }The report posts the way its Post does, in one transaction or not at all: the vendor, ledger or bank transactions of each line's payment method (employee-paid lines to the worker's expense vendor account), the project cost transactions of project lines, sales tax, and the report and its lines set to Posted. Lines post on their own transaction dates (moved to the next open date when that period is on hold or closed) unless the Expense management parameters say to post on the posting date. Each line can post under its own voucher (USMF posts one voucher per line):
PostedDocumentIdis the voucher of the first line, and the message lists every voucher:Expense report 000031 posted (voucher 000001, 000002).When an automatic posting workflow applies to the report, the workflow posts it instead. -
Attach the receipts (optional). Use the standard
ExpAttachedDocumentsentity, which attaches a file to an expense line by itsExpTransNumber. Read the line numbers from the standardExpensesentity (ExpenseTransactionNumber, filtered onExpenseReportNumber). The entity first stores the receipt with the calling user's worker, so call it as the worker's own user. The Lattice document attachment entities do not cover expense reports.
The bulk variants createExpenseReportBatch and postExpenseReportBatch take { "_requests": [ ... ] }, each item
a full request with its own RequestId, and answer one result per item (see
Batch and async operations). The operations are synchronous only and have no preview.
How to verify
- The responses:
Successtrue,Status3; create:DocumentIdthe report number (and the workflow correlation id inPostedDocumentIdafter a submit); post:DocumentIdthe report,PostedDocumentIda voucher. - The report:
MobileExpReportswith$filter=dataAreaId eq 'usmf' and ExpenseReportNumber eq '000031'&cross-company=trueshowsDocumentStatusPending(In review) after the submit,Approvedafter the approval andLedger(Posted) after the posting, andEmployeePersonnelNumber000020. - The lines:
Expenseswith the same filter lists one row per line (ExpenseCategory,TransactionAmount,PaymentMethod,ProjectID); after posting, every line hasApprovalStatusLedgerand aVoucher. - The transactions:
LatticeVendorTransactionsfor vendorJULIAFunder the line vouchers carries the employee-paid amounts;LatticeProjectPostedTransactionshas a cost transaction for the project line on the project, with its category and date. Posting gives that transaction an id of its own, so look it up by project, category and date rather than by the line'sProjectTransactionID. - In D365: Expense management > My expenses > Expense reports (or All expense reports) shows the report and its lines, In review, then Approved, then Posted; the posted report's voucher shows the vendor transaction of the worker's expense vendor and, for project lines, the project's posted cost transactions.
When it goes wrong
A failed call answers Success false, Status 4, an ErrorCode and the message "Expense report creation failed
due to a validation or business rule error." (or "Expense report posting failed ..."); the reasons are in Errors.
| ErrorCode | Typical cause (text in Errors) | What to do |
|---|---|---|
ValidationFailed (1) | Create: no RequestId or unknown LegalEntityId; "At least one expense line is required."; "Expense line 1 is empty."; "Expense line 1: CategoryId is required."; an unknown or inactive category; "Expense line 1: Amount must not be zero."; an unknown currency, payment method, sales tax group, item sales tax group, project or activity; "Expense line 2: payment method ... is not allowed for expense category ..."; a category with no usable default payment method and no PaymentMethod; "Expense line 2: expense category Entertain is not used in projects, so the line cannot be charged to project 000184."; ActivityNumber without ProjId; an unknown worker; "Worker ... has no employment in legal entity ..."; no WorkerPersonnelNumber and the calling user has no worker. Post: no ExpenseReportNumber | Correct the request and resend. Nothing was created. |
DimensionValueNotFound (5) | An unknown financial dimension or value | Use dimension names and values of the legal entity. |
AccessDenied (14) | "User ... may not create expense reports for worker ..." | Call as the worker's user, a delegate, an expense administrator or an expense agent. |
DocumentNotFound (2) | Post: "Expense report ... does not exist in legal entity ..." | Check the number and the legal entity. |
InvalidStatus (3) | Post: "Expense report ... is already posted (voucher ...)."; "Expense report ... cannot be posted: its approval status is Pending. Only an approved expense report can be posted." (Draft, In review, Returned, Canceled...); a line that is not approved | Wait for the approval, or approve it (step 2). A posted report needs nothing more. |
ConfigurationMissing (11) | No number sequence for expense report numbers or expense vouchers; "No expense report workflow applies to expense report ..." | Fix the setup, or create the report without SubmitToWorkflow. |
BusinessRule (4) | The standard line or report validation, or the submit checks (expense policies, budget, itemization, required fields), refused the report; the posting checks or the posting refused it (a payment method without offset account, the project, an exchange rate, a closed period, budget). The standard messages are in Errors | Fix the cause in D365 or in your data, then resend. |
PreviewUnsupported (10) | PreviewMode true | Send without PreviewMode. Nothing was run. |
TransientRuntime (6) | A runtime fault; IsRetryable true | Resend with the same RequestId. |
LicenseNotValid (12) | The Lattice license is expired or invalid | Renew the license, then resend. |
Retries are safe. Resending a successful request with the same RequestId creates or posts nothing again: the
answer repeats the original result with the message Duplicate request detected. Original request processed at ....
A new RequestId for a report that is already posted is refused with InvalidStatus.
The approval never arrives. See Workflow approvals, When it goes wrong: a faulted workflow instance or a stopped Workflow message processing job leaves the report In review.
Reference
- API reference: Expense Report Service, and section 19 (Bulk Operations)
- Endpoint payload specification:
MGCLatticeExpenseReportService - Security model:
MGCLatticeExpenseReportServiceMaintain - Workflow approvals, Project journals and invoicing