Scenario: Take a customer return to a credit note
Goal. A customer sends goods back. You record the return as a D365 return order with its reason, receive the goods with a disposition code that says what happens to them, and post the credit note that reduces what the customer owes. Each step posts exactly once, even when your system resends it.
When to use. RMA portals, warehouse or customer-service systems that authorize returns outside D365 and need the return order, the receipt and the credit note in D365. The goods usually come from an order invoiced as in Sales order to invoice. To credit an amount without goods coming back, cancel a free text invoice instead (Free text invoice).
Prerequisites
- Security.
MGCLatticeAccountsReceivableServiceMaintaingrantscreateSalesReturn,receiveSalesReturn,creditSalesReturnand their batch variants.MGCLatticeIntegrationOperatorRoleandMGCLatticeIntegrationAdministratorRolehold it. See Security model. TheSalesInvoiceHeadersread-back below is a standard Microsoft entity and needs the standard D365 privilege. - D365 configuration in the legal entity:
- return reason codes (Sales and marketing > Setup > Returns > Return reason codes); in USMF
the codes are numbers, for example
21(Defective) and11(Obsolete); - disposition codes (Sales and marketing > Setup > Returns > Disposition codes). The code's
action decides the flow: in USMF,
11and12are Credit (receive into inventory, then credit note),21is Credit only,61is Scrap. This walkthrough uses11; - the number sequences for return orders (RMA number), sales orders, packing slips and credit notes (Accounts receivable > Setup > Accounts receivable parameters, Number sequences tab);
- the customer (USMF
US-001) and the released item (USMFD0001).
- return reason codes (Sales and marketing > Setup > Returns > Return reason codes); in USMF
the codes are numbers, for example
- Lattice setup. A valid license (an expired or invalid one refuses these operations with
LicenseNotValid). To name customers, items or return orders by your own keys, set up an external system as in Cross-reference keys.
Steps
All three operations are on MGCLatticeSalesReturnService in MGCLatticeAccountsReceivableServiceGroup:
POST https://<your-environment>/api/services/MGCLatticeAccountsReceivableServiceGroup/MGCLatticeSalesReturnService/<operation>,
with the request in _request and an OAuth 2.0 client-credentials token. Use a new RequestId for each step and
resend the same one when you retry that step.
-
Create the return order.
createSalesReturn:{"_request": {"RequestId": "e2b7c1d4-58a9-4f0e-b3c6-7d8e9f0a1b2c","LegalEntityId": "USMF","UserContext": "Admin","CustAccount": "US-001","ReturnReasonCodeId": "21","ReturnDeadline": "2026-11-06","Lines": [ { "ItemId": "D0001", "Qty": 1 } ]}}The return order is a sales order of type Returned order, created with the standard D365 defaulting and validation.
ReturnReasonCodeIdapplies to every line. SendQtyas a positive number: the service stores it negative, as D365 does for returns. A line can also carrySiteIdandWarehouseId(orInventDimId) for the receiving storage dimensions, and its ownDispositionCode.DocumentIdandPostedDocumentIdare the new return order number:Return sales order 001389 created for customer US-001.Keep the number for the next steps. A return order created in D365 by hand works with steps 2 and 3 too. -
Receive the goods.
receiveSalesReturn:{"_request": {"RequestId": "0f1e2d3c-4b5a-4968-8778-a6b5c4d3e2f1","LegalEntityId": "USMF","UserContext": "Admin","SalesOrderId": "001389","PostingDate": "2026-10-07","DispositionCode": "11"}}This runs the standard return flow for the whole return order: every open line still awaiting arrival is registered with its disposition code (the request's
DispositionCode, unless the line already has one), and the return packing slip then moves the lines to Received. The request takes no lines.DocumentIdis the return order;PostedDocumentIdis the return packing slip number. The message readsReturn receipt posted for sales return 001389. -
Post the credit note.
creditSalesReturn:{"_request": {"RequestId": "7a8b9c0d-1e2f-4a3b-9c4d-5e6f7a8b9c0d","LegalEntityId": "USMF","UserContext": "Admin","SalesOrderId": "001389","PostingDate": "2026-10-07"}}PostedDocumentIdis the credit note number (for exampleCCN-000032):Credit note posted for sales return 001389.The credit note posts as an open credit on the customer's account.
Many returns at once. createSalesReturnBatch, receiveSalesReturnBatch and creditSalesReturnBatch take
{ "_requests": [ ... ] }, each item a full request with its own RequestId, and answer with one result per item
(see API reference, Bulk Operations).
None of the return operations has a preview: a request with "PreviewMode": true is refused with
PreviewUnsupported before anything runs.
How to verify
- The responses:
Successtrue,Status3,DocumentIdthe return order andPostedDocumentIdthe return order (create), the return packing slip (receive) or the credit note (credit). - The status query:
getSalesOrderStatusonMGCLatticeSalesOrderPostingServicewith the return order number asDocumentIdanswers for return orders too; after the credit noteDocumentStatusisInvoicedandStatusDetailscarriesPackingSlipDateandInvoiceDate(see Sales order to invoice). - OData read-back: the credit note is a row of
SalesInvoiceHeadersfor the return order:GET https://<your-environment>/data/SalesInvoiceHeaders?cross-company=true&$filter=dataAreaId eq 'USMF' and SalesOrderNumber eq '001389'. - In D365: Sales and marketing > Sales returns > All return orders, open the return order and check its return status and the lines' disposition code.
- Business events, when activated:
MGCLatticeSalesReturnOrderCreatedBusinessEventon create,MGCLatticeReturnOrderReceivedBusinessEventfor each line registered, andMGCLatticeReturnCreditNotePostedBusinessEventon the credit note (which also raisesMGCLatticeSalesOrderInvoicePostedBusinessEventwith a negative amount) (events).
When it goes wrong
ErrorCode | Typical cause in this flow | What to do |
|---|---|---|
ValidationFailed (1) | Missing ReturnReasonCodeId (ReturnReasonCodeId is required.), CustAccount or lines; a line without Qty (Request line 1 has no quantity: send the line's Qty.) or ItemId; receive or credit without SalesOrderId (SalesOrderId is required.); a line that has no disposition code while the receive request sends none (Return line 1 of 001389 has no disposition code: ...); an external key that does not resolve | Correct the request and send it again. |
DocumentNotFound (2) | The customer does not exist; the return order is not in the legal entity (Sales order 001399 was not found.); an unknown disposition code (Disposition code '99' was not found.) | Check the ids and LegalEntityId. |
InvalidStatus (3) | SalesOrderId names an ordinary sales order (Sales order 001367 is not a return order.) | Send the return order number. |
BusinessRule (4) | D365 refused the order or the posting (the reasons are in Errors), or there was nothing to post, for example a credit note for a return whose goods were already credited (Invoice of sales order 001389 posted nothing: no Invoice journal was created. Check that the order has open lines to post.) | Run the steps in order: create, receive, credit. Each step is rolled back completely on failure, so nothing is half-posted. |
PreviewUnsupported (10) | PreviewMode true | Send the request without preview. |
TransientRuntime (6) | A runtime fault; IsRetryable is true | Resend with the same RequestId. |
DuplicateRequest (7) | The RequestId was already used by another operation | Use a new RequestId per step. |
LicenseNotValid (12) | The Lattice license is expired or invalid | Renew the license, then resend. |
Retries are safe. Resending a successful step with the same RequestId creates or posts nothing again: the
answer repeats the original DocumentId and PostedDocumentId with the message
Duplicate request detected. Original request processed at .... A failed step is not remembered, so the same
RequestId runs again after you fix the cause. For RequestInProgress and RequestNeedsReview see
Sales order to invoice. When the returns arrive as files, a
failed record is kept in the dead-letter queue, see Inbound file to posted document.
Reference
- API reference: section 6 (Return Order Services) and section 19 (Bulk Operations)
- Endpoint payload specification:
MGCLatticeSalesReturnService - Security model
- Cross-reference keys
- Sales order to invoice
- Customer payment and settlement
- Post a journal exactly-once