Skip to main content

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​

  1. Security. MGCLatticeAccountsReceivableServiceMaintain grants createSalesReturn, receiveSalesReturn, creditSalesReturn and their batch variants. MGCLatticeIntegrationOperatorRole and MGCLatticeIntegrationAdministratorRole hold it. See Security model. The SalesInvoiceHeaders read-back below is a standard Microsoft entity and needs the standard D365 privilege.
  2. 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) and 11 (Obsolete);
    • disposition codes (Sales and marketing > Setup > Returns > Disposition codes). The code's action decides the flow: in USMF, 11 and 12 are Credit (receive into inventory, then credit note), 21 is Credit only, 61 is Scrap. This walkthrough uses 11;
    • 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 (USMF D0001).
  3. 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.

  1. 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. ReturnReasonCodeId applies to every line. Send Qty as a positive number: the service stores it negative, as D365 does for returns. A line can also carry SiteId and WarehouseId (or InventDimId) for the receiving storage dimensions, and its own DispositionCode. DocumentId and PostedDocumentId are 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.

  2. 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. DocumentId is the return order; PostedDocumentId is the return packing slip number. The message reads Return receipt posted for sales return 001389.

  3. Post the credit note. creditSalesReturn:

    {
    "_request": {
    "RequestId": "7a8b9c0d-1e2f-4a3b-9c4d-5e6f7a8b9c0d",
    "LegalEntityId": "USMF",
    "UserContext": "Admin",
    "SalesOrderId": "001389",
    "PostingDate": "2026-10-07"
    }
    }

    PostedDocumentId is the credit note number (for example CCN-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: Success true, Status 3, DocumentId the return order and PostedDocumentId the return order (create), the return packing slip (receive) or the credit note (credit).
  • The status query: getSalesOrderStatus on MGCLatticeSalesOrderPostingService with the return order number as DocumentId answers for return orders too; after the credit note DocumentStatus is Invoiced and StatusDetails carries PackingSlipDate and InvoiceDate (see Sales order to invoice).
  • OData read-back: the credit note is a row of SalesInvoiceHeaders for 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: MGCLatticeSalesReturnOrderCreatedBusinessEvent on create, MGCLatticeReturnOrderReceivedBusinessEvent for each line registered, and MGCLatticeReturnCreditNotePostedBusinessEvent on the credit note (which also raises MGCLatticeSalesOrderInvoicePostedBusinessEvent with a negative amount) (events).

When it goes wrong​

ErrorCodeTypical cause in this flowWhat 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 resolveCorrect 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 trueSend the request without preview.
TransientRuntime (6)A runtime fault; IsRetryable is trueResend with the same RequestId.
DuplicateRequest (7)The RequestId was already used by another operationUse a new RequestId per step.
LicenseNotValid (12)The Lattice license is expired or invalidRenew 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​