Expenses Realtime-enabled
GETPOSTPATCHDELETETable: EONE Transaction
Import employee expense reports (e.g. from Concur) as their own header/line document, a header with an expenseLines child collection, backed by a dedicated staging table so it can be reviewed and approved in Business Central before it reaches the ledger. Each line is one expense against an expense G/L account; the header records how the report was paid.
Report name, number and key
One expense report is one document, and three header fields carry its identity:
description. The report name, for exampleAugust Travel - Tokyo. SmartConnect carries it onto the journal lines, so it becomes the description of the resulting general ledger entries. That is where an accountant reads it.externalDocumentNumber. The report number from your expense system, for example a Concur report id. Keep it human-readable, because it is what gets quoted when someone queries the report.integrationId. The report key. It is carried onto every journal line the report produces, and it is what you look the report up by afterwards. See Processed Documents.
The in-product eOne Expenses page shows description as Report Name, so the client and the API agree on which field is which.
Reimbursement type
reimbursementType records how the report is settled with the person who incurred it, as your expense system already classified it. The values are:
- Reimbursable. The employee paid out of pocket and is owed the money back.
- Company Paid. The company paid directly, so nothing is owed to the employee.
- Card Paid. Paid on a corporate card, tracked separately so the card statement can be reconciled.
Leave it blank when your source does not classify reports. It is a classification, not a posting instruction: the balancing account below still decides where the credit lands.
balancingAccountType set to Employee. The classification and the balancing account come from different mappings, so they disagree exactly when one of them is wrong. A Reimbursable report credited to a bank account would look settled while the employee is still out of pocket. Both validate and process reject it and name the account type they found, so you see the mapping error before anything posts.How it was paid: the balancing account
The header's balancingAccountType + balancingAccountNumber are the credit side, i.e. how the expense was settled, the same idea as a Continia payment type or the Expense Agent's payment method:
- Employee. Out-of-pocket; credits the employee, creating a reimbursement in the Employee ledger.
- Bank Account. Company card or cash already paid.
- Vendor. For example, a corporate-card provider.
- G/L Account. A clearing/holding account.
Shape
POST the header, then POST one line per expense under expenses(<id>)/expenseLines. Dimensions are per line here, since expenses are split by cost centre (see Expense Lines for the line fields). process writes one debit journal line per expense line plus a single balancing credit line to the account above, then clears the staging document.
Processing is configurable
How a staged expense reaches the ledger is set on the eOne Integration Setup page (Journal Wrappers section): Manual (call process, or use the in-product eOne Expenses page), Auto Draft to Journal, or Auto Post. Reports arriving pre-approved from the source pair naturally with Auto Post.
Bound actions
Invoke with POST /expenses(<id>)/Microsoft.NAV.<action>:
validate: re-runs the document's checks (accounts, balancing account, amounts) without processing it, reporting any problem instatus/errorMessage.process: writes one debit journal line per expense line plus a single balancing credit line, then clears the staging document (unposted draft, or posted, per the Setup policy above).post: processes the expense and posts it, whatever the Setup policy says. Use it when SmartConnect decides that this one expense goes to the ledger now, rather than an administrator putting the whole endpoint on Auto Post. Only the lines this expense just wrote are posted, so other drafts sitting in the same journal batch are left alone, and the approval gate applies exactly as it does toprocess.reopen: returns a failed expense to Open and clears its error, handing it back to the background job. A expense that hitsstatus= Error is not collected again on its own, so on an Auto endpoint - where by definition nothing callsprocess- this is how a corrected expense gets moving. It refuses a expense that is pending approval, exactly as the Business Central action does.
process and post return no body. The staged expense deletes itself the moment its journal lines exist, so there is nothing to echo, and the outcome lives on the Processed Documents row the same call wrote. Fetch it by the integrationId you sent the expense with - processedDocuments?$filter=integrationId eq '<your id>' - and read postedDocumentNumber, postedPostingDate, glTransactionNumber and status; after post they are already filled in. validate writes nothing and points back at the staged expense. A rejected call returns the error and leaves the expense in staging, with errorMessage describing the problem, ready to correct and retry. See Journal Wrappers.
status (Open → Pending Approval → Approved) and errorMessage fields report where the document is; a failed automatic attempt lands status = Error with the reason in errorMessage.What a PATCH may do
Two rules apply to every write, on the header and on its lines and applications alike.
A expense under approval cannot be changed. Once status is Pending Approval or Approved, a PATCH, a line append or a delete is rejected. What the approver signed off on is what posts. Call reopen first if the expense genuinely has to change; that is an explicit act rather than a silent revocation, and it leaves no orphaned approval entry behind.
Deleting is also a setup choice. A DELETE of a staged expense is refused when the endpoint's Allow Manual Delete switch is off on the eOne Integration Setup page. It is on by default, so nothing changes until an administrator turns it off to mark the whole document family integration-owned. The approval rule above holds regardless of the switch.
Correcting a failed expense clears its error. Any successful write to a expense whose status is Error returns it to Open and empties errorMessage, so it becomes visible to the background job again - a line or application write counts, because the failure is usually in a line. The full text of every failed attempt is kept separately and is not lost when the live message is cleared. Awaiting Invoice is left alone: that one heals itself when the invoice posts.
Status writeback: telling your expense system what happened
Every stage of the report's life in Business Central has a signal SmartConnect can read. The map carries each one back to Concur, Expensify or any other source system:
- Received: the POST response, and the staged document's read-only
statusanderrorMessagefields while it waits or fails. - Processed and posted: the report's row on the
processedDocumentsendpoint. The row appears when the document is processed. Itsstatusflips to Posted when the journal actually posts, carrying the finalpostedDocumentNumberandglTransactionNumber. The endpoint supports webhooks, so a map subscribes once and reacts to both moments. - Paid: the reimbursement's row on the
postedEmployeePaymentsendpoint (also webhook-enabled), keyed by the sameemployeeIntegrationIdused on the report.
For Concur, SmartConnect sends the Financial Integration Service acknowledgement as soon as a document is fetched, because Concur can recall an unacknowledged document. The posting confirmation follows when processedDocuments reports the report, and the payment confirmation when the reimbursement posts. For Expensify, one update call marks the report Reimbursed at whichever of those moments matches your policy.
Sending the same report twice
Expense systems re-export. Expensify re-sends anything not stamped with your export label, a Concur recall replays a document, and a timeout makes any map retry a POST. Two layers keep a re-send from becoming a duplicate posting:
- In your export: mark what you have already pulled. Expensify's
markAsExportedaction with a per-environment label, combined with themarkedAsExportedfilter, makes Expensify itself skip anything already sent to this Business Central environment. - In Business Central: the duplicate gate. With Reject Duplicate Integration Ids on (the default), a report whose
integrationIdis already staged or already processed is refused, and the error names the document it was first imported as. The map surfaces that as a per-record exception instead of posting the report twice.
To deliberately import a report again, for example after a correction in the source system, delete its row from the eOne Processed Documents page and re-send it. The gate then accepts the new version.