Skip to main content

Scenario: Post a general journal or an intercompany journal

Goal. Your external system (a close tool, an allocation engine, a payroll or billing platform) sends ledger entries to Dynamics 365 and gets back a posted general journal: lines on main accounts, customers, vendors or bank accounts, with offset accounts and financial dimensions where you need them, and, for intercompany entries, the offset posted in another legal entity. You validate the request first, post it, and confirm the journal is posted. A journal posted in error, or an accrual that must be undone next period, is reversed the same way, as the posted journal's Reverse does.

When to use. Accruals, reclassifications, allocations and any other ledger entry; customer invoices without a sales order (billing platforms), vendor adjustments and transfers between bank accounts (step 5). For the retry-safe pattern every caller should follow (stable RequestId, replay, stranded-claim recovery), see Post a journal exactly-once; this page does not repeat it. If the entries arrive as files, front this with Inbound file to posted document; for very large volumes, see Import journals in bulk. A general journal that was entered or imported in D365 and only needs posting is posted with Post an existing journal. Budget amounts go through Budget register entries, and exchange-rate adjustments through Currency revaluation.

Prerequisites​

  1. Security. The calling user (the D365 user mapped to your Entra app) needs:

    • postGeneralJournal: privilege MGCLatticeGeneralLedgerServiceMaintain, in duty MGCLatticeIntegrationServiceOperationsDuty (operator and administrator roles).
    • postIntercompanyJournal: privilege MGCLatticeGeneralLedgerIntercompanyMaintain, in the high-risk duty MGCLatticeIntegrationServiceHighRiskDuty, which only MGCLatticeIntegrationAdministratorRole holds.
    • getJournalStatus: privilege MGCLatticeGeneralLedgerServiceView, in MGCLatticeIntegrationServiceQueryDuty (all three integration roles).
    • reverseJournal: privilege MGCLatticeGeneralLedgerJournalReverseMaintain ("General ledger - reverse posted journals (high-risk)"), in the high-risk duty MGCLatticeIntegrationServiceHighRiskDuty. Posting a journal does not grant reversing it.

    A call without the privilege is refused by D365 with HTTP 403 before Lattice runs.

  2. A journal name of type Daily with a voucher number sequence, in the legal entity (General ledger > Journal setup > Journal names). USMF has GenJrn (general journal) and IntJrn (intercompany).

  3. Main accounts that allow manual entry in the legal entity's chart of accounts. USMF (chart Shared): 110110 Bank Account - USD, 110180 Petty Cash, 231300 Intercompany Payable - USMF/DEMF, 401100 (revenue), 601500 (expense). (200110 Accounts Payable - Other does not allow manual entry and is refused by the post.) For customer, vendor and bank lines, see step 5 for what else they need.

  4. An open ledger period for the posting date, and an exchange rate for each line currency.

  5. Intercompany only: to post an offset in another legal entity, intercompany accounting must be set up between the two legal entities (General ledger > Posting setup > Intercompany accounting).

  6. Reversal only: the number sequences for reversal vouchers and trace numbers in the general ledger parameters (General ledger > Ledger setup > General ledger parameters, Number sequences tab; USMF has them), and an open ledger period for the reversal date. A financial reason (USMF ERROR) is optional for a ledger voucher; a journal with customer vouchers needs one when the Accounts receivable parameters require a reason code for reversals.

  7. Lattice. A valid license (a never-registered environment is allowed through; an adverse verdict refuses posting with LicenseNotValid). No Lattice parameters are needed. A journal line can name its customer or vendor by your own key (AccountNumExternalKey / OffsetAccountNumExternalKey, with ExternalSystem on the request); main accounts and bank accounts are D365 ids. getJournalStatus can take a journal's external key (DocumentIdExternalKey with ExternalSystem), and reverseJournal one too (JournalNumExternalKey with ExternalSystem), see Cross-reference keys.

Steps​

