Scope Item: 5HP — Advanced Intercompany Stock Transfer
Document Type: NBIC (Cross-Company, Delivery-Linked)
Verified on: SAP S/4HANA Cloud Public Edition
Test Date: 2026-09-22 / 2026-10-19
1. Why 5HP Reversal Has No Standard Path
5HP uses the NBIC purchase order type. Three constraints shape the reversal problem:
No Return STO for NBIC. SAP has confirmed that the aICO framework does not support a Return STO document type. The standard Return PO path for external supplier returns does not apply to intercompany transfers — the financial and stock account logic is fundamentally different.
The delivery chain is mandatory in both directions. Because NBIC requires OBD → PGI → IBD → GR in the forward flow, the reversal must mirror this in strict reverse. Skipping or reordering steps produces VL/066 locks or status inconsistencies.
Two WL-linked goods movements require UI intervention. The GRRV (MT107, WL-linked blocked stock GR) and the GIIV (Post GI from Issuing SiT, WL-linked) cannot be cancelled via OData API. Both must be cancelled through the Monitor Value Chains Fiori app before the delivery documents can be deleted.
2. Verified Reversal Sequence
Document chain (Full 5HP variant):
STO [NBIC — Plant 1310 → Plant 1320]
└─→ OBD
│ PGI material documents:
│ ├── MT681 (GIOD): Plant 1310 unrestricted -1, transit +1
│ └── MT107 (GRRV, WL-linked): Plant 1320 GR blocked +1
├─→ IV2 [auto-generated after PGI]
└─→ IBD [auto-generated after PGI]
│ IBD GR material documents (single GR posting):
│ ├── MT685 (GIIV, WL-linked): Plant 1310 SiT -1
│ ├── MT107 (GRRV, WL-linked): Plant 1320 GR blocked +1
│ └── MT109 (GRID): Plant 1320 GR blocked -1 → unrestricted +1Full 5HP identification: In Monitor Value Chains, check whether a “Post GI from Issuing SiT (GIIV)” step appears after PGI. If yes, the IBD GR creates all three material documents (GIIV + GRRV + GRID) together. If not, this is the simple variant and only MT109 (GRID) needs to be reversed in Step 2.
Reversal execution order:
Step 1 — Cancel IV2 billing document [API ✅]
Step 2 — ReverseGoodsReceipt on IBD [API ✅ — reverses MT109 + MT685 atomically]
Step 3 — Cancel GRRV (MT107, WL-linked) [UI ✅ — Monitor Value Chains, after Step 2]
Step 4 — Cancel GIIV (Post GI from Issuing SiT) [UI ✅ — Monitor Value Chains, after Step 3]
Step 5 — Delete IBD [API ✅ — HTTP 204, Steps 3+4 required first]
Step 6 — ReverseGoodsIssue on OBD [API ✅ — after Step 5]
Step 7 — Delete OBD [API ✅ — HTTP 204, after Step 6]
Step 8 — SetItemToDeleted on STO item [API ✅]3. Authentication and Session Architecture
All write operations use a Communication User via Basic Auth, provisioned through the relevant Communication Arrangement (SAP_COM_0022 for Delivery Processing Integration, SAP_COM_0006 for Material Documents, SAP_COM_0A80 for Stock Transport Orders, SAP_COM_0A95 for Billing Documents, SAP_COM_0734 for Outbound Delivery GI).
$user = “SALES_AGENT_API”
$pass=”<your-password>”
$base = “https://<your-tenant>-api.s4hana.cloud.sap”
$cookieJar = “$env:TEMP\session_$(Get-Date -Format ‘HHmmss').txt”
$ibdRoot = “$base/sap/opu/odata/sap/API_INBOUND_DELIVERY_SRV;v=2”
$obdRoot = “$base/sap/opu/odata/sap/API_OUTBOUND_DELIVERY_SRV;v=2”
$stoRoot = “$base/sap/opu/odata4/sap/api_stocktransportorder/srvd_a2x/sap/stocktransportorder/0001”
Session rule: Every POST must use curl -c/-b <cookie-jar> to maintain session continuity. The CSRF token fetch, entity GET, and FunctionImport POST must share the same HTTP session.
CSRF token fetch template:
# Fetch token against the target service root entity set (not base URL)
$fetchOut = & curl.exe -s -D – -o NUL `
-u “${user}:${pass}” `
-H “x-csrf-token: fetch” `
-H “Accept: application/json” `
-c $cookieJar `
“$ibdRoot/A_InbDeliveryHeader”
$csrf = ($fetchOut | Select-String “(?i)x-csrf-token:\s*(.+)”).Matches.Groups[1].Value.Trim()Each service (
API_INBOUND_DELIVERY_SRV,API_OUTBOUND_DELIVERY_SRV,api_stocktransportorder) requires its own CSRF token fetched in a separate session. Do not reuse a token across services.
4. Step 1 — Cancel Intercompany Invoice (IV2)
Fiori: Manage Billing Documents → select IV2 → Cancel
API:
GET /sap/opu/odata/sap/API_BILLING_DOCUMENT_SRV/A_BillingDocument(‘<IV2_NUMBER>')
?$format=json
# Extract __metadata.etag from response
POST /sap/opu/odata/sap/API_BILLING_DOCUMENT_SRV/Cancel?BillingDocument=”<IV2_NUMBER>”
&$format=json
x-csrf-token: <fetched>
If-Match: <etag_from_GET>
Accept: application/json
⚠️ETag required: The Cancel FunctionImport returns HTTP 428 if
If-Matchis missing. Always do a GET first to retrieve the ETag, then pass it in the POST. PassBillingDocumentas a query string parameter — embedding it in the URL path returns HTTP 404.
Check all IV2 documents for the OBD before cancelling:
GET /sap/opu/odata/sap/API_BILLING_DOCUMENT_SRV/A_BillingDocumentItem
?$filter=ReferenceSDDocument eq ‘<OBD_NUMBER>'
&$select=BillingDocument,BillingDocumentItem,ReferenceSDDocument
&$format=jsonThen verify each billing document's status:
GET /sap/opu/odata/sap/API_BILLING_DOCUMENT_SRV/A_BillingDocument(‘<BillingDocument>')
?$select=BillingDocument,BillingDocumentType,OverallBillingStatus,CancelledBillingDocument
&$format=jsonBillingDocumentType = IV2,OverallBillingStatus = C→ active invoice, cancel thisBillingDocumentType = IV2S→ cancellation document (expected, no action)BillingDocumentType = IV2with emptyCancelledBillingDocumentandOverallBillingStatus = A→ re-created by background job, also cancel this
All IV2 documents must be in cancelled state before Step 6.
⚠️Billing re-creation trap: After cancelling IV2, the OBD's
OverallIntcoBillingStatusresets toA, making it eligible for re-billing. If a background billing job (BILLING_DOCUMENT_RUN) runs before Step 6, it will create a new IV2 and blockReverseGoodsIssueagain. Execute Steps 1 through 6 in a single uninterrupted session — no idle time between them.
5. Step 2 — Reverse Goods Receipt on IBD (GRID + GIIV)
Service: API_INBOUND_DELIVERY_SRV (OData V2)
Prerequisite: IBD OverallGoodsMovementStatus = C
In the Full 5HP variant, this single API call atomically reverses:
- MT109 (GRID): removes stock from receiving plant unrestricted inventory
- MT685 (GIIV): restores SiT +1 at issuing plant (required for Step 6 to succeed)
The WL-linked MT107 (GRRV) is intentionally not reversed here — it is handled in Step 3.
$ibd = “<IBD_NUMBER>”
# Fetch CSRF token for IBD service
$fetchOut = & curl.exe -s -D – -o NUL `
-u “${user}:${pass}” `
-H “x-csrf-token: fetch” `
-H “Accept: application/json” `
-c $cookieJar `
“$ibdRoot/A_InbDeliveryHeader”
$csrf = ($fetchOut | Select-String “(?i)x-csrf-token:\s*(.+)”).Matches.Groups[1].Value.Trim()
# ReverseGoodsReceipt — do NOT include If-Match (returns HTTP 501 if added)
& curl.exe -s -X POST `
-u “${user}:${pass}” `
-H “x-csrf-token: $csrf” `
-H “Accept: application/json” `
-b $cookieJar -c $cookieJar `
-w “`nHTTP:%{http_code}” `
“$ibdRoot/ReverseGoodsReceipt?DeliveryDocument=”${ibd}”&ActualGoodsMovementDate=datetime'<REVERSAL_DATE>T00%3A00%3A00'&`$format=json”
# Expected: HTTP 200, IBD OverallGoodsMovementStatus → A
Verified response (HTTP 200):
{ “d”: { “results”: [] } }An empty results array confirms clean reversal with no warnings.
⚠️Critical — never cancel GRID directly: Cancelling MT109 via Monitor Value Chains or
API_MATERIAL_DOCUMENT_SRVwithout going throughReverseGoodsReceiptleaves MT685 (GIIV) unreversed. This depletes SiT to 0 at the issuing plant, causingVL/624on the subsequentReverseGoodsIssuecall. The chain becomes unrecoverable through standard paths.
6. Step 3 — Cancel WL-linked Blocked Stock (GRRV / MT107)
Fiori: Monitor Value Chains → navigate to the GRRV (MT107) document → Cancel
⚠️The Cancel button only appears AFTER Step 2 (
ReverseGoodsReceipt) is complete. It is not present before that — do not look for it earlier.
This step removes the WL-linked GR blocked stock at the receiving plant. It must be performed before Step 5 (Delete IBD) and before Steps 5–7 can succeed.
API status: ❌ M7/492 — WL-linked document cancellation via API_MATERIAL_DOCUMENT_SRV is blocked. Monitor Value Chains is the only viable path.
7. Step 4 — Cancel Post GI from Issuing SiT (GIIV)
Fiori: Monitor Value Chains → navigate to the GIIV document → Cancel
Same pattern as Step 3. While in Monitor Value Chains on the STO header, find the GIIV row in the Documents panel, click its Navigation arrow button to open the detail view, then use the Cancel button in the header toolbar.
This step cancels the GIIV process step. It must be completed (together with Step 3) before Steps 5–7 — without it, OBD OverallGoodsMvtStatus remains C after Step 6, causing Step 7 DELETE to fail with VL/066.
API status: ❌ — no OData equivalent. Monitor Value Chains UI Cancel is the only path.
⚠️UI5 button behavior: Standard DOM
element.click()silently fails on SAP UI5 controls. Use Playwrightbrowser_clickvia snapshot ref (real mousedown/mouseup) orsap.ui.getCore().byId(id).firePress()as fallback.
8. Step 5 — Delete Inbound Delivery
Prerequisite: Steps 3+4 (GRRV and GIIV Monitor Value Chain Cancels) complete. IBD OverallGoodsMovementStatus = A.
API: ✅ DELETE via API_INBOUND_DELIVERY_SRV with If-Match: * (HTTP 204).
$ibd = “<IBD_NUMBER>”
$cookieJarIBD = “$env:TEMP\ibd_$(Get-Date -Format ‘HHmmss').txt”
# Fetch CSRF token
$fetchOut = & curl.exe -s -D – -o NUL `
-u “${user}:${pass}” `
-H “x-csrf-token: fetch” `
-H “Accept: application/json” `
-c $cookieJarIBD `
“$ibdRoot/A_InbDeliveryHeader”
$csrf = ($fetchOut | Select-String “(?i)x-csrf-token:\s*(.+)”).Matches.Groups[1].Value.Trim()
# DELETE IBD
& curl.exe -s -X DELETE `
-u “${user}:${pass}” `
-H “x-csrf-token: $csrf” `
-H “If-Match: *” `
-H “Accept: application/json” `
-b $cookieJarIBD -c $cookieJarIBD `
-w “`nHTTP_STATUS:%{http_code}” `
“$ibdRoot/A_InbDeliveryHeader(‘${ibd}')”
# Expected: HTTP 204 (no body)
Verify deletion: GET the IBD → expect HTTP 404 “Resource not found”.
⚠️VL/066 on DELETE means Steps 3+4 were incomplete. Complete GRRV and GIIV Monitor Value Chain Cancels first, then retry.
9. Step 6 — Reverse Goods Issue on OBD
Service: API_OUTBOUND_DELIVERY_SRV (OData V2)
Prerequisites:
- Step 2 (GR Reversal) complete: IBD
OverallGoodsMovementStatus = A - Steps 3+4 (GRRV and GIIV cancel) complete
- Step 5 (IBD deleted) complete
- Step 1 (IV2 cancelled) complete: OBD
OverallIntcoBillingStatus = A
Verification before calling:
# Confirm OBD billing status is A and all IV2 documents are cancelled
& curl.exe -s -u “${user}:${pass}” `
-H “Accept: application/json” `
“$obdRoot/A_OutbDeliveryHeader(‘<OBD_NUMBER>')?`$select=OverallIntcoBillingStatus,OverallGoodsMvtStatus&`$format=json”
# Required: OverallIntcoBillingStatus=”A”API call:
$obd = “<OBD_NUMBER>”
$cookieJarOBD = “$env:TEMP\obd_$(Get-Date -Format ‘HHmmss').txt”
$fetchOut = & curl.exe -s -D – -o NUL `
-u “${user}:${pass}” `
-H “x-csrf-token: fetch” `
-H “Accept: application/json” `
-c $cookieJarOBD `
“$obdRoot/A_OutbDeliveryHeader”
$csrf = ($fetchOut | Select-String “(?i)x-csrf-token:\s*(.+)”).Matches.Groups[1].Value.Trim()
# ReverseGoodsIssue — do NOT include If-Match (returns HTTP 501 if added)
& curl.exe -s -X POST `
-u “${user}:${pass}” `
-H “x-csrf-token: $csrf” `
-H “Accept: application/json” `
-b $cookieJarOBD -c $cookieJarOBD `
-w “`nHTTP_STATUS:%{http_code}” `
“$obdRoot/ReverseGoodsIssue?DeliveryDocument=”${obd}”&ActualGoodsMovementDate=datetime'<REVERSAL_DATE>T00:00:00'&`$format=json”
# Expected: HTTP 200
Fiori alternative (after IBD deleted): Reverse GI – Outbound Delivery (VL09) also becomes available once the IBD is deleted in Step 5.
🔑OBD status after this call: When Steps 3+4 Monitor Value Chain Cancels were completed first,
OBD.OverallGoodsMvtStatuscorrectly updates C→A after this call, unblocking Step 7 DELETE. If status stays C, Steps 3 or 4 were incomplete — complete them and retry.
10. Step 7 — Delete Outbound Delivery
Prerequisite: Step 6 (GI reversed) complete. OBD OverallGoodsMvtStatus = A.
API: ✅ DELETE via API_OUTBOUND_DELIVERY_SRV with If-Match: * (HTTP 204).
$obd = “<OBD_NUMBER>”
$cookieJarOBD2 = “$env:TEMP\obd2_$(Get-Date -Format ‘HHmmss').txt”
# Fetch CSRF token
$fetchOut = & curl.exe -s -D – -o NUL `
-u “${user}:${pass}” `
-H “x-csrf-token: fetch” `
-H “Accept: application/json” `
-c $cookieJarOBD2 `
“$obdRoot/A_OutbDeliveryHeader”
$csrf = ($fetchOut | Select-String “(?i)x-csrf-token:\s*(.+)”).Matches.Groups[1].Value.Trim()
# DELETE OBD
& curl.exe -s -X DELETE `
-u “${user}:${pass}” `
-H “x-csrf-token: $csrf” `
-H “If-Match: *” `
-H “Accept: application/json” `
-b $cookieJarOBD2 -c $cookieJarOBD2 `
-w “`nHTTP_STATUS:%{http_code}” `
“$obdRoot/A_OutbDeliveryHeader(‘${obd}')”
# Expected: HTTP 204 (no body)
Verify deletion: GET the OBD → expect HTTP 404.
⚠️If VL/066 appears: OBD status is still C — Steps 3 and/or 4 Monitor Value Chain Cancels were not completed. Go back and complete Steps 3+4, retry Step 6 before retrying Step 7.
11. Step 8 — Set STO Item to Deleted
Service: api_stocktransportorder (OData V4)
$sto = “<STO_NUMBER>”
$item = “<ITEM_NUMBER>”
$cookieJarSTO = “$env:TEMP\sto_$(Get-Date -Format ‘HHmmss').txt”
$fetchOut = & curl.exe -s -D – -o NUL `
-u “${user}:${pass}” `
-H “x-csrf-token: fetch” `
-H “Accept: application/json” `
-c $cookieJarSTO `
“$stoRoot/StockTransportOrderItem”
$csrf = ($fetchOut | Select-String “(?i)x-csrf-token:\s*(.+)”).Matches.Groups[1].Value.Trim()
‘{}' | Set-Content -Path “$env:TEMP\empty.json” -Encoding UTF8
& curl.exe -s -X POST `
-u “${user}:${pass}” `
-H “x-csrf-token: $csrf” `
-H “Accept: application/json” `
-H “Content-Type: application/json” `
-b $cookieJarSTO -c $cookieJarSTO `
-w “`nHTTP:%{http_code}” `
–data-binary “@$env:TEMP\empty.json” `
“$stoRoot/StockTransportOrderItem(StockTransportOrder=”${sto}”,StockTransportOrderItem='${item}')/SAP__self.SetItemToDeleted”
# Expected: HTTP 200, STO item DeletionCode = L
12. Material Document Chain — Net Inventory Impact
After all reversal steps complete, the net inventory impact is zero across both plants.
Material Document Movement Type Plant Stock Type Direction Reversed By
| GIOD | MT681 | 1310 Issuing | Unrestricted | −1 (issue) | Step 6 ReverseGoodsIssue |
| GIOD | MT681 | 1310 Issuing | Transit/SiT | +1 (receipt into transit) | Step 6 ReverseGoodsIssue |
| GRRV | MT107 (WL) | 1320 Receiving | GR blocked | +1 (receipt) | Step 3 Monitor Value Chain Cancel |
| GIIV | MT685 (WL) | 1310 Issuing | Transit/SiT | −1 (issue from SiT) | Step 2 ReverseGoodsReceipt |
| GRID | MT109 | 1320 Receiving | Unrestricted | +1 (transfer from GR blocked) | Step 2 ReverseGoodsReceipt |
13. Verification Checklist
Use these GET calls to confirm prerequisite state before each write operation:
# Before Step 2: confirm IBD GR is posted
GET $ibdRoot/A_InbDeliveryHeader(‘<IBD_NUMBER>')?$select=OverallGoodsMovementStatus
# Required: OverallGoodsMovementStatus=”C”
# Before Step 6: confirm IV2 cancelled and IBD deleted
GET $obdRoot/A_OutbDeliveryHeader(‘<OBD_NUMBER>')?$select=OverallIntcoBillingStatus,OverallGoodsMvtStatus
# Required: OverallIntcoBillingStatus=”A”
# Before Step 6: no active IV2 remains
GET API_BILLING_DOCUMENT_SRV/A_BillingDocumentItem?$filter=ReferenceSDDocument eq ‘<OBD_NUMBER>'
# All IV2 type docs should have CancelledBillingDocument filled
# Any IV2 with OverallBillingStatus=”A” and empty CancelledBillingDocument is a re-created doc — cancel it first
14. If-Match Quick Reference
FunctionImport Service If-Match Rule Error If Wrong
Cancel | BillingDoc (API_BILLING_DOCUMENT_SRV) | Must be ETag from GET | 428 (missing) |
PostGoodsIssue | OBD | Must be * | 412 (outdated) / 428 (missing) |
ReverseGoodsIssue | OBD | Must NOT be included | 501 (/IWBEP/CM_MGW_RT/018) |
PostGoodsReceipt | IBD | Must be * | 412 / 428 |
ReverseGoodsReceipt | IBD | Must NOT be included | 501 (/IWBEP/CM_MGW_RT/018) |
| DELETE | IBD / OBD | Must be * | 412 |
15. API Automation Summary
Step Action API Viable? Method / Endpoint Notes
| 1 | Cancel IV2 | ✅Yes | POST API_BILLING_DOCUMENT_SRV/Cancel | GET ETag first; check for billing re-creation between Steps 1 and 6 |
| 2 | Reverse GRID + GIIV | ✅Yes | POST API_INBOUND_DELIVERY_SRV/ReverseGoodsReceipt | No If-Match. Atomic: reverses both MT109 and MT685 in Full 5HP |
| 3 | Cancel GRRV (MT107) | ❌UI only | Monitor Value Chains → Cancel | Button appears only after Step 2. API blocked M7/492 |
| 4 | Cancel GIIV process step | ❌UI only | Monitor Value Chains → Cancel | Required before Steps 5–7; without it OBD status stays C |
| 5 | Delete IBD | ✅Yes | DELETE API_INBOUND_DELIVERY_SRV/A_InbDeliveryHeader | If-Match: *. Steps 3+4 must be complete first (HTTP 204) |
| 6 | Reverse GI on OBD | ✅Yes | POST API_OUTBOUND_DELIVERY_SRV/ReverseGoodsIssue | No If-Match. VL09 Fiori also works after Step 5 |
| 7 | Delete OBD | ✅Yes | DELETE API_OUTBOUND_DELIVERY_SRV/A_OutbDeliveryHeader | If-Match: *. OBD status must be A after Step 6 (HTTP 204) |
| 8 | Set STO item to Deleted | ✅Yes | POST api_stocktransportorder/…/SetItemToDeleted | OData V4 bound action, empty body {} |
Automation boundary: Steps 1, 2, 5, 6, 7, and 8 are fully API-automatable. Steps 3 and 4 require UI intervention via Monitor Value Chains. The two UI steps are a hard prerequisite for the API steps that follow — they cannot be skipped.
The features covered in this article are based on SAP S/4HANA Cloud, Public Edition 2608, please refer to the latest information for changes in subsequent versions.
Hope you LIKE it if it addresses your issue. After that, please feel free to comment after following my account and I will reply ASAP.
Source link
