Introduction: The Business Need
Consider a common scenario in industrial automation: TechWave Solutions GmbH sells welding robots along with a suite of value-added services — installation, annual technical support, remote diagnostics, and custom training. While the hardware ships through standard stock-based fulfillment, services follow a different commercial rhythm.
Their customer, Apex Manufacturing Ltd., has purchased an Annual Remote Diagnostics Service Package for ¥36,000. Rather than billing the full amount upfront, the contract specifies milestone-based payments:
Milestone Trigger Amount %
| 1 — Contract Signing | Upon SO confirmation | ¥21,600 | 60% |
| 2 — Service Activation | Remote agent deployed | ¥14,400 | 40% |
This pattern — splitting a service contract into progress-based or event-driven invoices — is milestone billing. It is increasingly common across industries where service delivery spans weeks or months: industrial IoT monitoring, managed infrastructure, phased consulting engagements, and SaaS implementations with go-live milestones.
Why milestone billing matters:
- Cash flow alignment — Invoice when value is delivered, not all at once
- Revenue recognition — IFRS 15 / ASC 606 compliance for performance obligations satisfied over time
- Customer relationship — Reduces upfront payment resistance; ties cost to tangible progress
- Contractual flexibility — Block invoicing until a milestone is formally accepted
In SAP S/4HANA Cloud, milestone billing is implemented through Billing Plans on sales order items. This guide walks you through the complete end-to-end flow using OData APIs — from master data and pricing through to the final billing document. Every step includes copy-pasteable JSON payloads verified against a live S/4HANA Cloud system.
Prerequisites & Configuration Overview
System Landscape
- SAP S/4HANA Cloud, public edition (tested on release 2502)
- OData API access via Communication Arrangements:
- SAP_COM_0109 — Sales Order Integration (API_SALES_ORDER_SRV)
- SAP_COM_0303 — Billing Document Integration (V4 api_billingdocument)
- SAP_COM_0116 — Pricing Condition Record (API_SLSPRICINGCONDITIONRECORD_SRV)
Configuration Elements Required
Element App / SSCUI Key Setting
| Item Category | SSCUI 100394 — Define Item Categories | CTAD with Billing Plan Type = 90 |
| Billing Block Reasons | SSCUI 101494 — Define Billing Block Reasons | 03, Y2, etc. |
Note: In S/4HANA Cloud public edition, customizing activities are managed through SAP Central Business Configuration (CBC). Open the “Manage Your Solution” app in Fiori Launchpad to access the CBC portal link, then search for the SSCUI ID (e.g., 100394) to locate and modify the configuration activity.
Why Item Category CTAD?
This is the single most important configuration decision. In a standard S/4HANA Cloud system:
- TAD (default for OR + service material) — does NOT support billing plans (no billing plan type assigned in SSCUI 100394)
- CTAD — supports billing plan type 90 (milestone billing)
- TAO / TAM / TAP — do not exist in S/4HANA Cloud public edition
You must explicitly specify SalesOrderItemCategory: "CTAD" when creating the sales order via API. The system will not auto-determine it from item category determination (SSCUI 100393) for this use case.
Step 1: Sales Order Creation
API Call
POST /sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder
Content-Type: application/json
{
"SalesOrderType": "OR",
"SalesOrganization": "1320",
"DistributionChannel": "20",
"OrganizationDivision": "00",
"SoldToParty": "1000260",
"PurchaseOrderByCustomer": "DIAG-2026-ANNUAL",
"CustomerPaymentTerms": "0001",
"IncotermsClassification": "EXW",
"IncotermsLocation1": "SHANGHAI",
"RequestedDeliveryDate": "/Date(1778803200000)/",
"to_Item": [{
"Material": "LQ_SRV-DIAG",
"RequestedQuantity": "1",
"RequestedQuantityUnit": "H",
"SalesOrderItemCategory": "CTAD"
}]
}
Critical: You must explicitly set SalesOrderItemCategory: "CTAD". If omitted, the system defaults to TAD, which does not support billing plans.
Response: HTTP 201 — Sales Order number returned (e.g., 588), Net Amount = ¥36,000.00 CNY
Post-Creation: Set Production Plant
The item needs a plant assignment for billing to work. PATCH the item:
PATCH /sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrderItem(SalesOrder="588",SalesOrderItem='10')
If-Match: *
{
"ProductionPlant": "1320"
}
Response: HTTP 204
Step 2: Billing Plan Configuration
This is the core of milestone billing. The billing plan is a separate entity attached to the sales order item — it cannot be created via deep-insert during SO creation.
Step 2a: Create Billing Plan Header
POST /sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrderItemBillingPlan
Content-Type: application/json
{
"SalesOrder": "588",
"SalesOrderItem": "10",
"BillingPlanType": "90",
"BillingPlanStartDate": "/Date(1778803200000)/"
}
Key field: BillingPlanType: "90" = Milestone Billing
Response: HTTP 201 — Billing Plan created. Retrieve the plan number:
GET /sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrderItemBillingPlan
?$filter=SalesOrder eq '588' and SalesOrderItem eq '10'
Returns BillingPlan: "246" (system-assigned number).
Step 2b: Create Milestone Dates
Each milestone is a billing plan item (date line). Create them via:
POST /sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SlsOrderItemBillingPlanItem
Content-Type: application/json
Milestone #1 — Contract Signing (60%, ¥21,600):
{
"SalesOrder": "588",
"SalesOrderItem": "10",
"BillingPlan": "246",
"BillingPlanDateCategory": "04",
"BillingPlanBillingDate": "/Date(1778803200000)/",
"BillingPlanAmount": "21600.00",
"BillingBlockReason": ""
}
Milestone #2 — Service Activation (40%, ¥14,400):
{
"SalesOrder": "588",
"SalesOrderItem": "10",
"BillingPlan": "246",
"BillingPlanDateCategory": "04",
"BillingPlanBillingDate": "/Date(1781481600000)/",
"BillingPlanAmount": "14400.00",
"BillingBlockReason": "03"
}
Field reference:
Field Value Meaning
BillingPlanDateCategory |
"04" |
Milestone date (vs. “01” periodic, “02” partial) |
BillingPlanBillingDate |
/Date(ms)/ |
When this milestone becomes due |
BillingPlanAmount |
decimal | Fixed amount for this milestone |
BillingBlockReason |
"" or "03" |
Empty = ready to bill; “03” = blocked until release |
Important: Clear Auto-Assigned Billing Block
The system automatically assigns billing block “Y2” to newly created milestones. You must explicitly clear it for any milestone you want to bill immediately:
PATCH /sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SlsOrderItemBillingPlanItem
(SalesOrder="588",SalesOrderItem='10',BillingPlan='246',BillingPlanItem='1')
If-Match: *
{
"BillingBlockReason": ""
}
Response: HTTP 204
Note: This auto-block behavior is system-specific. Your system may use a different default block or none at all, depending on billing plan type configuration.
Step 3: Create Billing Document
With Milestone #1 unblocked and its billing date reached, we can now generate the invoice. S/4HANA Cloud uses the OData V4 Billing Document API with the CreateFromSDDocument action.
API Call
POST /sap/opu/odata4/sap/api_billingdocument/srvd_a2x/sap/billingdocument/0001/
BillingDocument/com.sap.gateway.srvd_a2x.api_billingdocument.v0001.CreateFromSDDocument
Content-Type: application/json
{
"_Control": {
"DefaultBillingDocumentType": "FAZ",
"AutomPostingToAcctgIsDisabled": true
},
"_Reference": [{
"SDDocument": "0000000588",
"SDDocumentCategory": "C",
"BillingDocumentType": "FAZ",
"BillingDocumentDate": "2026-05-15",
"DestinationCountry": "CN",
"SalesOrganization": "1320"
}]
}
Key points:
- Billing Type
FAZ— This is a Down Payment Request, which is the standard billing document type for milestone billing plans. Do NOT useF2(standard invoice) — it will fail. SDDocumentmust be zero-padded to 10 charactersSDDocumentCategory: "C"= Sales OrderAutomPostingToAcctgIsDisabled: true— Optional; set to false if you want immediate FI posting- The API automatically identifies which milestones are eligible (unblocked + date reached) and bills only those
Response: HTTP 200
{
"value": [{
"BillingDocument": "90000458",
"BillingDocumentType": "FAZ",
"TotalNetAmount": 21600.0,
"TransactionCurrency": "CNY",
"BillingDocumentDate": "2026-05-15",
"SoldToParty": "1000260"
}]
}
The system created billing document 90000458 for exactly ¥21,600 — the 60% milestone amount.
Step 4: Verification & Monitoring
Confirm Milestone Status
Read back the billing plan items to verify that Milestone #1 is now closed:
GET /sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SlsOrderItemBillingPlanItem
?$filter=SalesOrder eq '588' and SalesOrderItem eq '10'
# Date Amount Block Status Meaning
| 1 | 2026-05-15 | ¥21,600 | — | C | Closed (billed) |
| 2 | 2026-06-15 | ¥14,400 | 03 | A | Open (blocked) |
Releasing Milestone #2
When the service is activated and you're ready to invoice the remaining 40%, simply remove the billing block:
PATCH /A_SlsOrderItemBillingPlanItem(SalesOrder="588",SalesOrderItem='10',
BillingPlan='246',BillingPlanItem='2')
If-Match: *
{
"BillingBlockReason": ""
}
Then call CreateFromSDDocument again — it will generate a second FAZ document for ¥14,400.
Fiori App “Create Billing Documents” (WebGUI: VF04) — Billing Due List
For monitoring across multiple orders, use the Fiori app Create Billing Documents (app ID F0798, or WebGUI transaction VF04):
- Filter by Sales Documents (check the checkbox)
- Enter your SD Document number
- The list shows all eligible billing plan dates with their processing status
Troubleshooting & Common Pitfalls
Symptom Root Cause Solution
| “No billing plan type maintained in sales document item category” | Using TAD instead of CTAD | Explicitly set SalesOrderItemCategory: "CTAD" (verify in SSCUI 100394) |
CreateFromSDDocument returns 400 “Check input parameters” |
Billing date in wrong year (timestamp error) | Verify /Date() values — 2026 ≠ 2025! |
| “Create Billing Documents” app shows 0 results | “Sales Documents” checkbox not selected, or billing date filter too narrow | Check both settings |
| Milestone created with unexpected block | System default behavior assigns Y2 | PATCH BillingBlockReason to "" |
| “Document is incomplete so billing cannot be carried out” | Missing Plant or other required fields | Set ProductionPlant on the item; ensure delivery date is set |
| Billing doc created but no FI posting | Missing account assignment group (KTGRM) on material | Maintain KTGRM = XB in material master |
V2 SD_CUSTOMER_INVOICES_CREATE returns 403 |
API user lacks authorization | This service requires a separate communication arrangement; use V4 API instead |
| Billing creates F2 instead of FAZ | Wrong billing type specified | Use DefaultBillingDocumentType: "FAZ" in the V4 API call |
Timestamp Reference
OData V2 uses milliseconds since epoch in UTC:
Date /Date() Value
| 2026-01-01 | /Date(1767225600000)/ |
| 2026 |