All four operations are on MGCLatticeGeneralLedgerJournalService in service group MGCLatticeGeneralLedgerServiceGroup. Send a POST with an OAuth 2.0 bearer token and the request in _request (see Trial and day-one guide).

  1. Build the request. One request is one journal. A line on main accounts needs MainAccountId, CurrencyCode and a debit (AmountCurDebit) or credit (AmountCurCredit) amount; OffsetMainAccountId, Description, TransDate (defaults to the header PostingDate, which defaults to today) and Dimensions are optional. (A line with a customer, vendor or bank account names its accounts by type instead; see step 5.) Lattice gives all main-account lines of the journal one voucher from the journal name's voucher series (a journal name set to allocate vouchers at posting lets D365 assign them), so the voucher must balance: either give each line an OffsetMainAccountId (each line then balances itself), or send debit and credit lines without offsets that net to zero.

  2. Validate first (preview). Send the request with "PreviewMode": true:

    POST https://<your-environment>/api/services/MGCLatticeGeneralLedgerServiceGroup/MGCLatticeGeneralLedgerJournalService/postGeneralJournal
    {
    "_request": {
    "RequestId": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
    "LegalEntityId": "USMF",
    "UserContext": "erp-close",
    "JournalNameId": "GenJrn",
    "Description": "October accrual",
    "PostingDate": "2026-10-15",
    "PreviewMode": true,
    "Lines": [
    {
    "MainAccountId": "110110",
    "OffsetMainAccountId": "110180",
    "AmountCurDebit": 1000.00,
    "CurrencyCode": "USD",
    "Description": "Accrual"
    }
    ]
    }
    }

    A valid request returns Success true, PreviewApplied true, PreviewFidelity 1 (ValidationOnly) and the message "Preview (ValidationOnly): general journal request for GenJrn is valid. No journal was created or posted." The preview checks the request: the legal entity, that the journal name exists, at least one line, each line's account members (MainAccountId and CurrencyCode on a main-account line, or a supported AccountType with its AccountNum), no OffsetCompany on a general journal, the customer, vendor and bank accounts, keys and setup records (terms of payment, posting profile, method of payment, sales tax groups, bank transaction type) the lines name, and the one-transaction-per-voucher rule of step 5. It does not check the journal type, the main accounts, the dimensions or the balance; those are checked when you post. A preview does not use up its RequestId.

  3. Post. Send the same request without PreviewMode (or with false). The service creates the journal (named after Description), posts it, and answers only when the journal is posted: Success true, Status 3, and DocumentId = PostedDocumentId = the journal number, with the message "General journal ... posted successfully." Keep the journal number; the status query needs it.

  4. Add financial dimensions (optional). To post a line with dimensions, add a Dimensions list to the line, one entry per dimension, using the dimension names from the Financial dimensions page:

    "Dimensions": [ { "DimensionName": "Department", "DimensionValue": "022" } ]

    Each value must exist for that dimension and suit the main account's account structure.

  5. Post customer invoices, vendor adjustments and bank transfers. A line can carry a customer, vendor or bank account. It names its accounts with AccountType + AccountNum and OffsetAccountType + OffsetAccountNum instead of MainAccountId / OffsetMainAccountId (not both on one line). The types travel as integers: 0 Ledger (a main account), 1 Customer, 2 Vendor, 6 Bank.

    What D365 needs: the customer, vendor and bank accounts; the accounts receivable and payable posting profiles (Accounts receivable > Setup > Accounts receivable parameters > Ledger and sales tax > Posting profile, and the same for accounts payable); for a bank transfer, a bank transaction type (Cash and bank management > Setup > Bank transaction types; USMF 05 Transfer) and an interim main account that allows manual entry. Check General ledger > Ledger setup > General ledger parameters > Allow multiple transactions within one voucher: off (the default, and USMF) means one customer or vendor transaction and one bank transaction per voucher.

    A customer invoice, one line per invoice, with the revenue account as offset:

    {
    "_request": {
    "RequestId": "9b2e6a51-3c1d-4f8e-a0b7-5d6c7e8f9a01",
    "LegalEntityId": "USMF",
    "UserContext": "billing",
    "JournalNameId": "GenJrn",
    "Description": "Customer invoices",
    "Lines": [
    {
    "AccountType": 1,
    "AccountNum": "US-001",
    "OffsetAccountType": 0,
    "OffsetAccountNum": "401100",
    "InvoiceId": "INV-10045",
    "AmountCurDebit": 1250.00,
    "Description": "Consulting, March"
    }
    ]
    }
    }
    • Defaults from the account, as the journal page fills them: a customer line takes the customer's currency, terms of payment, method of payment and sales tax group, the posting profile of the accounts receivable parameters, and, when the line has an InvoiceId, a due date calculated from the terms of payment; a vendor line takes the vendor's values the same way; a bank line takes the bank account's currency. What the line sends wins: InvoiceId, DocumentNumber, DocumentDate, DueDate, PaymentTermsId, PostingProfile, SalesTaxGroup, ItemSalesTaxGroup, MethodOfPayment, PaymentReference, BankTransactionType. CurrencyCode is needed only on a main-account line.

    • Sales tax is calculated when the journal posts, as on the journal page: with the customer's sales tax group and the default item sales tax group, the customer owes the amount plus the tax.

    • A vendor adjustment is a line with "AccountType": 2, the vendor, a credit (raises the payable) or a debit, and the expense account as the offset (USMF US-101 against 601500).

    • A bank transfer is two bank lines, each against the interim main account: the paying bank account as a credit and the receiving one as a debit, with the bank transaction type:

      "Lines": [
      { "AccountType": 6, "AccountNum": "USMF OPER", "OffsetAccountType": 0, "OffsetAccountNum": "110180",
      "AmountCurCredit": 5000.00, "BankTransactionType": "05", "Description": "Fund payroll account - out" },
      { "AccountType": 6, "AccountNum": "USMF PAYRL", "OffsetAccountType": 0, "OffsetAccountNum": "110180",
      "AmountCurDebit": 5000.00, "BankTransactionType": "05", "Description": "Fund payroll account - in" }
      ]

      Use your bank transfer clearing account in place of 110180; the two lines net to zero on it.

    Vouchers. A line with a customer, vendor or bank account and an offset account is a voucher of its own; all other lines share one voucher, and a line without an offset balances against the other lines of its voucher. Unless Allow multiple transactions within one voucher is set, a voucher may hold only one customer or vendor transaction and only one bank transaction, as the standard posting requires: a request that would break this is refused with BusinessRule before anything is created. That is why a bank-to-bank transfer is sent as two lines through an interim account, not as one line from one bank account to the other.

    The posted customer invoice is open for settlement: settle it with a payment through Customer payment and settlement (postCustomerPaymentJournal, then settleCustomerTransactions by InvoiceId, for the invoice total). The answer is the same as for any general journal: DocumentId = PostedDocumentId = the journal number.

  6. Post an intercompany journal. Call postIntercompanyJournal with an intercompany journal name. The request has the same members as a general journal:

    POST https://<your-environment>/api/services/MGCLatticeGeneralLedgerServiceGroup/MGCLatticeGeneralLedgerJournalService/postIntercompanyJournal
    {
    "_request": {
    "RequestId": "0d9e8f7a-6b5c-4d3e-9f2a-1b0c9d8e7f6a",
    "LegalEntityId": "USMF",
    "UserContext": "erp-close",
    "JournalNameId": "IntJrn",
    "Description": "Shared services charge",
    "PostingDate": "2026-10-15",
    "Lines": [
    {
    "MainAccountId": "110110",
    "OffsetMainAccountId": "231300",
    "AmountCurDebit": 500.00,
    "CurrencyCode": "USD",
    "Description": "Intercompany debit"
    }
    ]
    }
    }

    A line without OffsetCompany posts in the request's legal entity, as above. To post a line's offset in another legal entity, add "OffsetCompany": "DEMF" to the line and set OffsetMainAccountId to an account of that legal entity's chart of accounts: the service resolves the offset account there. The offset can also be a customer, vendor or bank account of that legal entity (OffsetAccountType + OffsetAccountNum, as in step 5), for example a USMF expense against a vendor of USSI. The answer is the same as for a general journal ("Intercompany journal ... posted successfully.").

  7. Confirm the posting. Call getJournalStatus with the journal number in DocumentId:

    POST https://<your-environment>/api/services/MGCLatticeGeneralLedgerServiceGroup/MGCLatticeGeneralLedgerJournalService/getJournalStatus
    {
    "_request": {
    "RequestId": "3a4b5c6d-7e8f-4a1b-9c2d-3e4f5a6b7c8d",
    "LegalEntityId": "USMF",
    "DocumentId": "<journal number from step 3>"
    }
    }

    The answer carries Success true, DocumentType GeneralJournal, DocumentStatus Posted (or NotPosted), LastModifiedDateTimeUtc, and StatusDetails with Posted, JournalName, Description and NumberOfLines. The query works for any ledger journal number, including intercompany journals, and reads and changes nothing.

  8. Reverse a posted journal (when needed). Call reverseJournal with the journal number. Every voucher of the journal is reversed by the standard reversal that covers it, as the posted journal's Reverse does, on the reversal date:

    POST https://<your-environment>/api/services/MGCLatticeGeneralLedgerServiceGroup/MGCLatticeGeneralLedgerJournalService/reverseJournal
    {
    "_request": {
    "RequestId": "e5d4c3b2-a1f0-4e9d-8c7b-6a5f4e3d2c1b",
    "LegalEntityId": "USMF",
    "UserContext": "erp-close",
    "JournalNum": "<journal number from step 3>",
    "ReversalDate": "2026-11-01",
    "ReasonCode": "ERROR",
    "ReasonComment": "October accrual reversed"
    }
    }
    • Which vouchers. General journal, allocation and elimination vouchers (a ledger voucher counts as one when it was posted with transaction type General journal, Allocation or Elimination, as every line postGeneralJournal and postIntercompanyJournal post is), customer and vendor transactions, and customer payments posted to a bank account (canceled as the bank's payment cancellation does, which also reverses their settlements). Bank, fixed asset and project transactions are reversed in their own module (a fixed asset voucher with reverseAssetTransaction, see Fixed asset lifecycle), so a journal with them (such as a bank transfer from step 5) is refused. A customer invoice or vendor adjustment from step 5 is reversed here; unsettle it first if a payment settled it.
    • One voucher. Send Voucher to reverse only that voucher of the journal; Voucher alone names the journal that contains it.
    • Date. ReversalDate defaults to today and cannot be before a voucher's date.
    • All vouchers of the request are reversed in one transaction: all or none.

    DocumentId is the reversed journal and PostedDocumentId the reversal voucher of its first voucher; the message lists every pair, for example Journal 000874 reversed: 1 voucher(s), original > reversal: GNJL001204 > GNJL001231. The standard engines' own messages are in InformationalMessages.

To post many journals in one call, use postGeneralJournalBatch / postIntercompanyJournalBatch with a _requests list (each item has its own RequestId and result); reverseJournalBatch reverses many journals the same way. The posts and the reversal can also be queued with submitAsyncOperation (see API reference, sections 19 and 21).

How to verify​

  • The post response: Success true, Status 3, ErrorCode 0, DocumentId = PostedDocumentId = the journal number. Posting-engine warnings, if any, are in Warnings.
  • getJournalStatus for that number returns DocumentStatus Posted and the expected NumberOfLines.
  • In D365: General ledger > Journal entries > General journals, showing posted journals, lists the journal with its description; General ledger > Inquiries and reports > Voucher transactions shows the ledger entries.
  • Over OData: read LatticeGeneralLedgerEntries with $filter=JournalNumber eq '<journal number>' and LegalEntityId eq 'USMF' to see the posted account entries (main account, amount, dimension display value).
  • Customer, vendor and bank lines: LatticeCustomerTransactions, LatticeVendorTransactions and LatticeBankAccountTransactions, filtered on Voucher or InvoiceId (and dataAreaId, with cross-company=true). A customer invoice shows its amount plus the sales tax, open for its total, with its due date; a bank transfer shows the credit on the paying bank account and the debit on the receiving one. In D365: Accounts receivable > Customers > All customers > Transactions, Accounts payable > Vendors > All vendors > Transactions, and Cash and bank management > Bank accounts > Bank transactions.
  • A reversal: Success true, Status 3, DocumentId = the journal, PostedDocumentId = the reversal voucher. LatticeGeneralLedgerEntries with $filter=SubledgerVoucher eq '<reversal voucher>' and LegalEntityId eq 'USMF' shows the original amounts with opposite signs on the same main accounts. In D365, open the posted journal on General journals, Lines > Voucher: the voucher transactions show the original and the reversal voucher, and the original transactions show as reversed (Transaction reversal > Trace).
  • The call itself: System administration > Inquiries > Lattice > Services > Service call log, or LatticeServiceCallLogs filtered on your RequestId.
  • If you subscribe to business events, the General journal posted event fires once per posted Daily journal.

When it goes wrong​

A failed post answers Success false, Status 4, an ErrorCode, and a generic Message ("General journal posting failed due to validation or business rule constraints."); the specific reasons are in Errors.

ErrorCodeTypical cause (text in Errors)What to do
ValidationFailed (1)No RequestId; unknown LegalEntityId; no JournalNameId; "Journal name ... was not found."; "Journal name ... is not a General journal type."; no lines; "Request line N is empty."; "Line N: MainAccountId is required." (or CurrencyCode); "Line N: OffsetCompany is not supported by postGeneralJournal..."; "Offset company ... was not found."; "Line N names its accounts both ways..."; "Line N: AccountType 3 is not supported. Send 0 (Ledger), 1 (Customer), 2 (Vendor) or 6 (Bank)."; a typed line without AccountNum, or an offset type other than Ledger without OffsetAccountNum; "Line N: PaymentTermsId applies only to a line with a customer or vendor account." (also DueDate, PostingProfile, MethodOfPayment); BankTransactionType on a line without a bank account; an unknown customer, vendor or bank account (in the offset company for an intercompany offset); an unknown terms of payment, posting profile, method of payment, sales tax group, item sales tax group or bank transaction type; an external key on a main or bank account, a key that does not resolve, or no ExternalSystemCorrect the request and send it again. In a preview, an unknown journal name is reported as DocumentNotFound (2).
DimensionValueNotFound (5)"Main account '...' could not be resolved in the chart of accounts."; the same for the offset account; "Financial dimension '...' was not found."; "Value '...' for financial dimension '...' could not be resolved."Use accounts and dimension values that exist in the legal entity (the offset company for an intercompany offset). Nothing is posted.
ConfigurationMissing (11)"Journal name ... has no voucher number sequence configured."; "Legal entity ... has no ledger with a chart of accounts."; no chart of accounts for the offset companyFix the setup in D365, then resend.
BusinessRule (4)"Line N would put a second customer or vendor transaction in one voucher, which the general ledger parameter Allow multiple transactions within one voucher does not allow..." (or a second bank transaction): nothing was created; D365 refused the post: the voucher does not balance, the period is closed, an account does not allow manual entry, an exchange rate is missing; or "Journal ... was not posted."Give each customer, vendor or bank line its own offset account, or split a bank transfer into two lines (step 5). Otherwise fix the cause and resend; see below.
TransientRuntime (6)An infrastructure fault; IsRetryable trueResend the same request with the same RequestId.
DuplicateRequest (7), RequestInProgress (8), RequestNeedsReview (9)The RequestId was used beforeSee Post a journal exactly-once.
LicenseNotValid (12)The license verdict is adverse; IsRetryable trueRenew the license, then resend. Previews keep working.

A refused post leaves no journal behind. The service commits the journal before it posts it. When the post fails (BusinessRule, or TransientRuntime during posting), the service deletes the unposted journal again and the failed request releases its RequestId, so fix the cause and resend: the resend creates and posts the journal anew. Only if that cleanup itself fails does the journal stay unposted on General journals, with your Description as its name; delete it (or correct and post it in D365) instead of resending. Requests refused before the journal is created (ValidationFailed, DimensionValueNotFound, ConfigurationMissing) never create one.

Status query failures. getJournalStatus answers ValidationFailed without a DocumentId and DocumentNotFound ("Journal ... was not found.") for a number that does not exist in the legal entity.

Reversal failures. A refused reversal answers "Journal reversal failed due to validation or business rule constraints." and reverses nothing; the reasons are in Errors.

ErrorCodeTypical cause (text in Errors)What to do
ValidationFailed (1)No JournalNum, JournalNumExternalKey or Voucher; "Reason code ... was not found."; "A reason code is required to reverse voucher ..."; "The reversal date ... is before the date of voucher ..."; "Voucher ... is in journals ... and .... Send JournalNum to choose the journal."Correct the request and resend.
DocumentNotFound (2)An unknown journal; "Voucher ... was not found in a ledger journal."; "Voucher ... is not in journal ..."Check the journal and voucher in the legal entity.
InvalidStatus (3)"Journal ... is not posted, so it cannot be reversed."; "Voucher ... has already been reversed or canceled."Nothing to reverse.
BusinessRule (4)"Voucher ... of journal ... cannot be reversed by the standard journal reversal. It reverses general journal, allocation and elimination vouchers, customer and vendor transactions, and customer payments posted to a bank account; bank, fixed asset and project transactions are reversed in their own module."; "Voucher ... is settled. Unsettle it before you reverse it."; a voucher with several customer payments (reverse each with reverseCustomerPayment); a ledger transaction the standard reversal cannot reverse (posting type, closed year, intercompany account); a refusal of the standard engineReverse the voucher in its own module or in D365, unsettle it first, or fix the cause in Errors.
ConfigurationMissing (11)"The number sequence for ... is not set up." (reversal voucher, trace number, or the customer closing voucher for a payment cancellation)Set up the number sequence, then resend.
PreviewUnsupported (10)PreviewMode true: a reversal has no previewSend without PreviewMode.

Resending a reversal with the same RequestId returns the recorded result and reverses nothing again; a new RequestId for a journal already reversed is refused with InvalidStatus.

Queued posts. A post or reversal submitted with submitAsyncOperation that fails while it runs lands in the dead-letter queue (System administration > Inquiries > Lattice > Integration operations > Integration operations console); replay reuses the original RequestId. See Integration operations.

Reference​