Scenario: Bill a customer with a free text invoice, and cancel one
Goal. Bill a customer for something that is not on a sales order (a service fee, a subscription, a recharge) by posting a free text invoice from your billing system, and take a posted free text invoice back when it was raised in error, through a correcting invoice. Each call posts exactly once, even when your system resends it.
When to use. Billing, subscription or project systems that own the invoice lines and need them posted to Accounts receivable. Invoices for goods on a sales order are posted with the order, see Sales order to invoice. To collect a posted free text invoice, continue with Customer payment and settlement.
Prerequisites
- Security (see Security model):
MGCLatticeAccountsReceivableServiceMaintaingrantspostFreeTextInvoiceandpostFreeTextInvoiceBatch;MGCLatticeIntegrationOperatorRoleandMGCLatticeIntegrationAdministratorRolehold it.MGCLatticeAccountsReceivableFTICancelMaintaingrantscancelFreeTextInvoiceandcancelFreeTextInvoiceBatch. It is a high-risk privilege inMGCLatticeIntegrationServiceHighRiskDuty, which onlyMGCLatticeIntegrationAdministratorRoleholds: the operator role cannot cancel.- The
FreeTextInvoiceHeadersread-back below is a standard Microsoft entity and needs the standard D365 privilege;LatticeCustomerTransactionscomes with the Lattice roles.
- D365 configuration in the legal entity:
- the free text invoice number sequences (Accounts receivable > Setup > Accounts receivable parameters, Number sequences tab) and a customer posting profile;
- the customer (USMF
US-001) and a revenue main account that allows manual entry (USMF401100, Product sales); - for a cancellation, a financial reason enabled for customers (USMF:
ERROR,GOODWILL,LOST,MISC,NSF,OTHER,PRICE,QUALITY,WRITEOFF); D365 does not save a correction without one; - sales tax groups, if you send them (USMF: customer group
CA, item groupsALLorAU/VI).
- Lattice setup. A valid license (an expired or invalid one refuses both operations with
LicenseNotValid). To name the customer or an invoice by your own key, set up an external system as in Cross-reference keys.
Steps
Both operations are on MGCLatticeFreeTextInvoiceService in MGCLatticeAccountsReceivableServiceGroup:
POST https://<your-environment>/api/services/MGCLatticeAccountsReceivableServiceGroup/MGCLatticeFreeTextInvoiceService/<operation>,
with the request in _request and an OAuth 2.0 client-credentials token.
-
Post the invoice.
postFreeTextInvoicecreates the invoice and posts it in one call:{"_request": {"RequestId": "3c9d8e7f-6a5b-4c4d-8e3f-2a1b0c9d8e7f","LegalEntityId": "USMF","UserContext": "Admin","InvoiceAccount": "US-001","CurrencyCode": "USD","PostingDate": "2026-10-07","DueDate": "2026-11-06","Lines": [{"Description": "Installation service, October","MainAccountId": "401100","AmountCur": 500.00,"Quantity": 1,"UnitPrice": 500.00}]}}Each line needs an amount: send
AmountCur, orUnitPrice(andQuantity, which defaults to 1); when you send both,AmountCurmust equalQuantityxUnitPriceto 0.01.MainAccountIdis the revenue account of the line;SalesTaxGroupandItemSalesTaxGroupare optional.CurrencyCodedefaults to the customer's currency. The answer reports success only when the invoice is posted:DocumentIdandPostedDocumentIdare the posted invoice number (for exampleFTI-00000122) and the message readsFree text invoice posted for customer US-001.Store the invoice number: the cancellation and the payment settlement name the invoice by it. -
Cancel the invoice (only when it was raised in error).
cancelFreeTextInvoicecreates a correcting invoice for the posted invoice, as the Correct function on a posted free text invoice does, and posts it:{"_request": {"RequestId": "9e8d7c6b-5a49-4382-a1b0-c9d8e7f6a5b4","LegalEntityId": "USMF","UserContext": "Admin","InvoiceId": "FTI-00000122","CancelDate": "2026-10-07","ReasonCode": "ERROR","ReasonComment": "Invoice raised in error"}}Name the invoice by
InvoiceId(the posted invoice number) or byInvoiceRecId(its record id, which wins when both are sent).ReasonCodeis required.CancelDatedefaults to today.DocumentIdis the canceled invoice andPostedDocumentIdthe correcting invoice:Free text invoice FTI-00000122 canceled via correcting invoice.
Many invoices at once. postFreeTextInvoiceBatch and cancelFreeTextInvoiceBatch take
{ "_requests": [ ... ] }, each item a full request with its own RequestId, and answer with one result per item
(see API reference, Bulk Operations).
Neither operation has a preview: a request with "PreviewMode": true is refused with PreviewUnsupported
before anything is created.
How to verify
- The response:
Successtrue,Status3; for a post,DocumentId=PostedDocumentId= the invoice number; for a cancellation,DocumentIdthe canceled invoice andPostedDocumentIdthe correcting invoice. - OData read-back: the posted invoices of the customer, including the correcting invoice:
GET https://<your-environment>/data/FreeTextInvoiceHeaders?cross-company=true&$filter=dataAreaId eq 'USMF' and InvoiceAccount eq 'US-001'&$select=FreeTextNumber,InvoiceAccount(FreeTextNumberis the invoice number). The open customer transaction of the invoice is inLatticeCustomerTransactions(filter onCustomerAccountandInvoiceId). - In D365: Accounts receivable > Invoices > All free text invoices lists the invoice and the correcting invoice as posted.
- Business event, when activated:
MGCLatticeFreeTextInvoicePostedBusinessEventfor each posted free text invoice (events).
When it goes wrong
ErrorCode | Typical cause in this flow | What to do |
|---|---|---|
ValidationFailed (1) | Missing InvoiceAccount or a customer that does not exist (Customer account US-999 was not found.); no lines or an empty line; a line with neither AmountCur nor UnitPrice (Line 1: AmountCur is required.) or with amounts that do not agree (Line 1: AmountCur 500.00 does not equal Quantity 2.00 x UnitPrice 200.00.); a cancellation without InvoiceRecId or InvoiceId, or without a valid ReasonCode (Reason code CANCEL was not found.); an external key that does not resolve | Correct the request and send it again. Nothing was created. |
DimensionValueNotFound (5) | MainAccountId is not in the legal entity's chart of accounts (Main account '999999' could not be resolved in the chart of accounts.) | Send a main account of the legal entity's chart of accounts. |
DocumentNotFound (2) | The invoice to cancel does not exist in the legal entity (Free text invoice with InvoiceId FTI-00000999 was not found.) | Check the number and LegalEntityId. |
ConfigurationMissing (11) | The legal entity has no ledger with a chart of accounts | Complete the ledger setup. |
BusinessRule (4) | D365 refused to post: The free text invoice for customer US-001 was created but not posted., or for a cancellation Free text invoice FTI-00000122 was not canceled: correcting invoice FTI-00000123 was created but did not post. D365's reasons are in Errors. | Fix the cause and resend; see below. |
TransientRuntime (6) | A runtime fault; IsRetryable is true | Resend with the same RequestId. |
PreviewUnsupported (10) | PreviewMode true | Send the request without preview. |
LicenseNotValid (12) | The Lattice license is expired or invalid | Renew the license, then resend. |
A refused post leaves no invoice behind. The service saves the invoice (or the correcting invoice) before it posts it. When D365 refuses the posting, the service deletes the unposted invoice again and the request is not remembered, so fix the cause and resend: the resend creates the invoice anew. Only if that cleanup itself fails (the invoice's own delete check refuses it) does the unposted invoice stay on All free text invoices; post or delete it in D365 before you resend.
Retries are safe otherwise. Resending a successful request with the same RequestId posts nothing again:
the answer repeats the original DocumentId and PostedDocumentId with the message
Duplicate request detected. Original request processed at .... A RequestId already used by another operation
is refused with DuplicateRequest (7). Calling cancelFreeTextInvoice without the high-risk privilege is refused
by D365 security (HTTP 403) before Lattice runs.
Reference
- API reference: section 7 (Free Text Invoice Service) and section 19 (Bulk Operations)
- Endpoint payload specification:
MGCLatticeFreeTextInvoiceService - Security model
- Cross-reference keys
- Customer payment and settlement
- Sales order to invoice
- Post a journal exactly-once